testsAndMisc/phone_focus_mode/daemon_apps.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

166 lines
6.0 KiB
Bash
Executable File

#!/system/bin/sh
# shellcheck shell=ash
# daemon_apps.sh — the app layer of focus_daemon.sh: deciding whether a
# package is allowed, disabling everything that is not while focus mode is on,
# and re-enabling it afterwards.
#
# A flat sibling, not lib/: deploy.sh pushes phone scripts into one directory.
#
# Sourced by focus_daemon.sh, which owns log(), $CURRENT_MODE (written here
# and in init, read by main, so it stays with them) and the config.sh globals
# these read ($DISABLED_APPS_FILE, $MODE_FILE, $STATE_DIR).
#
# NOTE for anyone editing the `while IFS= read -r pkg; do ... done <file`
# loops below: they must stay redirect-driven. Every body calls pm, which
# inherits and drains the loop's stdin if the file is piped in instead — the
# measured symptom elsewhere in this repo was processing 1 package of 54.
reconcile_disabled_apps() {
[ -f "$DISABLED_APPS_FILE" ] || return
local tmp_disabled="$STATE_DIR/disabled_by_focus.tmp"
: >"$tmp_disabled"
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
if is_allowed "$pkg"; then
pm install-existing --user 0 "$pkg" >/dev/null 2>&1 || true
pm enable "$pkg" >/dev/null 2>&1 || true
log "Re-enabled allowed app during state reconciliation: $pkg"
continue
fi
echo "$pkg" >>"$tmp_disabled"
done <"$DISABLED_APPS_FILE"
mv "$tmp_disabled" "$DISABLED_APPS_FILE"
}
# ---- Check if package is allowed (whitelist or system-protected) ----
is_allowed() {
local pkg="$1"
# During the night curfew, swap the permissive day list for the strict
# night list. The sysprotect + default-handler guards below still apply on
# top of whichever list is active.
local list="$STATE_DIR/whitelist.txt"
if curfew_active; then
list="$STATE_DIR/night_whitelist.txt"
fi
# Exact match against the active whitelist file
if grep -qxF "$pkg" "$list" 2>/dev/null; then
return 0
fi
# Prefix match against system-protect file
while IFS= read -r prefix; do
[ -z "$prefix" ] && continue
case "$pkg" in
"$prefix"*) return 0 ;;
esac
done <"$STATE_DIR/sysprotect.txt"
# Hard-stop guard: refuse to disable any package that is the current
# default handler for a critical role (Dialer / SMS / Home / Contacts).
# Without this, a misconfigured WHITELIST can disable the default Phone
# app and Android falls back to com.android.settings/.FallbackHome -
# the persistent "Phone is starting..." screen with broken SystemUI
# gestures (no swipe-up recents). Recovering requires `pm enable` over
# ADB. Treat the guard as last-resort safety net independent of WHITELIST
# contents so a future config edit can never wipe these out.
is_default_handler "$pkg" && return 0
# The default browser is guarded only OUTSIDE curfew. At night the whole
# point is to disable browsers, so this guard must not re-allow it.
if ! curfew_active &&
grep -qxF "$pkg" "$STATE_DIR/default_browser.txt" 2>/dev/null; then
return 0
fi
return 1
}
enable_focus_mode() {
local first_entry=0
if [ "$CURRENT_MODE" != "focus" ]; then
first_entry=1
log "ENABLING focus mode - restricting non-whitelisted apps"
: >"$DISABLED_APPS_FILE"
fi
# Refresh default-handler list every tick. The user may switch dialer /
# SMS / launcher between sweeps; the guard in is_allowed() consults this
# list so a newly-promoted handler is never disabled.
refresh_default_handlers
# Blocked system app list is static; built once in init() (see
# build_blocked_sys_file), not rebuilt on every sweep.
local blocked_sys="$STATE_DIR/blocked_sys.txt"
# Periodic rescan catches third-party apps the user re-enabled (e.g. via
# Play Store or `pm enable` in a terminal) since the last tick.
# -e = enabled only, so we skip apps that are already disabled.
local tmp_pkgs="$STATE_DIR/pkg_list.txt"
pm list packages -3 -e 2>/dev/null | sed 's/^package://' >"$tmp_pkgs"
local newly_disabled=0
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
is_allowed "$pkg" && continue
if pm disable-user --user 0 "$pkg" >/dev/null 2>&1; then
grep -qxF "$pkg" "$DISABLED_APPS_FILE" 2>/dev/null ||
echo "$pkg" >>"$DISABLED_APPS_FILE"
newly_disabled=$((newly_disabled + 1))
fi
done <"$tmp_pkgs"
rm -f "$tmp_pkgs"
# Disable-for-user-0 any blocked system apps (Play Store, browsers,
# package installer UI, terminal apps).
# IMPORTANT: We intentionally use pm disable-user (NOT pm uninstall) here.
# pm uninstall -k --user 0 removes the package from Android's user-0
# package registry. If the daemon is killed with SIGKILL during a reboot
# (bypassing the cleanup trap), those packages stay uninstalled across the
# reboot. Android's bootloop-protection (MTK and others) then detects
# missing critical system packages and triggers recovery / factory wipe.
# pm disable-user leaves the package registered but inactive, so the
# PackageManager scan at next boot succeeds and no wipe occurs.
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
if pm disable-user --user 0 "$pkg" >/dev/null 2>&1; then
grep -qxF "$pkg" "$DISABLED_APPS_FILE" 2>/dev/null ||
echo "$pkg" >>"$DISABLED_APPS_FILE"
newly_disabled=$((newly_disabled + 1))
fi
done <"$blocked_sys"
CURRENT_MODE="focus"
echo "focus" >"$MODE_FILE"
if [ "$first_entry" -eq 1 ]; then
local count
count=$(wc -l <"$DISABLED_APPS_FILE" 2>/dev/null || echo 0)
log "Focus mode enabled - disabled $count apps"
elif [ "$newly_disabled" -gt 0 ]; then
log "Focus mode re-sweep: re-disabled $newly_disabled apps (re-enabled by user?)"
fi
reconcile_disabled_apps
}
disable_focus_mode() {
[ "$CURRENT_MODE" = "normal" ] && return
log "DISABLING focus mode - re-enabling apps"
local count=0
if [ -f "$DISABLED_APPS_FILE" ] && [ -s "$DISABLED_APPS_FILE" ]; then
# Re-enable all disabled apps (both 3rd-party and system apps).
# Both paths now use pm disable-user, so pm enable is the only
# restore command needed.
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
pm enable "$pkg" >/dev/null 2>&1 && count=$((count + 1))
done <"$DISABLED_APPS_FILE"
: >"$DISABLED_APPS_FILE"
fi
CURRENT_MODE="normal"
echo "normal" >"$MODE_FILE"
log "Focus mode disabled - re-enabled $count apps"
}