testsAndMisc/phone_focus_mode
Krzysztof kuhy Rudnicki 98ca3ac56a Whitelist com.kuhy.lyricanki in focus mode
Focus Owner hides every package absent from the allowlist, so a newly
installed lyricanki was installed=true but hidden=true: absent from the
launcher and from `pm list packages`, and `am start` reported the activity
did not exist even though dumpsys listed its MAIN/LAUNCHER filter. Nothing
about the app itself was wrong.

policy.json is regenerated from config.sh by python_pkg.focus_policy, never
hand-edited.

Also drops com.android.launcher3 and com.android.camera2 from
SYSTEM_NEVER_DISABLE. That list is prefix-matched and both are strict
supersets of entries already in it, so coverage is byte-identical; this
keeps config.sh under the 250-line cap without weakening the policy.
com.android.settings stays: its only shorter match is com.android.se, an
accidental substring rather than intent.

Verified on the phone: signer matched so the Device Owner upgraded in
place, dpm still reports DeviceOwner, lyricanki flipped to hidden=false,
and workout_app + the dialer are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAEChhK3sWGxd6ucsUQmcT
2026-08-22 16:26:52 +02:00
..
docs config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
focus_status_app Fail the build on any shellcheck finding; fix the 222 the old gate hid 2026-08-03 19:49:11 +02:00
lib focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
systemd feat(phone-focus): add recovery workflow, automation scripts, and docs 2026-05-01 19:07:27 +02:00
backup_manifest.sh feat(phone-focus): add recovery workflow, automation scripts, and docs 2026-05-01 19:07:27 +02:00
batch3_bloatware_uninstall.sh fix(phone): restore Blackview launcher for recents gesture 2026-05-31 16:27:08 +02:00
config_curfew.sh config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
config_dns.sh config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
config_launcher.sh config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
config_paths.sh config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
config_tether.sh config.sh: split 571 -> 250, with a fingerprint that actually works 2026-08-17 17:17:02 +02:00
config.sh Whitelist com.kuhy.lyricanki in focus mode 2026-08-22 16:26:52 +02:00
ctl_curfew.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_daemon.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_dns.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_hosts.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_launcher.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_tether.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_usage.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
ctl_workout.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
curfew_enforcer.sh curfew_enforcer: split 367 -> 244 + 156, net layer at 100% 2026-08-17 16:18:05 +02:00
curfew_net.sh curfew_enforcer: split 367 -> 244 + 156, net layer at 100% 2026-08-17 16:18:05 +02:00
daemon_apps.sh focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests 2026-08-17 16:54:04 +02:00
daemon_location.sh focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests 2026-08-17 16:54:04 +02:00
daemon_state.sh focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests 2026-08-17 16:54:04 +02:00
deploy_actions.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy_daemons.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy_gps.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy_install.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy_magisk.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy_phases.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
deploy.sh deploy: split 875 -> 241 + 6, finishing phone_focus_mode 2026-08-17 17:53:52 +02:00
distraction_purge.sh Block YouTube on the unrooted Pixel, and trust more than one phone 2026-08-10 19:16:01 +02:00
dns_enforcer.sh dns_enforcer: split 325 -> 171 + 170, and give it a working test 2026-08-17 15:10:10 +02:00
dns_iptables.sh dns_enforcer: split 325 -> 171 + 170, and give it a working test 2026-08-17 15:10:10 +02:00
enable_persistent_adb.sh phone_focus_mode: reach the phone over adb without touching it 2026-08-03 13:33:45 +02:00
focus_ctl.sh focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer 2026-08-17 17:46:04 +02:00
focus_daemon.sh focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests 2026-08-17 16:54:04 +02:00
hosts_enforcer.sh hosts_enforcer: split 421 -> 207 + 166 + 91, both libs at 100% 2026-08-17 16:30:56 +02:00
hosts_magisk.sh hosts_enforcer: split 421 -> 207 + 166 + 91, both libs at 100% 2026-08-17 16:30:56 +02:00
hosts_mount.sh hosts_enforcer: split 421 -> 207 + 166 + 91, both libs at 100% 2026-08-17 16:30:56 +02:00
launcher_enforcer.sh phone_focus_mode: add persistent home-mode status notification 2026-04-20 15:33:46 +02:00
magisk_service.sh phone_focus_mode: block hotspot/tethering to close the second-phone bypass 2026-07-13 10:48:59 +02:00
phone_backup.sh phone_backup: split 333 -> 229 + 114, with the library at 100% 2026-08-17 16:02:25 +02:00
README.md Bring every markdown file under the cap 2026-08-16 19:25:59 +02:00
restore_apps.sh Make the enforcer real: block YouTube, and refuse to be uninstalled 2026-08-10 21:12:53 +02:00
run_phone.sh Fix the 11 shellcheck findings that kept CI red since b83ce5d 2026-08-04 20:55:02 +02:00
strip_workout_hosts.py Make focus policy and blocklist usable without root; fix workout CDN unblock 2026-08-05 09:15:02 +02:00
tether_enforcer.sh tether_enforcer: split 267 -> 182 + 103, and grow both deploy lists 2026-08-17 14:38:53 +02:00
tether_iptables.sh tether_enforcer: split 267 -> 182 + 103, and grow both deploy lists 2026-08-17 14:38:53 +02:00
workout_detector.sh Apply focus-mode, screen-locker, and steam backlog updates 2026-05-03 22:41:53 +02:00

Phone Focus Mode

Location-based app restriction for a rooted Android phone using wireless ADB.

When within ~500m of home: only whitelisted productive apps remain usable. When outside that radius: all apps work normally.

Requirements

  • Rooted phone with Magisk installed
  • Wireless ADB enabled (Settings → Developer options → Wireless debugging)
  • adb installed on your PC (sudo apt install adb on Debian/Ubuntu)
  • GPS/Location enabled on the phone

Setup (first time)

1. Find your home coordinates

Open Google Maps, right-click your apartment → copy the coordinates shown.

2. Edit config_secrets.sh

HOME_LAT="-48.876667"   # your latitude
HOME_LON="-123.393333"  # your longitude

3. (Optional) Adjust the whitelist in config.sh

To find the exact package name of any app:

./deploy.sh <phone_ip> --find-pkg stronglift
./deploy.sh <phone_ip> --find-pkg anki
./deploy.sh <phone_ip> --find-pkg pomodoro

Then add the correct package name to WHITELIST in config.sh.

4. Deploy

chmod +x deploy.sh
./deploy.sh 192.168.1.42        # replace with your phone's IP

This:

  1. Pushes all scripts to /data/local/tmp/focus_mode/ on the device
  2. Installs a Magisk service.d script so the daemon auto-starts on boot
  3. Starts the daemon immediately

Usage

./deploy.sh <ip> --status    # Current mode, location, distance from home
./deploy.sh <ip> --log       # View recent daemon log
./deploy.sh <ip> --list      # List all apps + whitelist status
./deploy.sh <ip> --enable    # Force focus mode ON (for testing)
./deploy.sh <ip> --disable   # Force focus mode OFF
./deploy.sh <ip> --stop      # Stop daemon entirely (restores all apps)
./deploy.sh <ip> --start     # Start daemon
./deploy.sh <ip> --restart   # Restart daemon (picks up config changes)
./deploy.sh <ip> --pull-log  # Download log file to your PC

How it works

Every 60 seconds:
  get_location()  ─── dumpsys location ──► lat,lon
        │
        ▼
  calc_distance()  ─── Haversine formula ──► meters
        │
        ├── within radius?  ──► enable_focus_mode()
        │                           pm disable-user all non-whitelisted apps
        │                           record which apps were disabled
        │
        └── outside radius? ──► disable_focus_mode()
                                    pm enable each app in the disabled list

Hysteresis: 50m buffer prevents rapid toggling at the boundary. You must travel radius - 50m inward to trigger lock, and radius + 50m outward to unlock.

Fail-safe: If location is unavailable for 5 consecutive checks (~5 minutes), focus mode is automatically disabled so you can't be locked out.

State persistence: The daemon records exactly which apps it disabled (in /data/local/tmp/focus_mode/disabled_by_focus.txt), so it never accidentally re-enables apps that were already disabled by the user before focus mode ran.

On-device control (without PC)

From a root terminal app (e.g. Termux + tsu):

su --mount-master -c 'sh /data/local/tmp/focus_mode/focus_ctl.sh status'
su --mount-master -c 'sh /data/local/tmp/focus_mode/focus_ctl.sh disable'

Why --mount-master: MagiskSU puts each su -c session in an isolated mount namespace by default, so bind mounts made by the hosts enforcer would be invisible (and /data/adb/focus_mode/* checks would fail due to SELinux interactions). --mount-master joins the global namespace where the daemons (started from Magisk service.d at boot) actually live. The boot autostart script doesn't need this flag because post-fs-data already runs there.

File layout

File Purpose
config.sh Coordinates, radius, whitelist, constants
focus_daemon.sh Main daemon — runs on device, loops every 60s
focus_ctl.sh Control utility — runs on device
curfew_enforcer.sh Night-curfew enforcer — grayscale + DND + optional net
hosts_enforcer.sh Bind-mounts hosts.canonical over /system/etc/hosts
magisk_service.sh Magisk boot hook → auto-starts both daemons
deploy.sh PC-side ADB deployment and control script

Hosts hardening

A second daemon, hosts_enforcer.sh, locks the phone's /system/etc/hosts to the same blocklist installed by linux_configuration/hosts/install.sh on the PC. Three layers:

  1. Canonical copy at /data/adb/focus_mode/hosts.canonical is chattr +i.
  2. It is bind-mounted read-only over /system/etc/hosts at boot.
  3. A watchdog verifies a sha256 every 15 seconds and restores on mismatch.

This blocks the common echo > /etc/hosts one-liner from a terminal app. It is NOT a guarantee against a determined root user on the device itself — a real "impossible without USB" gate would require removing su access, which would break the rest of this system. The watchdog at least ensures tampering is logged and reverted within ~15s.

Status and logs:

./deploy.sh <ip> --hosts-status
./deploy.sh <ip> --hosts-log

Periodic rescan / Play Store

The focus daemon now re-scans every tick (not just on first entry). If you re-enable an app via Play Store or pm enable, it gets re-disabled within CHECK_INTERVAL_FOCUS seconds. com.android.vending (Play Store), com.*.packageinstaller, and popular terminal apps are also uninstalled --user 0 in focus mode to close the usual bypass paths. Google Play Services (com.google.android.gms) is left alone so banking apps work.

More detail

Split out to stay under the 250-line cap.