testsAndMisc/phone_focus_mode/launcher_enforcer.sh

180 lines
5.7 KiB
Bash
Raw Normal View History

#!/system/bin/sh
# shellcheck shell=ash
# ============================================================
# Launcher enforcer for rooted Android.
#
# Goal:
# 1. Keep $LAUNCHER_PACKAGE installed at all times. If it is
# uninstalled (with or without `-k`), reinstall from
# $LAUNCHER_APK snapshot within $LAUNCHER_CHECK_INTERVAL.
# 2. Keep it pinned as the default HOME activity. If the user
# switches launchers via Settings or the picker, restore it.
# 3. Prevent competing launchers ($LAUNCHER_COMPETITORS) from
# being offered by `pm disable-user`-ing them.
#
# Known limitation: a user with root in a terminal can still
# stop this daemon and change HOME. That's the same threat model
# as hosts_enforcer.sh - this closes the "tap to uninstall / pick
# a new launcher" gap and leaves a tamper trail in the log.
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
. "$SCRIPT_DIR/config.sh"
PIDFILE="$STATE_DIR/launcher_enforcer.pid"
# Tracks competitors we disabled ourselves, so `launcher-stop` can undo them.
DISABLED_COMPETITORS_FILE="$STATE_DIR/disabled_competitors.txt"
mkdir -p "$STATE_DIR" "$(dirname "$LAUNCHER_APK")"
touch "$LAUNCHER_LOG" "$DISABLED_COMPETITORS_FILE"
chmod 666 "$LAUNCHER_LOG" "$DISABLED_COMPETITORS_FILE" 2>/dev/null || true
log() {
local ts
ts="$(date '+%Y-%m-%d %H:%M:%S')"
echo "[$ts] $1" >> "$LAUNCHER_LOG"
}
rotate_log() {
local lines
lines="$(wc -l < "$LAUNCHER_LOG" 2>/dev/null || echo 0)"
if [ "$lines" -gt 500 ]; then
local tmp="$LAUNCHER_LOG.tmp"
tail -n 500 "$LAUNCHER_LOG" > "$tmp"
mv "$tmp" "$LAUNCHER_LOG"
fi
}
acquire_lock() {
if [ -f "$PIDFILE" ]; then
local old_pid
old_pid="$(cat "$PIDFILE")"
if kill -0 "$old_pid" 2>/dev/null; then
local cmdline
cmdline="$(tr '\0' ' ' < "/proc/$old_pid/cmdline" 2>/dev/null)"
if echo "$cmdline" | grep -q "launcher_enforcer"; then
echo "launcher_enforcer already running (PID $old_pid)"
exit 0
fi
fi
rm -f "$PIDFILE"
fi
echo $$ > "$PIDFILE"
}
# ---- Package state helpers ----
pkg_installed() {
# `pm path` exits 0 and prints package: line when present.
pm path "$1" >/dev/null 2>&1
}
pkg_enabled() {
# `pm list packages -e` lists enabled packages. Use exact match.
pm list packages -e 2>/dev/null | grep -qxE "package:$1"
}
current_home_component() {
# Returns "pkg/Activity" of the current default HOME, or "" if ambiguous.
cmd package resolve-activity --brief -c android.intent.category.HOME \
-a android.intent.action.MAIN 2>/dev/null \
| awk 'NR==2{print}'
}
# ---- Enforcement actions ----
reinstall_launcher() {
if [ ! -f "$LAUNCHER_APK" ]; then
log "ERROR: cannot reinstall - APK snapshot missing at $LAUNCHER_APK"
return 1
fi
local expected actual
expected="$(cat "$LAUNCHER_SHA_FILE" 2>/dev/null)"
actual="$(sha256sum "$LAUNCHER_APK" 2>/dev/null | awk '{print $1}')"
if [ -n "$expected" ] && [ "$expected" != "$actual" ]; then
log "ERROR: APK snapshot hash mismatch (expected $expected, got $actual) - refusing to install"
return 1
fi
log "REINSTALL: $LAUNCHER_PACKAGE missing - installing from snapshot"
# -g grants all runtime permissions so the launcher starts clean
# without blocking on a permission dialog that itself may need the
# launcher to be usable.
if pm install -r -g "$LAUNCHER_APK" >/dev/null 2>&1; then
log "REINSTALL: $LAUNCHER_PACKAGE installed successfully"
return 0
fi
# Fallback: `pm install` without -g on older Androids
if pm install -r "$LAUNCHER_APK" >/dev/null 2>&1; then
log "REINSTALL: $LAUNCHER_PACKAGE installed (without -g)"
return 0
fi
log "ERROR: pm install failed"
return 1
}
ensure_home_pinned() {
local desired actual
desired="$(cat "$LAUNCHER_ACTIVITY_FILE" 2>/dev/null)"
if [ -z "$desired" ]; then
return 0 # not armed yet; deploy.sh --snapshot-launcher writes this
fi
actual="$(current_home_component)"
if [ "$actual" = "$desired" ]; then
return 0
fi
log "HOME: default is '$actual' not '$desired' - restoring"
cmd package set-home-activity "$desired" >/dev/null 2>&1 || \
log "ERROR: set-home-activity failed for $desired"
}
disable_competitors() {
# Disable every competitor that is still enabled. Remember what we
# disabled so `launcher-stop` can re-enable.
echo "$LAUNCHER_COMPETITORS" | while read -r pkg; do
[ -z "$pkg" ] && continue
[ "${pkg#\#}" != "$pkg" ] && continue # skip comments
if pkg_installed "$pkg" && pkg_enabled "$pkg"; then
if pm disable-user --user 0 "$pkg" >/dev/null 2>&1; then
log "Disabled competing launcher: $pkg"
grep -qxE "$pkg" "$DISABLED_COMPETITORS_FILE" 2>/dev/null \
|| echo "$pkg" >> "$DISABLED_COMPETITORS_FILE"
fi
fi
done
}
# ---- Main loop ----
cleanup() {
log "launcher_enforcer shutting down"
rm -f "$PIDFILE"
exit 0
}
trap cleanup INT TERM
main() {
acquire_lock
log "launcher_enforcer started (PID=$$)"
# Initial arm-up
if ! pkg_installed "$LAUNCHER_PACKAGE"; then
reinstall_launcher || true
fi
ensure_home_pinned
disable_competitors
while true; do
if ! pkg_installed "$LAUNCHER_PACKAGE"; then
reinstall_launcher || true
fi
ensure_home_pinned
disable_competitors
rotate_log
sleep "$LAUNCHER_CHECK_INTERVAL"
done
}
main "$@"