#!/system/bin/sh # shellcheck shell=ash # ============================================================ # Focus Mode Daemon # Runs on rooted Android device. Periodically checks GPS # location and restricts non-whitelisted apps when near home. # ============================================================ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" . "$SCRIPT_DIR/config.sh" PIDFILE="$STATE_DIR/daemon.pid" # ---- PID lock: exit if already running ---- acquire_lock() { mkdir -p "$STATE_DIR" if [ -f "$PIDFILE" ]; then local old_pid old_pid="$(cat "$PIDFILE")" if kill -0 "$old_pid" 2>/dev/null; then echo "Daemon already running (PID $old_pid), exiting." exit 0 fi # Stale pidfile rm -f "$PIDFILE" fi echo $$ > "$PIDFILE" } # ---- Logging ---- log() { local ts ts="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$ts] $1" >> "$LOG_FILE" } # ---- Build helper files for fast package checks ---- build_whitelist_file() { echo "$WHITELIST" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' \ | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' > "$STATE_DIR/whitelist.txt" } build_sysprotect_file() { echo "$SYSTEM_NEVER_DISABLE" | grep -v '^[[:space:]]*$' \ | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' > "$STATE_DIR/sysprotect.txt" } 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" } # ---- Initialization ---- init() { mkdir -p "$STATE_DIR" touch "$LOG_FILE" touch "$DISABLED_APPS_FILE" if [ "$HOME_LAT" = "0.000000" ] && [ "$HOME_LON" = "0.000000" ]; then log "ERROR: Home coordinates not set! Edit config.sh first." exit 1 fi build_whitelist_file build_sysprotect_file if [ -f "$MODE_FILE" ]; then CURRENT_MODE="$(cat "$MODE_FILE")" else CURRENT_MODE="normal" fi if [ "$CURRENT_MODE" = "focus" ]; then reconcile_disabled_apps fi log "Focus mode daemon started (PID=$$, mode=$CURRENT_MODE, home=$HOME_LAT,$HOME_LON, radius=${RADIUS}m)" log "Intervals: focus=${CHECK_INTERVAL_FOCUS}s normal=${CHECK_INTERVAL_NORMAL}s" } # ---- Location ---- get_location() { dumpsys location 2>/dev/null \ | grep -oE '[-]?[0-9]{1,3}\.[0-9]{4,},[-]?[0-9]{1,3}\.[0-9]{4,}' \ | head -1 } # ---- Distance Calculation (Haversine via awk) ---- calc_distance() { echo "$1 $2 $3 $4" | awk '{ PI = 3.14159265358979323846 R = 6371000.0 lat1 = $1 * PI / 180.0 lon1 = $2 * PI / 180.0 lat2 = $3 * PI / 180.0 lon2 = $4 * PI / 180.0 dlat = lat2 - lat1 dlon = lon2 - lon1 sdlat = sin(dlat / 2.0) sdlon = sin(dlon / 2.0) a = sdlat * sdlat + cos(lat1) * cos(lat2) * sdlon * sdlon c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a)) printf "%d\n", R * c }' } # ---- Check if package is allowed (whitelist or system-protected) ---- is_allowed() { local pkg="$1" # Exact match against whitelist file if grep -qxF "$pkg" "$STATE_DIR/whitelist.txt" 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" return 1 } # ---- Focus Mode Control ---- enable_focus_mode() { if [ "$CURRENT_MODE" = "focus" ]; then reconcile_disabled_apps return fi log "ENABLING focus mode - restricting non-whitelisted apps" : > "$DISABLED_APPS_FILE" local tmp_pkgs="$STATE_DIR/pkg_list.txt" pm list packages -3 2>/dev/null | sed 's/^package://' > "$tmp_pkgs" 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 echo "$pkg" >> "$DISABLED_APPS_FILE" fi done < "$tmp_pkgs" rm -f "$tmp_pkgs" # Also remove explicitly blocked system apps (e.g. browsers) # Uses pm uninstall --user 0 so they vanish from Settings entirely local blocked_sys="$STATE_DIR/blocked_sys.txt" local uninstalled_sys="$STATE_DIR/uninstalled_sys.txt" echo "$BLOCKED_SYSTEM_APPS" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' \ | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' > "$blocked_sys" : > "$uninstalled_sys" while IFS= read -r pkg; do [ -z "$pkg" ] && continue # Try uninstall; even if already uninstalled, record it for re-install later pm uninstall -k --user 0 "$pkg" >/dev/null 2>&1 echo "$pkg" >> "$uninstalled_sys" echo "$pkg" >> "$DISABLED_APPS_FILE" done < "$blocked_sys" rm -f "$blocked_sys" local count count=$(wc -l < "$DISABLED_APPS_FILE" 2>/dev/null || echo 0) CURRENT_MODE="focus" echo "focus" > "$MODE_FILE" log "Focus mode enabled - disabled $count apps" 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-install system apps that were uninstalled for user if [ -f "$STATE_DIR/uninstalled_sys.txt" ] && [ -s "$STATE_DIR/uninstalled_sys.txt" ]; then while IFS= read -r pkg; do [ -z "$pkg" ] && continue pm install-existing --user 0 "$pkg" >/dev/null 2>&1 done < "$STATE_DIR/uninstalled_sys.txt" : > "$STATE_DIR/uninstalled_sys.txt" fi # Re-enable all disabled apps 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" } # ---- Signal handlers ---- cleanup() { log "Daemon shutting down - re-enabling all apps" disable_focus_mode rm -f "$PIDFILE" exit 0 } # HUP is intentionally NOT trapped so the daemon survives ADB disconnects. # Only SIGTERM/SIGINT trigger a clean shutdown. trap cleanup INT TERM # ---- Main Loop ---- main() { acquire_lock init while true; do location="$(get_location)" if [ -n "$location" ]; then lat="$(echo "$location" | cut -d',' -f1)" lon="$(echo "$location" | cut -d',' -f2)" distance="$(calc_distance "$lat" "$lon" "$HOME_LAT" "$HOME_LON")" if [ "$CURRENT_MODE" = "focus" ]; then threshold=$((RADIUS + HYSTERESIS)) else threshold=$((RADIUS - HYSTERESIS)) fi if [ "$distance" -le "$threshold" ] 2>/dev/null; then enable_focus_mode else disable_focus_mode fi log "Location: $lat,$lon | Distance: ${distance}m | Threshold: ${threshold}m | Mode: $CURRENT_MODE" else log "Location unavailable - defaulting to focus mode (restrictions ON)" enable_focus_mode fi # Dynamic interval: shorter at home (can charge), longer away (save battery) if [ "$CURRENT_MODE" = "focus" ]; then sleep "$CHECK_INTERVAL_FOCUS" else sleep "$CHECK_INTERVAL_NORMAL" fi done } main "$@"