Add android/, a normal Gradle/Kotlin project with its own wrapper. It blocks by interruption: an Accessibility Service watches the foreground package and a full-screen overlay covers any listed app while the device is locked (FR-16). No Device Owner, no factory reset, no managed profile. The client subscribes to the retained restrict/<device>/config topic on the 8883 TLS plane, keeps the last config on disk so a broker-less device keeps its rules (FR-14), and reports tampering on its own status topic (FR-17): a disabled service, a revoked overlay permission, or a force-stop seen on the next start. The Control Service now parses the tamper field and raises an Apprise alert, so it reaches Sam. The block path and the tamper alert are proven on the headless pixel35 emulator on .27; see android/README.md and android/tools/emu-proof.sh. Co-Authored-By: Paperclip <noreply@paperclip.ing>
64 lines
2.8 KiB
Markdown
64 lines
2.8 KiB
Markdown
# Kids' Android client — Phase 5.1
|
|
|
|
The core no-wipe block path of the kids' phone app (AGENTS.md §11 Phase 5,
|
|
FR-16 + FR-17). A normal Gradle project with its own wrapper. The toolchain is
|
|
**not** on `.13`; it builds on `.27`:
|
|
|
|
```bash
|
|
ssh 192.168.20.27 '~/android/build.sh internet_use_control'
|
|
```
|
|
|
|
That pulls this repo from Gitea and runs `./gradlew assembleDebug`. It prints
|
|
`APK=<path>`; the debug APK is `android/app/build/outputs/apk/debug/app-debug.apk`.
|
|
|
|
## What it does
|
|
|
|
| Piece | File | Job |
|
|
|---|---|---|
|
|
| Accessibility Service | `BlockAccessibilityService.kt` | Watches the foreground package (FR-16). |
|
|
| Block overlay | `OverlayController.kt` | A full-screen `TYPE_APPLICATION_OVERLAY` window over a blocked app. |
|
|
| Block rule | `BlockEngine.kt` | Blocked = locked **and** on the blocklist **and** not allowlisted (FR-4). |
|
|
| Config transport | `MqttController.kt` | Subscribes, retained, to `restrict/<device>/config` on the 8883 TLS plane with the device's own credentials; publishes presence and tamper on `restrict/<device>/status`. |
|
|
| Last state | `AppPrefs.kt` | The last config is kept on disk, so a broker-less device keeps its rules (FR-14). |
|
|
| Tamper alert | `TamperReporter.kt` | Reports a disabled service, a revoked overlay permission, or a force-stop (FR-17). |
|
|
| Status screen | `MainActivity.kt` | What is blocked, until when, and the health of the enforcement. |
|
|
|
|
**No Device Owner. No factory reset. No managed profile.** The child can disable
|
|
the client; the enforcement is the tamper alert, not the overlay.
|
|
|
|
## How the tamper alert reaches Sam
|
|
|
|
The device's broker ACL only lets it write its own `status` topic, so the tamper
|
|
report rides there:
|
|
|
|
```json
|
|
{"online":true,"tamper":{"kind":"accessibility_disabled","detail":"the service was destroyed","at":"2026-10-13T08:00:00Z"}}
|
|
```
|
|
|
|
`internal/mqtt` parses it, `internal/alert` records a `tamper` alert and delivers
|
|
it through Apprise, and `cmd/control` wires the two together.
|
|
|
|
## Test on the headless emulator
|
|
|
|
```bash
|
|
ssh 192.168.20.27 '~/android/emu.sh start'
|
|
ssh 192.168.20.27 '~/android/emu.sh wait'
|
|
ssh 192.168.20.27 '~/android/emu.sh install /tmp/webrestrict-pre/android/app/build/outputs/apk/debug/app-debug.apk'
|
|
|
|
PKG=au.audasmedia.webrestrict.kids
|
|
adb shell appops set $PKG SYSTEM_ALERT_WINDOW allow
|
|
adb shell settings put secure enabled_accessibility_services $PKG/$PKG.BlockAccessibilityService
|
|
adb shell settings put secure accessibility_enabled 1
|
|
|
|
# Open a blocked app (the local default blocks Settings) and screenshot.
|
|
adb shell am start -a android.settings.SETTINGS
|
|
sleep 3
|
|
ssh 192.168.20.27 '~/android/emu.sh screenshot /tmp/shot.png'
|
|
|
|
# Show the tamper alert: disable the service.
|
|
adb shell settings put secure accessibility_enabled 0
|
|
adb logcat -d -s WebRestrictTamper
|
|
|
|
ssh 192.168.20.27 '~/android/emu.sh stop'
|
|
```
|