# Android P0 acceptance — human-must test cases (v1.0.0-rc.8) This document captures the human-side verification pass for the Android P0 ship of Chanora. The officially-tested environment is **Android 9 (API 28) or later on a physical Android device, installed via either Play Internal Track or `bundletool install-apks`** (DEC-004). Emulator support is best-effort — microphone capture and audio routing in the AVD do not represent real-device behaviour. Automated tests (`cargo check`, `cargo test --workspace --lib`, `flutter test`, `flutter build appbundle --release`) sign off the mechanical side. This checklist signs off the user-experience side. A failure on any row blocks promotion of the Android build from rc.8. Source: this checklist mirrors the iOS, iPadOS, macOS, Linux, and Windows acceptance documents. SRS references: SRS-013 (audio), SRS-111 (foreground service P0), SRS-119 (AAB packaging), SRS-163 (Android back navigation), SRS-187 (min API 28), SRS-188 (Play target API), SRS-208 (in-call audio mode), SRS-209 (RECORD_AUDIO + listen-only fallback). SAD references: SAD-018, SAD-037, SAD-063, SAD-077, SAD-081, SAD-084, SAD-085, SAD-086. SDD references: SDD-028, SDD-073, SDD-105, SDD-106, SDD-107, SDD-108, SDD-109, SDD-110. Decision: DEC-004 (Android min API 28). System requirements: SysRS-288, SysRS-305, SysRS-306. Android does NOT have a global PTT capability comparable to Windows / macOS / Linux desktops. Per SDD-110 the desktop PTT backend ladder collapses to `FocusedPttBackend` on Android and the capability badge always advertises `L0Focused`. PTT works only while Chanora itself is the foreground app (and while the voice foreground service per SDD-107 keeps the mic stream alive when the screen is off). ## 0. Pre-flight | # | Step | Expected | |---|---|---| | 0.1 | On a Linux/macOS host install Android SDK + NDK + cmdline-tools. Accept SDK licences. | `adb`, `bundletool`, and Gradle wrapper are runnable. | | 0.2 | Connect a physical Android device (API 28+) via USB. Enable Developer Options → USB debugging. | `adb devices` lists the device as `device` (not `unauthorized`). | | 0.3 | Build the bridge for Android: `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android && cd ~/chanora && cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -t x86 -p 28 -- build --release -p chanora_bridge`. | `target//release/libchanora_bridge.so` exists for each ABI. (SDD-073 NDK ABI set, SDD-105 JNI load.) | | 0.4 | Build the AAB: `cd apps/chanora_flutter && flutter build appbundle --release`. | `build/app/outputs/bundle/release/app-release.aab` exists. (SRS-119, SDD-109) | | 0.5 | Validate the AAB with bundletool: `bundletool build-apks --bundle=app-release.aab --output=chanora.apks --connected-device`. | bundletool reports `Universal APK`/`split APKs` per ABI and no validation errors. (SDD-109) | | 0.6 | Install on the connected device: `bundletool install-apks --apks=chanora.apks`. (Or Play Console → Internal Testing → upload + opt-in.) | App installs without signature errors. (SDD-073 release-signing CI assertion.) | | 0.7 | Launch the app. First-launch microphone prompt appears. | Prompt rationale is localized. (SRS-209, SDD-106) | | 0.8 | About / version label inside Chanora | Shows **`v1.0.0-rc.8`**. | ## Device matrix expectations Per SRS-187 / SRS-188, DEC-004, AND SDD-116 (Android voice audio backend device matrix) the human pass shall be executed on the following devices: | Tier | Android version | Reason | Acceptance subset | |---|---|---|---| | Floor | Android 9 (API 28) | Minimum supported per DEC-004 / SRS-187 / SysRS-288; SDD-116 row A | TC-1..TC-11 (all), TC-14 | | Mid-tier | Android 12 (API 31) | Covers FGS foreground-type tightening (API 31); SDD-116 row B | TC-1..TC-11 (all), TC-12 predictive-back (if API ≥ 33), TC-15 | | Target | Android 14 (API 34) per SRS-188 | Play upload + runtime target; SDD-116 row C; FGS-type=microphone strict on API 34 | TC-1..TC-11 (all), TC-13 Play Internal install path, TC-16 | | OEM: Pixel | Any API ≥ 28 | SDD-116 OEM coverage — Pixel | TC-17 latency rig | | OEM: Samsung | Any API ≥ 28 | SDD-116 OEM coverage — Samsung | TC-18 latency rig | The Pixel and Samsung OEM rows MAY overlap with the API tier rows (e.g. the API 34 device may be a Pixel). The minimum aggregate is four physical devices when overlap is used, five otherwise. ## TC-1: Install + identity persistence (DEC-006, SRS-201) | # | Step | Expected | |---|---|---| | 1.1 | Enter a reachable TS3 server address, a nickname, blank password | Connect button enabled | | 1.2 | Tap Connect | Server snapshot appears | | 1.3 | Background + relaunch with the same nickname | Server-visible UID is the same (identity persisted in app-private files dir) | Traces: SRS-013, SRS-209. SWE.6: SWE6-SV-018. SWE.5: SWE5-IV-016. ## TC-2: Microphone permission state machine (SRS-209, SDD-106) | # | Step | Expected | |---|---|---| | 2.1 | First run: tap PTT → system prompt appears → Allow. | Mic transmits. Capability badge says `L0Focused`. | | 2.2 | Revoke RECORD_AUDIO via Settings → Apps → Chanora → Permissions while app is running. Return to Chanora and attempt to transmit. | App degrades to listen-only stream (per SRS-209 fallback). No crash. User-visible banner explains state. | | 2.3 | Re-grant RECORD_AUDIO. | Transmit re-enables without restart (or via clearly-labelled "Retry" button). | | 2.4 | Fresh install: deny the prompt twice (Android treats this as "permanently denied"). Attempt PTT. | App stays in listen-only; in-app banner deep-links to system settings. | Traces: SRS-209, SysRS-306, SAD-085, SDD-106. SWE.4: SWE4-UV-041. SWE.5: SWE5-IV-017. SWE.6: SWE6-SV-019. ## TC-3: Voice channel join + foreground service longevity (SRS-111, SDD-107) | # | Step | Expected | |---|---|---| | 3.1 | Join a voice channel. | Foreground service notification appears with `foregroundServiceType="microphone"` (API 30+). Per SDD-107 the notification is non-dismissible while connected. | | 3.2 | Lock the screen. Leave the device idle for **≥ 10 minutes**. | Voice continues to be sent/received. No `ANR`, no service kill, no `RemoteServiceException`. | | 3.3 | Open Settings → Battery while still connected. | Chanora is not listed as "restricted". (P0: warn user if it is.) | | 3.4 | Disconnect voice channel. | Foreground service stops, notification disappears, AudioManager mode is restored (TC-6 verifies). | Traces: SRS-111, SAD-081, SDD-107. SWE.4: SWE4-UV-043. SWE.5: SWE5-IV-018, SWE5-IV-019. SWE.6: SWE6-SV-018. ## TC-4: PTT press / release (Focused only — Android limitation, SDD-110) | # | Step | Expected | |---|---|---| | 4.1 | Open Voice settings → Push to talk | Capability badge clearly states **L0 Focused** with explanatory text noting Android does not expose global hotkeys to background apps. | | 4.2 | Touch and hold the on-screen PTT button | Mic indicator goes active while held; releases on touch-up. | | 4.3 | Background the app while holding PTT (swipe home). | Per SDD-110 PTT does NOT continue while backgrounded; transition is observable in the UI on return. | Traces: SRS-195, SRS-196, SAD-086, SDD-110. SWE.4: SWE4-UV-044. SWE.6: SWE6-SV-024. ## TC-5: Release tail | # | Step | Expected | |---|---|---| | 5.1 | Set release-tail to 0 ms; tap-release | Sharp on/off. | | 5.2 | Set release-tail to 500 ms; tap-release | Mic stays on ~500 ms after release. | ## TC-6: AudioManager in-call mode round-trip (SRS-208, SDD-108) | # | Step | Expected | |---|---|---| | 6.1 | Before connecting, capture `AudioManager.getMode()` via diagnostic export (or `adb shell dumpsys audio`). Note the value (typically `MODE_NORMAL`). | Baseline recorded. | | 6.2 | Connect + join voice channel. | Per SRS-208 / SDD-108 the controller enters in-call mode (`MODE_IN_COMMUNICATION` or platform equivalent). | | 6.3 | Disconnect. | Per SDD-108 prior-mode snapshot is restored. Re-read AudioManager mode == baseline from 6.1. | | 6.4 | Bluetooth SCO route check (P1 awareness, P0 minimum: no crash): pair a Bluetooth headset, connect Chanora voice. | P0: no crash, no permanent route lock. P1 follow-up: SCO routes correctly. | Traces: SRS-208, SysRS-305, SAD-084, SDD-108. SWE.4: SWE4-UV-045. SWE.5: SWE5-IV-019. SWE.6: SWE6-SV-020. SYS.4: SYS4-SIV-017. ## TC-7: Mic / speaker mute | # | Step | Expected | |---|---|---| | 7.1 | Mute mic via VoiceBar | Mic icon shows muted state; PTT does not unmute. | | 7.2 | Speaker mute | Other clients silenced locally. | ## TC-8: Reconnect | # | Step | Expected | |---|---|---| | 8.1 | Toggle airplane mode for 10 s then off | Connection-lost banner, then recovers. Foreground service does not crash during the transition. | ## TC-9: Diagnostic export + privacy (DEC-027) | # | Step | Expected | |---|---|---| | 9.1 | Export a diagnostic bundle (Settings → Diagnostics → Export). | Bundle is saved via the system Storage Access Framework picker. Allow-listed fields only; no raw key labels, no audio bytes, no `RECORD_AUDIO` permission token. | Traces: SRS-117/SRS-118 family. SWE.6: SWE6-SV-006. ## TC-10: Capability badge labels (SDD-110) | # | Step | Expected | |---|---|---| | 10.1 | Capability badge | Shows `L0Focused` with text explaining Android does not expose session-level global hotkeys. | ## TC-11: Android back navigation (SRS-163, SDD-028) | # | Step | Expected | |---|---|---| | 11.1 | Open a modal sheet (e.g. Settings → Push-to-Talk binding sheet) and press the system back gesture / button. | Modal closes; route stack unchanged. (SDD-028 modal-first rule.) | | 11.2 | From a leaf screen press back. | Pops one route. Does not exit the app unless on the home route. | | 11.3 | While PTT is actively held, press back. | Back is **ignored** until PTT is released. (SDD-028 PTT-active-ignore rule.) | | 11.4 | From the home route press back. | App backgrounds via standard activity finish (does not crash). | Traces: SRS-163, SAD-018, SDD-028. SWE.4: SWE4-UV-040, SWE4-UV-042. SWE.5: SWE5-IV-020. SWE.6: SWE6-SV-021. ## TC-12: Predictive back (Android 13+ API 33 branch, SDD-028) | # | Step | Expected | |---|---|---| | 12.1 | On API 33+ enable "Predictive back animations" in Developer Options. Trigger a back gesture from a non-home route. | Predictive back preview shows; release commits the pop. SDD-028 API 33+ branch (`OnBackInvokedCallback`) is exercised. | | 12.2 | On API ≤ 32 the same scenario uses the legacy `OnBackPressedDispatcher` branch (SDD-028 pre-33 branch). | Same end behaviour; no predictive preview. | Traces: SRS-163, SDD-028. SWE.4: SWE4-UV-042. SWE.6: SWE6-SV-021. ## TC-13: Play Internal Track / bundletool install path (SRS-119, SRS-188, SDD-109) | # | Step | Expected | |---|---|---| | 13.1 | Upload `app-release.aab` to Play Console → Internal Testing track (or via `bundletool` for the same effect). | Play accepts the bundle: targetSdk matches SRS-188, minSdk == 28 per DEC-004, signing key is the release CI key per SDD-073. | | 13.2 | Opt-in tester installs via Play store internal link. | App installs and runs identically to the bundletool path (TC-0.6). | | 13.3 | Verify the AAB contains per-ABI split APKs (`armeabi-v7a`, `arm64-v8a`, `x86_64`). | bundletool report lists all expected ABIs (SDD-109 ABI splits). | Traces: SRS-119, SRS-188, SAD-063, SDD-073, SDD-109. SWE.5: SWE5-IV-020. SWE.6: SWE6-SV-022, SWE6-SV-023. SYS.4: SYS4-SIV-018. ## TC-14: Per-device evidence collection on API 28 device (SDD-116 matrix row A) | # | Step | Expected | |---|---|---| | 14.1 | On the API 28 device, complete pre-flight then `voice_join` a test channel. | Channel join succeeds; FGS active per TC-3. | | 14.2 | Export the backend diagnostics bundle (Settings → Diagnostics → Export, or the equivalent in-app action). | Export contains `achieved_performance_mode`, `achieved_sharing_mode`, `achieved_input_preset`, `achieved_output_usage`, `achieved_output_content_type`, and per-effect `{aec, ns, agc}` engagement (`hw` / `sw` / `unavailable`). | | 14.3 | Record all six achieved values + per-effect engagement into the SDD-116 matrix table for the API 28 row. | All fields populated; no `unknown`. | | 14.4 | `voice_leave`. | Backend tears down cleanly per SWE5-IV-023 reverse order. | Traces: SRS-210, SRS-211, SRS-212, SRS-213, SRS-214, SRS-215, SDD-111, SDD-112, SDD-113, SDD-116. SWE.6: SWE6-SV-026, SWE6-SV-027, SWE6-SV-028, SWE6-SV-029, SWE6-SV-030. SWE.5: SWE5-IV-021. ## TC-15: Per-device evidence collection on API 31 device (SDD-116 matrix row B) | # | Step | Expected | |---|---|---| | 15.1 | On the API 31 device, complete pre-flight then `voice_join`. | Channel join succeeds. | | 15.2 | Export diagnostics bundle. | Same fields as TC-14.2 populated. | | 15.3 | Record values into SDD-116 matrix table for the API 31 row. | All fields populated. | | 15.4 | Verify FGS-foreground-type tightening branch is exercised: `dumpsys activity services ` shows the voice service running. | Service entry present; type recorded. | | 15.5 | `voice_leave`. | Clean teardown. | Traces: SRS-210..SRS-215, SDD-111..SDD-116. SWE.6: SWE6-SV-026..030. SWE.5: SWE5-IV-021, SWE5-IV-023. ## TC-16: Per-device evidence collection on API 34 device (SDD-116 matrix row C) | # | Step | Expected | |---|---|---| | 16.1 | On the API 34 device, complete pre-flight then `voice_join`. | Channel join succeeds. | | 16.2 | Export diagnostics bundle. | Same fields as TC-14.2 populated. | | 16.3 | Record values into SDD-116 matrix table for the API 34 row. | All fields populated. | | 16.4 | Verify `dumpsys activity services ` shows `foregroundServiceType=microphone` for the voice service (API 34 type tightening). | `microphone` type present. | | 16.5 | Screen-off / locked for ≥ 10 minutes during a live voice session. | Mic frames continue (per-minute frame-count probe in diagnostics is non-zero each minute). No `MissingForegroundServiceTypeException` in logcat. | | 16.6 | `voice_leave`. | Clean teardown. | Traces: SRS-210..SRS-215, SDD-107, SDD-111..SDD-116. SWE.6: SWE6-SV-026..030. SWE.5: SWE5-IV-021, SWE5-IV-023, SWE5-IV-026. ## TC-17: Glass-to-glass latency measurement on Pixel device (SDD-116 OEM coverage) | # | Step | Expected | |---|---|---| | 17.1 | Set up the latency rig per `swe6-software-verification-plan.md` "Glass-to-glass latency measurement procedure" (DUT = Pixel device, reference listener on LAN, stereo capture on audio interface). | Rig produces clean impulse / listener channels. | | 17.2 | Record `achieved_performance_mode` for the Pixel DUT. | Recorded (typically `LowLatency` on Pixel). | | 17.3 | Run 20 impulses; compute median and p95 latency. | Both recorded. | | 17.4 | Compare against SRS-210 tier per SWE6-SV-025 pass criteria. | If `LowLatency`: median ≤ 150 ms, p95 ≤ 180 ms. Otherwise: median ≤ 250 ms, p95 ≤ 300 ms. | | 17.5 | Archive raw stereo WAVs + measurement script + per-impulse CSV. | Artifacts attached to test report. | Traces: SRS-210, SDD-111, SDD-112, SDD-116. SWE.6: SWE6-SV-025. SWE.4: SWE4-UV-051 (tier classifier). ## TC-18: Glass-to-glass latency measurement on Samsung device (SDD-116 OEM coverage) | # | Step | Expected | |---|---|---| | 18.1 | Re-run the TC-17 rig with the DUT swapped to a Samsung device. | Rig setup unchanged. | | 18.2 | Record `achieved_performance_mode` for the Samsung DUT. | Recorded. | | 18.3 | Run 20 impulses; compute median and p95 latency. | Both recorded. | | 18.4 | Compare against SRS-210 tier per SWE6-SV-025 pass criteria. | Same thresholds as TC-17.4 against the Samsung-achieved tier. | | 18.5 | Archive evidence as TC-17.5. | Artifacts attached. | Traces: SRS-210, SDD-111, SDD-112, SDD-116. SWE.6: SWE6-SV-025. SWE.4: SWE4-UV-051. ## Auto-test sign-off These rows do not require human interaction. | Layer | Result | |---|---| | `cargo check --workspace` | clean | | `cargo test --workspace --lib` | unchanged (Android-specific Rust code is `#[cfg(target_os = "android")]`-gated) | | `cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -p 28 -- build --release -p chanora_bridge` | clean per ABI | | `flutter test` in `apps/chanora_flutter` | green; widget + e2e suites carry SRS trace headers | | `flutter build appbundle --release` | clean — produces `app-release.aab` | | `bundletool validate --bundle=app-release.aab` | no errors | | Gradle `assertMinSdk28` task (per SDD-073) | passes; minSdk in merged manifest == 28 | | R8 / ProGuard release pass (per SDD-073) | clean; mapping file archived | | CI release-signing key custody check (per SDD-073) | signing key reference matches expected fingerprint | ## Sign-off form ``` chanora v1.0.0-rc.8 — Android P0 acceptance Device 1 (API 28): ______________________ (model + Android version) Device 2 (mid): ______________________ (model + Android version) Device 3 (target): ______________________ (model + Android version) Tester: ______________________ Date: ______________________ TC-1 Install + identity persistence [ pass / fail ] notes: TC-2 RECORD_AUDIO state machine [ pass / fail ] notes: TC-3 Voice channel + FGS longevity [ pass / fail ] notes: TC-4 PTT press / release (Focused) [ pass / fail ] notes: TC-5 Release tail [ pass / fail ] notes: TC-6 AudioManager mode round-trip [ pass / fail ] notes: TC-7 Mic / speaker mute [ pass / fail ] notes: TC-8 Reconnect [ pass / fail ] notes: TC-9 Diagnostic export + privacy [ pass / fail ] notes: TC-10 Capability badge label [ pass / fail ] notes: TC-11 Back navigation [ pass / fail ] notes: TC-12 Predictive back (API 33+) [ pass / fail ] notes: TC-13 Play Internal / bundletool install [ pass / fail ] notes: TC-14 Per-device evidence API 28 (SDD-116 A)[ pass / fail ] notes: TC-15 Per-device evidence API 31 (SDD-116 B)[ pass / fail ] notes: TC-16 Per-device evidence API 34 (SDD-116 C)[ pass / fail ] notes: TC-17 Latency rig — Pixel [ pass / fail ] notes: TC-18 Latency rig — Samsung [ pass / fail ] notes: Overall result: [ Android rc.8 PASS / Android rc.8 FAIL ] Sign: ______________________ ``` ## Known gaps (not blockers) - **Bluetooth SCO routing**: P0 only requires no-crash behaviour. Full SCO route correctness is P1. - **Background PTT**: not possible on Android per platform model; `FocusedPttBackend` only (SDD-110). - **VAD** (`TransmitMode::VoiceActivity`) intentionally disabled in P0 (DEC-030). - **Tablet / foldable layouts**: this checklist targets phone form factor. The wide-mode (≥ 840 dp) UI works but is not signed off here. - **Play Console upload key custody**: depends on whoever holds the release keystore; CI signing assertion (SDD-073) covers the mechanical side only.