checkpoint: Lost My Phone app - SMS triggers, DND override, mediaPlayback FGS

This commit is contained in:
avi 2026-08-06 16:24:28 -05:00
commit 220dd85ed3
16 changed files with 1147 additions and 0 deletions

150
README.md Normal file
View file

@ -0,0 +1,150 @@
# Lost My Phone
An Android app that listens for SMS commands to trigger a loud ring — overriding
"Do Not Disturb" (DND) — so you can find your phone.
- **Package:** `com.example.lostmyphone`
- **No Google Play Services.** Pure Android SDK.
- **Languages:** Kotlin, Material Components (AndroidX).
---
## SMS Trigger Commands
| Command | Behaviour |
|--------------------|----------------------------------------------------------------------------------|
| `RING_NOW` | Plays the default system ringtone at max volume. |
| `PLAY_SONG <name>` | Searches local `MediaStore` for a matching song. Plays it, or falls back to the default ringtone if not found. |
Playback always runs for exactly **30 seconds**, then the previous volume is
restored.
> Send the SMS to your phone from any other phone. The command is matched
> case-insensitively.
---
## How Do Not Disturb Override Works (read this)
Android does **not** allow any app to silently bypass DND. The only supported path
is the **"Do Not Disturb access"** special app setting. **Without it the app will
refuse to play any sound.** This is enforced at two points:
1. **`SmsReceiver`** passes whether access is granted to the service.
2. **`RingService.onStartCommand`** re-verifies
`NotificationManager.isNotificationPolicyAccessGranted` as the **source of
truth**. If it is `false` the service:
- does **not** start playback,
- shows an ongoing "Action Required: DND Access" notification,
- and launches the system DND-access settings screen.
Only after the user flips that toggle will a trigger actually ring.
When access **is** granted, `RingService`:
- saves the current music volume,
- forces `AudioManager.STREAM_MUSIC` to max,
- requests `AUDIOFOCUS_GAIN_TRANSIENT` using `USAGE_NOTIFICATION_RINGTONE` and
sets `AudioManager.mode = MODE_RINGTONE` so the sound cuts through DND,
- plays for 30 s, then restores the volume and abandons focus.
---
## Build & Install
### Prerequisites
- JDK 17
- Android SDK with `compileSdk = 34` (platform 34 + build tools) — see
`app/build.gradle.kts`.
### Steps
```bash
cd /path/to/Lost_My_Phone
# 1) Point Gradle at your Android SDK (if not already in $ANDROID_HOME)
# create a local.properties file:
# echo "sdk.dir=/path/to/Android/Sdk" > local.properties
# 2) Generate the Gradle wrapper (optional if you already have gradle 8.6)
gradle wrapper
# 3) Build the debug APK
./gradlew :app:assembleDebug
# 4) The APK is at app/build/outputs/apk/debug/app-debug.apk
# 5) Install with adb
adb install app/build/outputs/apk/debug/app-debug.apk
```
---
## First-Launch Permission Setup
On install you must grant **four** things, in this order:
### 1. SMS permission (required — SMS receiver will not fire without it)
Launch the app → tap **"Grant SMS & Media Permissions"** → **Allow**.
- On Android 13+ this also asks for `READ_MEDIA_AUDIO` (needed for `PLAY_SONG`).
### 2. Notification permission (Android 13+)
Tap **"Grant Notification Permission"** → **Allow**.
(Needed so the foreground-service notification shows.)
### 3. Do Not Disturb Access (CRITICAL)
The only way the app can legally override DND. Do **one** of:
- **(A)** Tap **"Open Do Not Disturb Access Settings"** in the app, **or**
- **(B)** Navigate manually:
```
Settings > Apps > (All / See all N apps) > Lost My Phone
> Special access > "Do Not Disturb access" > ALLOW / turn ON
```
> Some OEMs label it "Do Not Disturb access", some "Notification policy access".
> Older Android versions: `Settings > Apps > Advanced > Special app access`.
> Exact wording varies by manufacturer/Android version; search Settings for
> **"Do Not Disturb access"**.
Once enabled, return to the app — the status card should read **"Do Not Disturb
Access: GRANTED"**.
### 4. (Optional) Manual test
Tap **"Test Ring Now (30s)"**. Must have completed step 3 or it will warn and open
settings instead.
---
## Architecture
```
app/src/main/java/com/example/lostmyphone/
├── MainActivity.kt # UI: status, permission requests, DND-access launcher, manual test
├── SmsReceiver.kt # BroadcastReceiver: parses SMS -> detects RING_NOW / PLAY_SONG
└── RingService.kt # ForegroundService: DND gate, volume override, focus + playback, 30s timer
```
- **`SmsReceiver`** listens for `android.provider.Telephony.SMS_RECEIVED` and starts
`RingService` as a foreground service (`foregroundServiceType="mediaPlayback"`).
- **`RingService`** encapsulates all playback and DND logic so the ring continues
with the screen off.
---
## Permissions declared (`AndroidManifest.xml`)
- `ACCESS_NOTIFICATION_POLICY` — enables DND override once user grants special access.
- `RECEIVE_SMS` — to detect the trigger commands.
- `FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_MEDIA_PLAYBACK` — media foreground service.
- `READ_MEDIA_AUDIO` (13+) / `READ_EXTERNAL_STORAGE` (≤ 12) — MediaStore song search.
- `POST_NOTIFICATIONS` (13+) — foreground / status notifications.
- `WAKE_LOCK`, `VIBRATE` — ensure the ring is audible/prominent.
---
## Privacy & Safety
- The app **never** plays audio unless (a) the user has granted DND access **and**
(b) a valid trigger was sent or the manual test was tapped.
- Permit to Media and SMS are only used for the described purpose. No data leaves
the device, no analytics, no network calls, no GMS/Firebase.