mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 12:03:11 +02:00
- New companion Android app (com.kuhy.focusstatus) under phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less command-line build pipeline (build.sh). Shows an ongoing notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with distance, GPS, disabled-app count, last check, daemon checkmarks, and a 'Re-check now' action button. - focus_daemon.sh: write_status_snapshot() + sleep_with_recheck() for JSON status + early-wake on trigger file. init() chmods STATE_DIR 777 so the app can drop the trigger file. - config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded with com.kuhy.focusstatus and 11 more user-requested apps (podcini X, mpv, bible/openbible, pkp/portalpasazera, orange, runnerup, splitbills/splitwise, xiaomi smarthome). - focus_ctl.sh: new 'recheck' + 'notif-status' subcommands. - deploy.sh: new step [7/7] builds APK, installs, grants POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches foreground service. - .gitignore: exclude focus_status_app/build symlink + debug.keystore. End-to-end verified on device: notification live with real values; Re-check button triggers a daemon location check within ~1s.
180 lines
5.7 KiB
Bash
Executable File
180 lines
5.7 KiB
Bash
Executable File
#!/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 "$@"
|