2026-05-03 22:30:48 +02:00
|
|
|
# Phone Focus Mode
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
Location-based app restriction for a rooted Android phone using wireless ADB.
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
When within ~500m of home: only whitelisted productive apps remain usable.
|
|
|
|
|
When outside that radius: all apps work normally.
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## Requirements
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
- 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
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## Setup (first time)
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
### 1. Find your home coordinates
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
Open Google Maps, right-click your apartment → copy the coordinates shown.
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
### 2. Edit `config_secrets.sh`
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
```sh
|
|
|
|
|
HOME_LAT="-48.876667" # your latitude
|
|
|
|
|
HOME_LON="-123.393333" # your longitude
|
|
|
|
|
```
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
### 3. (Optional) Adjust the whitelist in `config.sh`
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
To find the exact package name of any app:
|
2026-02-22 16:57:36 +01:00
|
|
|
|
|
|
|
|
```bash
|
2026-05-03 22:30:48 +02:00
|
|
|
./deploy.sh <phone_ip> --find-pkg stronglift
|
|
|
|
|
./deploy.sh <phone_ip> --find-pkg anki
|
|
|
|
|
./deploy.sh <phone_ip> --find-pkg pomodoro
|
2026-02-22 16:57:36 +01:00
|
|
|
```
|
|
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
Then add the correct package name to `WHITELIST` in `config.sh`.
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
### 4. Deploy
|
2026-02-22 16:57:36 +01:00
|
|
|
|
|
|
|
|
```bash
|
2026-05-03 22:30:48 +02:00
|
|
|
chmod +x deploy.sh
|
|
|
|
|
./deploy.sh 192.168.1.42 # replace with your phone's IP
|
2026-02-22 16:57:36 +01:00
|
|
|
```
|
|
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
This:
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
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
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## Usage
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-01 19:07:27 +02:00
|
|
|
```bash
|
2026-05-03 22:30:48 +02:00
|
|
|
./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
|
2026-02-22 16:57:36 +01:00
|
|
|
```
|
|
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## How it works
|
phone_focus_mode: add persistent home-mode status notification
- 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.
2026-04-20 15:33:32 +02:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
```
|
|
|
|
|
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
|
2026-05-01 19:07:27 +02:00
|
|
|
```
|
phone_focus_mode: add persistent home-mode status notification
- 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.
2026-04-20 15:33:32 +02:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
**Hysteresis:** 50m buffer prevents rapid toggling at the boundary. You must travel
|
|
|
|
|
`radius - 50m` inward to trigger lock, and `radius + 50m` outward to unlock.
|
phone_focus_mode: add persistent home-mode status notification
- 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.
2026-04-20 15:33:32 +02:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
**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):
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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'
|
phone_focus_mode: add persistent home-mode status notification
- 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.
2026-04-20 15:33:32 +02:00
|
|
|
```
|
|
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
**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.
|
phone_focus_mode: add persistent home-mode status notification
- 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.
2026-04-20 15:33:32 +02:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## File layout
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
| 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 |
|
|
|
|
|
| `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 |
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## Hosts hardening
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
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:
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
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.
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
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:
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-01 19:07:27 +02:00
|
|
|
```bash
|
2026-05-03 22:30:48 +02:00
|
|
|
./deploy.sh <ip> --hosts-status
|
|
|
|
|
./deploy.sh <ip> --hosts-log
|
2026-05-01 19:07:27 +02:00
|
|
|
```
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
## 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.
|
|
|
|
|
|
|
|
|
|
## Updating
|
|
|
|
|
|
|
|
|
|
After editing `config.sh` (e.g. changing whitelist):
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./deploy.sh <ip> # re-pushes all files
|
|
|
|
|
# or just the config:
|
|
|
|
|
adb push config.sh /data/local/tmp/focus_mode/config.sh
|
|
|
|
|
./deploy.sh <ip> --restart
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
**Location always unavailable:**
|
|
|
|
|
|
|
|
|
|
- Enable GPS and network location on the phone
|
|
|
|
|
- Open Google Maps once to warm up the GPS provider
|
|
|
|
|
- The daemon logs every attempt; check with `--log`
|
|
|
|
|
|
|
|
|
|
**App won't disable:**
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
- Some system apps can't be disabled even as root; they're silently skipped
|
|
|
|
|
- Check log for "Failed to disable" warnings
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
**Daemon not starting on boot:**
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
- Verify Magisk is installed and `service.d` is supported
|
|
|
|
|
- Check `/data/adb/service.d/99-focus-mode.sh` exists and is executable
|
|
|
|
|
- Some Magisk versions use `/data/adb/post-fs-data.d/` instead; try both
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
**Wrong package name in whitelist:**
|
2026-02-22 16:57:36 +01:00
|
|
|
|
2026-05-03 22:30:48 +02:00
|
|
|
- Use `./deploy.sh <ip> --find-pkg <keyword>` to find the exact package name
|
|
|
|
|
- Package names are case-sensitive
|