---
name: verify-dreamdroid
description: Prove dreamDroid phone UI. Prefer instrumented Compose tests. Use adb dumps only for a single look or a shell path that has no test yet.
---

# Verify dreamDroid

dreamDroid is a phone/tablet Enigma2 remote (`net.reichholf.dreamdroid`). **Default proof is instrumented tests**, not tapping the emulator. See `AGENTS.md`.

```bash
./gradlew.bat :app:connectedGoogleDebugAndroidTest
```

Use **JDK 25** (`JAVA_HOME`). App `compileOptions` stay on Java 17; Gradle/AGP run on JDK 25. Do not pass `-Pandroid.testInstrumentationRunnerArguments...` — that sets Gradle property `android` to a String and breaks `android.applicationVariants`. Filter a class with:

```bash
adb shell am instrument -w -e class net.reichholf.dreamdroid.ui.about.AboutScreenTest net.reichholf.dreamdroid.debug.test/net.reichholf.dreamdroid.testutil.HiltTestRunner
```

Tests live in `app/androidTest/java`. Add Compose UI tests next to each new screen. Prefer Compose Material 3 / Navigation `dialog` hosts (Phase **2.1g-ii**). If the UI is still Compose inside an XML dialog or `ComposeView`, host it that way in the test until that wrapper is deleted.

`verify-dreamdroid.py` is for a **single look** (screenshot) or a shell-only path that has no instrumented test yet. It is not the verification loop. **On Cursor Cloud Agents, do not use launch / GUI tapping / screenshot walkthroughs** — soft-accelerated emulator + agent display is unreliable; see `AGENTS.md` Cloud Agent environment. Use `bash .cursor/cloud/connected-test.sh` only.

Helper (from repo root):

```bash
python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py <command>
```

On Windows use the same command. The script finds `adb` on `PATH`, in `ADB`, or in `sdk.dir` from `local.properties`.

Default package: `net.reichholf.dreamdroid.debug` (googleDebug `applicationIdSuffix`). Never drive `net.reichholf.dreamdroid` (Play/F-Droid release) or the Amazon id.

Set `ANDROID_SERIAL` when more than one device is attached. Two googleDebug instances cannot run side by side on one device; debug vs release can.

Read [features/README.md](features/README.md) before driving. Exercise the mapped user path, not internal setters or HTTP clients.

## Launch (optional look)

1. `JAVA_HOME` at JDK 25. `gradle.properties` must not pass `-XX:MaxPermSize` or CMS flags.
2. Install if needed: `./gradlew.bat :app:installGoogleDebug`
3. Disposable session: `python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py launch --clear-data`

Ready when `pidof net.reichholf.dreamdroid.debug` returns a pid and the helper prints `started ... pid=...`. A fresh install with no saved profile opens the setup wizard (`Welcome!`) and stays there until a profile is saved. Bouquets, EPG, zap, and remote still need a reachable Enigma2 WebInterface unless the recipe says otherwise.

Teardown is `cleanup` below. Do not `am force-stop` or `pm uninstall` by the release package name.

## Doctor

```bash
python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py doctor
```

Pass only when an adb device is in `device` state, `net.reichholf.dreamdroid.debug` is installed and in the foreground, and it has a pid. If doctor fails, relaunch the debug package; do not tap a user-owned release install.

## Drive (optional look)

Prefer resource ids and visible text over coordinates. About lives at the bottom of Settings, not in the drawer.

| User control | Handle |
| --- | --- |
| Open drawer | content-desc `Open navigation drawer` (English) |
| Drawer profile row | `net.reichholf.dreamdroid.debug:id/drawer_profile` |
| Profile name / status | `...:id/drawer_profile_name`, `...:id/drawer_profile_status` |
| TV & Movies | text `TV & Movies` |
| EPG / Virtual Remote / Zap | text `EPG`, `Virtual Remote`, `Zap` |
| About | Settings, then text `About` (Compose dialog) |
| Add Profile FAB | `...:id/fab_main` content-desc from `R.string.profile_add` (shell XML FAB) || Autodiscovery | text `Dreambox Autodiscovery` |
| TV/Radio/Movies/Timer tabs | text `TV`, `Radio`, `Movies`, `Timer` |
| Now strip | text `Now` (English) / `Es läuft` (German) on TV & Movies (Settings → Now-playing strip; default on) |

- First-start after `--clear-data` shows Changelog. Dismiss with `back` before driving Profiles or the drawer. The changelog text contains the word `Profiles`, so `wait-text "Profiles"` is a false positive until the dialog is gone.

Receiver-backed screens (TV & Movies, Zap, EPG, remote keypresses) are unverified when the profile host does not answer. Report the unmet precondition; do not call a connection-error snackbar a successful zap.

## Evidence

Instrumented test output is the proof for Compose screens. Optional look artifacts live in `.cursor/skills/verify-dreamdroid/artifacts/` (gitignored). Cleanup must not delete them.

```bash
python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py dump --path .cursor/skills/verify-dreamdroid/artifacts/<feature>/ui.xml
python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py screenshot --path .cursor/skills/verify-dreamdroid/artifacts/<feature>/screen.png
```

## Cleanup

```bash
python .cursor/skills/verify-dreamdroid/scripts/verify-dreamdroid.py cleanup
```

Stops the debug package started by `launch` (`kill` of the recorded pid, then `am force-stop` of `net.reichholf.dreamdroid.debug` only). Removes the device-side dump file. Leaves `artifacts/` on disk.

## Helpers

`scripts/verify-dreamdroid.py` subcommands: `doctor`, `launch [--clear-data]`, `dump [--path]`, `screenshot --path`, `tap (--text|--desc|--resource-id)`, `wait-text TEXT`, `contains TEXT`, `back`, `cleanup`.
