testsAndMisc/phone_focus_mode/daemon_state.sh
Krzysztof kuhy Rudnicki 094c4765f3
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests
Location/curfew goes to daemon_location.sh, the derived state files to
daemon_state.sh, the app layer to daemon_apps.sh. All 23 functions moved
verbatim -- nothing restructured, which matters more here than anywhere
else in this session.

focus_daemon.sh has five `while IFS= read -r pkg; do ... done <file` loops
and EVERY body calls pm. Piping any of them in lets pm inherit and drain
the loop's stdin, so it would process one package instead of all of them --
measured elsewhere in this repo as 1 of 54. That audit ran BEFORE the
extraction, not after coverage came back short, and all five moved
untouched with a note above them saying why.

CURRENT_MODE and _CURFEW_TICK_CACHED are each written in both the entry
script and a library, so both stay declared with main, same call as
NET_BUILT in the curfew split.

110 assertions and 33 mutations, all killed. Two survived the first run and
both were defects in my own tests: the whitelist and sysprotect exactness
checks used a SUPERSTRING of a listed entry, which fails under grep -qF and
grep -qxF alike and so proved nothing about which was in use.

Two subjects are NOT at 100%, and the numbers are reported as measured:

  daemon_state.sh     67/67 = 100.00%
  daemon_location.sh  26/40 =  65.00%
  daemon_apps.sh      71/76 =  93.42%

Both shortfalls are kcov attribution failures, shown to be artifacts rather
than gaps. daemon_location.sh's missing lines are all inside the embedded
awk Haversine; mutating the Earth radius and dropping the longitude term
both fail the suite, and an isolated probe shows a one-line awk measuring
100% where the identical multi-line one measures 0%. Collapsing the formula
to satisfy the tool would cost the readability of the only real mathematics
in the repo, so it stays.

daemon_apps.sh's five are the trailing `done <file` of the very loops
described above. That is the same artifact piping fixed in curfew_net.sh,
and it is exactly what must NOT be done here. Redirecting two of them from
/dev/null fails the suite, so the lines run; all three are pinned in the
mutation spec instead.

Over-cap count 19 -> 18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:54:04 +02:00

141 lines
6.2 KiB
Bash
Executable File

#!/system/bin/sh
# shellcheck shell=ash
# daemon_state.sh — the state files focus_daemon.sh derives from config: the
# whitelists, the system-protect and blocked-system lists, the cached default
# intent handlers, and the JSON status snapshot the companion app reads.
#
# A flat sibling, not lib/: deploy.sh pushes phone scripts into one directory.
#
# Sourced by focus_daemon.sh, which owns log() and the config.sh globals these
# read ($WHITELIST, $NIGHT_WHITELIST, $SYSTEM_NEVER_DISABLE,
# $BLOCKED_SYSTEM_APPS, $STATE_DIR, $STATUS_FILE, $RADIUS). Nothing here is
# assigned, so there is no global to keep in step.
build_whitelist_file() {
echo "$WHITELIST" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' |
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' >"$STATE_DIR/whitelist.txt"
# Sanity check: the WHITELIST string in config.sh is fragile - any
# literal double-quote inside a comment will close the heredoc and
# silently truncate the variable. Log the parsed line count so any
# future regression is visible in the log, and warn loudly if it
# falls below a known floor (we always have ~70+ entries).
local n
n=$(wc -l <"$STATE_DIR/whitelist.txt" 2>/dev/null | tr -d ' ')
log "Whitelist parsed: $n entries"
if [ "${n:-0}" -lt 30 ]; then
log "WARN: whitelist suspiciously small ($n lines) - check config.sh for stray quotes inside WHITELIST string"
fi
}
build_night_whitelist_file() {
# Strict allow-list used while the night curfew is active (see config.sh
# NIGHT_WHITELIST and is_curfew_now()). Parsed exactly like the day list.
echo "$NIGHT_WHITELIST" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' |
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' >"$STATE_DIR/night_whitelist.txt"
local n
n=$(wc -l <"$STATE_DIR/night_whitelist.txt" 2>/dev/null | tr -d ' ')
log "Night-curfew whitelist parsed: $n entries"
if [ "${n:-0}" -lt 10 ]; then
log "WARN: night whitelist suspiciously small ($n lines) - check config.sh for stray quotes inside NIGHT_WHITELIST string"
fi
}
build_sysprotect_file() {
echo "$SYSTEM_NEVER_DISABLE" | grep -v '^[[:space:]]*$' |
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' >"$STATE_DIR/sysprotect.txt"
}
# $BLOCKED_SYSTEM_APPS is a static config value (never changes without a
# daemon restart), so - like build_sysprotect_file above - this only needs
# to run once at startup, not every enable_focus_mode() sweep.
build_blocked_sys_file() {
echo "$BLOCKED_SYSTEM_APPS" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' |
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' >"$STATE_DIR/blocked_sys.txt"
}
# ---- Default handler detection ----
# Refreshed once per focus_daemon tick into $STATE_DIR/default_handlers.txt.
# Each line is a package name. Lookup is a cheap grep against this file.
refresh_default_handlers() {
local f="$STATE_DIR/default_handlers.txt"
local tmp="$f.tmp"
: >"$tmp"
# Default Home (launcher). resolve-activity prints "Activity Resolver Table:"
# on line 1 and "<pkg>/<.Activity>" on line 2 in --brief mode.
cmd package resolve-activity --brief \
-c android.intent.category.HOME -a android.intent.action.MAIN 2>/dev/null |
awk -F/ 'NR==2 && $1 != "" {print $1}' >>"$tmp"
# Default Dialer
local dialer
dialer="$(cmd telecom get-default-dialer 2>/dev/null | tr -d '[:space:]')"
[ -n "$dialer" ] && echo "$dialer" >>"$tmp"
# Default SMS handler (settings provider key)
local sms
sms="$(settings get secure sms_default_application 2>/dev/null | tr -d '[:space:]')"
[ -n "$sms" ] && [ "$sms" != "null" ] && echo "$sms" >>"$tmp"
# Default input method (active keyboard). Disabling the active IME with
# pm disable-user PERSISTS across reboot; a 1am reboot would then leave no
# keyboard to type any recovery command. Protect it day and night so the
# curfew can never lock you out of typing.
local ime
ime="$(settings get secure default_input_method 2>/dev/null | cut -d/ -f1)"
[ -n "$ime" ] && [ "$ime" != "null" ] && echo "$ime" >>"$tmp"
sort -u "$tmp" -o "$f"
rm -f "$tmp"
# Default Browser handler is tracked SEPARATELY and guarded only OUTSIDE
# the curfew window (see is_allowed). During curfew the whole point is to
# disable browsers, so the default-handler guard must not resurrect them.
local bf="$STATE_DIR/default_browser.txt"
cmd package resolve-activity --brief \
-a android.intent.action.VIEW -d http://example.com 2>/dev/null |
awk -F/ 'NR==2 && $1 != "" {print $1}' >"$bf.tmp" 2>/dev/null
mv "$bf.tmp" "$bf" 2>/dev/null || : >"$bf"
}
is_default_handler() {
local pkg="$1"
grep -qxF "$pkg" "$STATE_DIR/default_handlers.txt" 2>/dev/null
}
# ---- Status snapshot for companion notification app ----
# Writes a tiny JSON file that focus_status_app reads every few seconds.
# Fields: mode, lat, lon, distance_m, threshold_m, radius_m, disabled_count,
# last_check_ts (unix), last_check_iso (human).
write_status_snapshot() {
local mode="$1" lat="$2" lon="$3" dist="$4" thr="$5"
local count iso ts cf ov frc
count="$(wc -l <"$DISABLED_APPS_FILE" 2>/dev/null | tr -d ' ' || echo 0)"
[ -z "$count" ] && count=0
ts="$(date +%s)"
iso="$(date '+%Y-%m-%d %H:%M:%S')"
# Curfew state for the companion app: 1/0 so it slots into the existing
# numeric JSON path. "curfew" = restrictions active now; "curfew_override"
# = the escape-hatch file is set (curfew suspended).
if curfew_active; then cf=1; else cf=0; fi
if [ -e "$CURFEW_OVERRIDE_FILE" ]; then ov=1; else ov=0; fi
# "curfew_force" = the demo/test force file is set (curfew forced on
# regardless of clock). Lets the companion app show Start/Stop demo.
if [ -e "$CURFEW_FORCE_FILE" ]; then frc=1; else frc=0; fi
local tmp="$STATUS_FILE.tmp"
# Shell-emitted JSON — keep values numeric where possible, strings quoted.
{
printf '{'
printf '"mode":"%s",' "$mode"
printf '"lat":"%s",' "${lat:-}"
printf '"lon":"%s",' "${lon:-}"
printf '"distance_m":%s,' "${dist:-null}"
printf '"threshold_m":%s,' "${thr:-null}"
printf '"radius_m":%s,' "$RADIUS"
printf '"disabled_count":%s,' "$count"
printf '"curfew":%s,' "$cf"
printf '"curfew_override":%s,' "$ov"
printf '"curfew_force":%s,' "$frc"
printf '"last_check_ts":%s,' "$ts"
printf '"last_check_iso":"%s"' "$iso"
printf '}\n'
} >"$tmp" 2>/dev/null || return 0
mv "$tmp" "$STATUS_FILE" 2>/dev/null || true
chmod 644 "$STATUS_FILE" 2>/dev/null || true
}