docs(p0): SysRS/SysDes/SRS/SAD/SDD/Verification + traceability for Android P0 reconciliation

Full P0 Android documentation chain:

- SysRS: API 24 → API 28 reconciliation per DEC-004 (SysRS-288);
  add SysRS-305 (Android in-call audio mode), SysRS-306
  (RECORD_AUDIO runtime timing).
- SysDes: SysDes-152 (in-call audio mode subsystem), SysDes-153
  (RECORD_AUDIO permission flow), SysDes-154 (Android voice audio
  backend), SysDes-155 (macOS runtime baseline).
- SRS: SRS-187 → API 28; add SRS-208 (in-call audio mode), SRS-209
  (RECORD_AUDIO + listen-only fallback), SRS-210..215 (Android voice
  audio backend latency/preset/AEC/usage/sharing/foreground service);
  retarget SysDes anchors from generic SysDes-135 to SysDes-152/153/154.
- SAD: SAD-063 refreshed (API 28); add SAD-084 (audio mode
  controller), SAD-085 (permission adapter with listen-only),
  SAD-086 (foreground service), SAD-087 (macOS runtime baseline);
  formalize cross-cutting + platform-specific allocation pattern
  in §24.1.
- SDD: expand SDD-028 (BackIntentService); refresh SDD-073 (build
  config); add SDD-105 (JNI bootstrap), SDD-106 (permission
  requester), SDD-107 (foreground service), SDD-108 (audio mode
  controller), SDD-109 (AAB pipeline), SDD-110 (PTT capability),
  SDD-111..116 (Android voice audio backend), SDD-118 (Android
  bridge build automation), SDD-119 (iOS/macOS bridge build
  automation back-fill).
- Verification: create android-p0-acceptance.md TC-1..TC-18; add
  SWE4-UV-040..052, SWE5-IV-016..026, SWE6-SV-018..030,
  SYS4-SIV-015 strengthened + SYS4-SIV-017/018.
- Governance: traceability matrix v0.9.9 with end-to-end chain
  closure; DEC-032 documents the temporary abiFilters reduction to
  arm64-v8a only and its restore-by gate.

Trace: full chain SysRS → SysDes → SRS → SAD → SDD → Code → Verification.
This commit is contained in:
EdisonJwa
2026-05-18 12:48:28 +08:00
parent 4c19410556
commit 5aa51c310f
13 changed files with 2487 additions and 17 deletions
+319
View File
@@ -0,0 +1,319 @@
# 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/<abi>/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 <pkg>` 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 <pkg>` 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.
@@ -3,7 +3,7 @@
**Document type:** Software Unit Verification Plan
**Process alignment:** ASPICE SWE.4 Software Unit Verification
**Version:** 0.9.2
**Version:** 0.9.14
**Status:** Baseline Candidate
**Language:** English
**Product:** Chanora
@@ -416,3 +416,212 @@ Unit verification results shall be summarized in a unit verification summary rep
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Added desktop PTT unit-verification items SWE4-UV-035 through SWE4-UV-039: `DesktopPttBackend` trait + `PttCapabilityLevel`, platform backends, `PttController` + `AudioTransmitGate`, `PttSanitizer` + `PttCapabilityBadge`, `MissedKeyUpWatchdog`. |
## 11. Android P0 Unit Verification Addendum
**SWE4-UV-040**: Verify Android build configuration unit (`SDD-073` expanded).
- Source SDD: SDD-073
- Verification method: Gradle task assertion + static inspection of `android/app/build.gradle.kts`
- Pass criteria: `minSdk` literal equals `28` (DEC-004); `ndk.abiFilters` set equals `{arm64-v8a, armeabi-v7a, x86_64}` (per SDD-073 item 4 and SDD-118 item 3, `x86` 32-bit is explicitly out of scope and shall not be in the set); release `signingConfig` references the CI signing key reference (not debug); R8/ProGuard `minifyEnabled = true` on release; `bundle { abi.enableSplit = true }`; assemble target produces an AAB artifact at `app/build/outputs/bundle/release/app-release.aab`.
- Evidence: Gradle log + manifest dump
**SWE4-UV-041**: Verify `AndroidPermissionRequester` state machine (`SDD-106`).
- Source SDD: SDD-106
- Source SRS: SRS-209
- Verification method: Dart/Kotlin unit test with a permission-platform test double
- Pass criteria: State transitions are exhaustive for `{granted, denied, permanently_denied, revoked_mid_session}`; transitioning out of `granted` clamps `AudioTransmitGate::set(false)`; re-entering `granted` does NOT auto-transmit (requires explicit user gesture); permanently-denied surfaces a settings-deep-link intent rather than re-prompting.
- Evidence: Test result
**SWE4-UV-042**: Verify `BackIntentService` Android branches (`SDD-028` expanded).
- Source SDD: SDD-028
- Source SRS: SRS-163
- Verification method: Dart unit test with platform-channel test double for both `OnBackInvokedCallback` (API 33+) and `OnBackPressedDispatcher` (API ≤ 32) paths.
- Pass criteria: Modal-first rule — if a modal is open, back closes the modal and does not pop the route. PTT-active-ignore — while `transmit_active == true` the back event is consumed without effect. Route-pop ordering — otherwise the topmost non-modal route pops. The API 33+ branch registers a `OnBackInvokedCallback` at `PRIORITY_DEFAULT`; the pre-33 branch registers an `OnBackPressedCallback`. Switching API level in the test double switches the active branch deterministically.
- Evidence: Test result
**SWE4-UV-043**: Verify `AndroidJniBootstrap` library load and init ordering (`SDD-105`).
- Source SDD: SDD-105
- Verification method: Instrumented unit test on Android (or Robolectric where applicable)
- Pass criteria: `System.loadLibrary("chanora_bridge")` is invoked from the `Application` subclass `onCreate` (not from an Activity); `JNI_OnLoad` runs exactly once per process and captures the `JavaVM*`; bridge init occurs before any FFI call from Dart; double-init is a no-op.
- Evidence: Test result + logcat trace
**SWE4-UV-044**: Verify `AndroidVoiceForegroundService` lifecycle unit (`SDD-107`).
- Source SDD: SDD-107
- Source SRS: SRS-111
- Verification method: Kotlin unit test + Robolectric `ServiceController`
- Pass criteria: `startForeground` is called within 5 s of `onStartCommand` (API 26+ requirement); notification channel exists with `IMPORTANCE_LOW`; `foregroundServiceType` includes `microphone` on API 30+; service stops cleanly on disconnect intent and does not leak its wakelock; restart policy is `START_NOT_STICKY` (no auto-respawn after force-stop).
- Evidence: Test result
**SWE4-UV-045**: Verify `AndroidAudioModeController` snapshot/restore + refcount (`SDD-108`).
- Source SDD: SDD-108
- Source SRS: SRS-208
- Source SysRS: SysRS-305
- Verification method: Kotlin unit test with `AudioManager` test double
- Pass criteria: First acquire snapshots prior `AudioManager.getMode()` and sets `MODE_IN_COMMUNICATION`; nested acquires increment refcount without re-snapshotting; matched release decrements; final release restores the snapshotted prior mode exactly once; when `AudioManager` is unavailable (e.g. injected null), controller logs a sanitized warning and degrades to no-op without throwing.
- Evidence: Test result
**SWE4-UV-046**: Verify `AndroidPttCapability` reports `L0Focused` and pins `FocusedPttBackend` (`SDD-110`).
- Source SDD: SDD-110
- Verification method: Rust + Dart unit test
- Pass criteria: On `target_os = "android"` the `DesktopPttBackend` factory returns `FocusedPttBackend` unconditionally (no ladder evaluation); `PttCapabilityBadge` renders the localized `L0Focused` label with the Android-specific explanation string; no Android branch attempts to bind global hotkeys.
- Evidence: Test result + UI screenshot
| SDD Range | SWE.4 Coverage |
|---|---|
| SDD-028 (expanded) | SWE4-UV-042 |
| SDD-073 (refreshed) | SWE4-UV-040 |
| SDD-105 | SWE4-UV-043 |
| SDD-106 | SWE4-UV-041 |
| SDD-107 | SWE4-UV-044 |
| SDD-108 | SWE4-UV-045 |
| SDD-110 | SWE4-UV-046 |
### Android voice audio backend unit-verification items (SDD-111..SDD-116 wave)
**SWE4-UV-047**: Verify `AndroidVoiceStreamConfig` builder records requested vs. achieved fields.
- Source SDD: SDD-112 (config struct), SDD-111 (trait surface)
- Source SRS: SRS-210, SRS-211, SRS-213, SRS-214
- Source SAD: SAD-077, SAD-081
- Verification method: Rust unit test with a fake Oboe builder return path (no device)
- Pass criteria: Given a `MobileVoiceStreamConfig` requesting `PERFORMANCE_MODE_LOW_LATENCY`, input preset `VoiceCommunication`, output usage `VoiceCommunication`, content type `Speech`, and sharing mode `Exclusive`, the resulting `AndroidVoiceStreamConfig` records the requested values verbatim AND exposes `achieved_*` accessors populated from the fake builder's grant values. Mismatch between requested and achieved is observable via the accessors with no panics. Both input and output stream configs are independently recorded.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: none directly (unit-level; matrix consumes the accessors per-device)
**SWE4-UV-048**: Verify input preset fallback ladder is observable at each step.
- Source SDD: SDD-111 (trait surface, open() error pathway), SDD-112 (preset field)
- Source SRS: SRS-211
- Source SAD: SAD-077
- Verification method: Rust unit test with a scripted Oboe builder mock that returns `ErrorIllegalArgument` for successive presets
- Pass criteria: With the mock denying `VoiceCommunication`, `open()` retries with `VoicePerformance`; with the mock denying both, `open()` retries with `Generic` (`Unprocessed`/default). Each retry is recorded in a structured field (e.g. `attempted_presets: Vec<InputPreset>`) and the final `achieved_input_preset()` returns the preset that was granted. If all presets fail, `open()` returns `BackendError::InputPresetUnavailable` (or the SDD-111 equivalent). No silent fallback.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: none directly (matrix records `achieved_input_preset` per device)
**SWE4-UV-049**: Verify sharing-mode fallback Exclusive→Shared is observable.
- Source SDD: SDD-111, SDD-112 (sharing mode field)
- Source SRS: SRS-214
- Source SAD: SAD-077
- Verification method: Rust unit test with a fake Oboe builder return path
- Pass criteria: When the fake builder accepts `SharingMode::Exclusive`, `achieved_sharing_mode()` returns `Exclusive` and no retry occurs. When the fake builder rejects `Exclusive` (returns the documented Oboe error path for exclusive denial), the unit retries with `Shared`, and `achieved_sharing_mode()` returns `Shared`. The original request (`Exclusive`) is still recorded for diagnostics.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: none directly (matrix records `achieved_sharing_mode` per device)
**SWE4-UV-050**: Verify hardware effects per-effect success/failure logic with software-AEC fallback.
- Source SDD: SDD-113 (per-effect construction + software fallback signal)
- Source SRS: SRS-212
- Source SAD: SAD-077, SAD-081
- Verification method: Rust unit test with a mocked JNI surface (`new_object`-style indirection) representing `AcousticEchoCanceler.create`, `NoiseSuppressor.create`, `AutomaticGainControl.create`
- Pass criteria: For each of AEC, NS, AGC: (a) success path — mock returns a non-null `GlobalRef`, effect is recorded as engaged, `setEnabled(true)` is invoked once; (b) failure path — mock returns null OR throws, the unit records the per-effect failure, does NOT abort the other two, and signals the engine to engage the software equivalent (per SDD-113 item 5 — for AEC failure the engine's software AEC must be flagged on). Order of effect construction does not affect outcome (no cross-effect coupling). `release()` is called on every engaged `GlobalRef` exactly once when the unit is torn down.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: per-device AEC/NS/AGC availability column
**SWE4-UV-051**: Verify performance-mode achieved recording feeds SRS-210 latency tier classification.
- Source SDD: SDD-111 (`achieved_performance_mode`), SDD-112 (mode field)
- Source SRS: SRS-210
- Source SAD: SAD-077
- Verification method: Rust unit test
- Pass criteria: When the fake builder grants `PERFORMANCE_MODE_LOW_LATENCY`, `achieved_performance_mode()` returns `LowLatency` and the SRS-210 tier classifier (the unit-level pure function that maps achieved mode → latency target) returns the 150 ms tier. When the fake builder grants `PERFORMANCE_MODE_NONE` or `POWER_SAVING`, the classifier returns the 250 ms tier. The classifier is a pure function reachable from the engine without a device.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: per-device `achieved_performance_mode` column
**SWE4-UV-052**: Verify output stream usage and content type are set correctly.
- Source SDD: SDD-112 (output config fields)
- Source SRS: SRS-213
- Source SAD: SAD-077
- Verification method: Rust unit test inspecting the builder calls captured by a mock Oboe builder
- Pass criteria: The output stream-construction path invokes `setUsage(VoiceCommunication)` and `setContentType(Speech)` exactly once on the builder before `openStream`. No other usage / content-type combination is reachable from the production path (the test asserts there is no code path that constructs the output stream with `Media` / `Music` defaults). `achieved_output_usage()` returns `VoiceCommunication` and `achieved_output_content_type()` returns `Speech` on a successful open.
- Evidence: Rust unit test result
- SDD-116 matrix row satisfied: per-device `achieved_output_usage` / `achieved_output_content_type` column
| SDD Range | SWE.4 Coverage |
|---|---|
| SDD-111 | SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051 |
| SDD-112 | SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051, SWE4-UV-052 |
| SDD-113 | SWE4-UV-050 |
| SDD-115 | (lifecycle sequencing — see SWE.5 integration items) |
| SDD-116 | (device matrix — see SWE.6 + android-p0-acceptance TC-14..TC-18) |
## Baseline Candidate 0.9.4 Update
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-18 | Added Android P0 unit-verification items SWE4-UV-040 through SWE4-UV-046 covering SDD-028 (expanded back-intent branches), SDD-073 (refreshed build config + AAB), SDD-105 (JNI bootstrap), SDD-106 (permission state machine + transmit clamp), SDD-107 (foreground service lifecycle), SDD-108 (audio mode controller refcount + restore), SDD-110 (PTT capability pin). |
| 0.9.5 | 2026-05-18 | Added Android voice audio backend unit-verification items SWE4-UV-047 through SWE4-UV-052 covering SDD-111 (trait surface), SDD-112 (config requested vs achieved), SDD-113 (per-effect hardware engagement + software fallback) and feeding SRS-210/211/213/214 unit-level classification. |
| 0.9.14 | 2026-05-18 | Wave 3 reviewer follow-up: allocated real SWE.4 IDs for two test sets that had been cited under provisional `SWE4-UV-NNN` placeholders. SWE4-UV-053..055 anchor the three `TransmitModeSelector` permission-state clamp tests in `crates/chanora_audio/src/transmit_selector.rs` (commit 635d160 was cited under the "SWE4-UV-026 family" without real anchors). SWE4-UV-056..057 anchor the two missing `DiagnosticExport::with_android_audio` rendering tests in `crates/chanora_diagnostics/src/lib.rs` (commit aea50a1 introduced the field/builder/`to_text` branch but the unit-test pair was queued for the commit-8 amend). Closes the Wave 3 traceability gap; downstream test cites in commits 4 and 8 are amended to reference these IDs. |
## 12. Android Permission Clamp + Diagnostics Android-Audio Unit Verification Addendum (Wave 3 — v0.9.14)
**Background.** Wave 3 reviewer audit flagged two newly-landed test groups whose source comments cited verification IDs as `SWE4-UV-NNN` placeholders rather than real allocated anchors:
1. Three `TransmitModeSelector` permission-state tests at `crates/chanora_audio/src/transmit_selector.rs:343-435` (introduced by commit 635d160). The reviewer noted at `transmit_selector.rs:343` that these tests cite "SWE4-UV-026 family" without real IDs.
2. Two missing unit tests for the new `chanora_diagnostics` Android-audio surface (`android_audio: Option<String>` field, `with_android_audio` builder, `[audio.android]` `to_text` branch) introduced by commit aea50a1; queued for the commit-8 amend.
This addendum allocates the five required anchors. SWE4-UV-052 was the prior high-water mark; SWE4-UV-053..057 are the next free contiguous range.
**SWE4-UV-053**: Verify `TransmitModeSelector` clamps transmit to `false` when `permission_state == Denied` (or `PermanentlyDenied`/`Unknown`) regardless of PTT, channel, or transmit-mode state.
- Source SDD: SDD-106 §6
- Source SRS: SRS-209 (fail-safe listen-only default)
- Verification method: Rust unit test
- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_denied_clamps_transmit_to_false` (lines 348-384)
- Pass criteria: With `TransmitMode::Ptt`, `in_channel = true`, `ptt_held = true` and the default `Granted` permission state the gate evaluates `true`. Publishing `PermissionGate::Denied` flips the gate to `false`. `PermanentlyDenied` and `Unknown` likewise clamp `false`. The clamp wins over `TransmitMode::Continuous`.
- Evidence: `cargo test -p chanora_audio` result
- Notes: Establishes the fail-safe semantics: cold-launch `Unknown` is listen-only per SRS-209.
**SWE4-UV-054**: Verify `TransmitModeSelector` releases the permission clamp on transition back to `Granted` and resumes normal PTT-driven evaluation.
- Source SDD: SDD-106 §6
- Source SRS: SRS-209
- Verification method: Rust unit test
- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_granted_releases_clamp` (lines 389-409)
- Pass criteria: After `Denied` clamps the gate to `false`, transitioning to `PermissionGate::Granted` (with PTT held + in channel) restores the gate to `true` on the next state tick. Releasing the PTT key then drops the gate per normal PTT semantics — the clamp is no longer in effect.
- Evidence: `cargo test -p chanora_audio` result
- Notes: Pairs with SWE4-UV-053; together they cover the clamp on/off edges.
**SWE4-UV-055**: Verify `TransmitModeSelector` ignores `PermissionState` events for non-`RECORD_AUDIO` permissions (contract: bridge filters before reaching the selector).
- Source SDD: SDD-106 §5
- Verification method: Rust unit test (contract pin)
- Test code path: `crates/chanora_audio/src/transmit_selector.rs::tests::permission_state_for_other_permission_does_not_clamp` (lines 418-435)
- Pass criteria: The selector exposes no setter for non-`RECORD_AUDIO` permissions. With `TransmitMode::Continuous` + `in_channel = true` and no call to `set_permission_state`, the selector remains in its `Granted` default and the gate evaluates `true`. Demonstrates the contract that unrelated permission events (e.g. `POST_NOTIFICATIONS`) cannot reach this code path.
- Evidence: `cargo test -p chanora_audio` result
- Notes: This is a negative/contract test; the JNI bridge is responsible for filtering by permission name before invoking `set_permission_state`.
**SWE4-UV-056**: Verify `DiagnosticExport::with_android_audio(Some(yaml))` renders the `[audio.android]` section between `[metadata]` and `[recent logs]` in `to_text()`.
- Source SDD: SDD-116 item 3 (diagnostics matrix surface), SDD-112 item 10 / SDD-113 item 7 (producing crate)
- Verification method: Rust unit test
- Test code path: `crates/chanora_diagnostics/src/lib.rs::tests::android_audio_renders_between_metadata_and_logs` (to be added by commit-8 amend)
- Pass criteria: Building a `DiagnosticExport::from_sink(...)` and chaining `.with_android_audio(Some("perf: low_latency\n".into()))` produces a `to_text()` output where the substring `[audio.android]` appears (a) after the `[metadata]` header, (b) before the `[recent logs]` header, and (c) is immediately followed by the supplied YAML fragment verbatim. The YAML fragment is not re-redacted by the diagnostics bundle (SDD-090 places sanitisation in the producing crate).
- Evidence: `cargo test -p chanora_diagnostics` result
- Notes: Section ordering is part of the SDD-116 matrix contract; downstream tooling parses by header.
**SWE4-UV-057**: Verify `DiagnosticExport` with default `android_audio = None` omits the `[audio.android]` header entirely (negative test).
- Source SDD: SDD-116 item 3 (negative case — non-Android or pre-session)
- Verification method: Rust unit test
- Test code path: `crates/chanora_diagnostics/src/lib.rs::tests::android_audio_absent_omits_section` (to be added by commit-8 amend)
- Pass criteria: A `DiagnosticExport::from_sink(...)` constructed without `.with_android_audio(...)` (i.e. `android_audio == None`) produces a `to_text()` output that does **not** contain the substring `[audio.android]`. The `[metadata]` and `[recent logs]` sections remain present in order. Calling `.with_android_audio(None)` explicitly is equivalent (idempotent omission).
- Evidence: `cargo test -p chanora_diagnostics` result
- Notes: Pairs with SWE4-UV-056; together they cover the conditional-render branch in `DiagnosticExport::to_text`.
| SDD Range | SWE.4 Coverage |
|---|---|
| SDD-106 §5 | SWE4-UV-055 |
| SDD-106 §6 | SWE4-UV-053, SWE4-UV-054 |
| SDD-116 item 3 | SWE4-UV-056, SWE4-UV-057 |
@@ -245,3 +245,133 @@ Software integration verification results shall be summarized in an integration
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Added desktop PTT software-integration verification item SWE5-IV-015 covering the integrated audio engine + Rust core PTT controller + bridge + Flutter UI + diagnostics path on Windows, macOS, and GNOME-on-Wayland Linux reference hosts. |
## 9. Android P0 Software Integration Verification Addendum
**SWE5-IV-016**: Verify Android JNI bootstrap → foreground service lifecycle integration.
- Source SAD: SAD-077, SAD-081
- Source SDD: SDD-105, SDD-107
- Verification method: Instrumented integration test on a physical API 28 device
- Pass criteria: `Application.onCreate` loads `libchanora_bridge.so`, `JNI_OnLoad` captures the `JavaVM`, the bridge is initialized, and `AndroidVoiceForegroundService` can be started by the Rust core (via JNI callback) without re-loading the library or racing init. Service start completes before the first audio frame is produced.
- Evidence: Integration test result + logcat trace
**SWE5-IV-017**: Verify permission denial → foreground service not started → listen-only stream open integration.
- Source SAD: SAD-077
- Source SDD: SDD-106, SDD-107
- Source SRS: SRS-209
- Verification method: Integration test using permission-state test double + real audio stack
- Pass criteria: When `RECORD_AUDIO` is denied (or revoked mid-session) the FGS is NOT started for capture; the playback stream remains open so the user hears other participants (listen-only fallback per SRS-209); UI surfaces the degraded state; re-granting permission re-enables capture without process restart.
- Evidence: Integration test result
**SWE5-IV-018**: Verify foreground service start triggers audio mode acquisition; service stop triggers mode restore.
- Source SAD: SAD-081, SAD-085
- Source SDD: SDD-107, SDD-108
- Source SRS: SRS-208
- Source SysRS: SysRS-305
- Verification method: Integration test on a physical device with `dumpsys audio` snapshots before / during / after
- Pass criteria: Before voice channel join, `AudioManager.mode` is the device baseline. After FGS start and join, mode is `MODE_IN_COMMUNICATION`. After leave/disconnect, FGS stops and the controller restores the snapshotted prior mode. Refcount holds across rapid join/leave/join cycles.
- Evidence: Integration test result + `dumpsys audio` traces
**SWE5-IV-019**: Verify `BackIntentService` integration with the Flutter Navigator route stack.
- Source SAD: SAD-018
- Source SDD: SDD-028 (expanded)
- Source SRS: SRS-163
- Verification method: Flutter integration test on Android with a non-trivial route stack (home → channel → settings → modal)
- Pass criteria: System back closes modal first (does not pop the route); subsequent back pops one route per press; at the home route system back backgrounds the activity. PTT-active-ignore: while the test driver asserts `transmit_active`, back is consumed. API 33+ and pre-33 code paths both produce identical end states (verified on at least one device per branch).
- Evidence: Integration test result
**SWE5-IV-020**: Verify AAB build pipeline integration (`SDD-109`).
- Source SAD: SAD-063
- Source SDD: SDD-073, SDD-109
- Source SRS: SRS-119, SRS-188
- Verification method: CI build job + `bundletool` validation
- Pass criteria: `flutter build appbundle --release` produces an AAB; `bundletool validate` reports no errors; `bundletool build-apks --connected-device` produces per-ABI splits for `{arm64-v8a, armeabi-v7a, x86_64}`; signing key fingerprint matches the CI release-key reference; `targetSdkVersion` matches the SRS-188 declared target; `minSdkVersion` == 28 (DEC-004); manifest contains `FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_MICROPHONE` (API 34+) + `RECORD_AUDIO` permissions and no others outside the allow-list.
- Evidence: CI artifact + bundletool report
| Architecture / Design Area | SWE.5 Coverage |
|---|---|
| SAD-018, SAD-063, SAD-077, SAD-081, SAD-084, SAD-085, SAD-086 | SWE5-IV-016 through SWE5-IV-020 |
| SDD-028 (expanded), SDD-073 (refreshed), SDD-105, SDD-106, SDD-107, SDD-108, SDD-109, SDD-110 | SWE5-IV-016 through SWE5-IV-020 |
### Android voice audio backend integration items (SDD-111..SDD-116 wave)
**SWE5-IV-021**: Verify `MobileVoiceAudioBackend` trait + audio engine integration on voice_join / voice_leave.
- Source SAD: SAD-077, SAD-081
- Source SDD: SDD-111 (trait surface), SDD-112 (config), SDD-115 (lifecycle)
- Source SRS: SRS-210, SRS-211, SRS-213, SRS-214
- Verification method: Rust integration test on a physical Android device (API 31 mid-tier minimum) driving the engine through `voice_join` / `voice_leave` against the real `AndroidVoiceUnit`
- Pass criteria: `voice_join` constructs `AndroidVoiceStreamConfig` from engine state, calls `AndroidVoiceUnit::open(...)`, opens BOTH input and output streams (verified by `session_id()` being `Some(_)` and the output-stream handle being non-null), then calls `start()`. Engine state transitions are observed in order: `Idle → Opening → Running`. `voice_leave` calls `stop()` then `close()` in that order; engine state returns to `Idle`. No stream is left open after `voice_leave` (verified by a follow-up `voice_join` succeeding with a fresh session id).
- Evidence: Integration test result + logcat with engine state transition lines
- SDD-116 matrix rows satisfied: all (open/close path exercised on every matrix device)
**SWE5-IV-022**: Verify SDD-108 audio-mode engagement precedes SDD-113 effect binding (forward) and effects release precedes mode restore (reverse).
- Source SAD: SAD-077, SAD-081, SAD-085
- Source SDD: SDD-108, SDD-113, SDD-115 (sequencing)
- Source SRS: SRS-208, SRS-212
- Verification method: Integration test instrumented with ordered event sink (timestamped events for `mode_engage`, `stream_open`, `effect_attach`, `start`, `stop`, `effect_release`, `stream_close`, `mode_restore`)
- Pass criteria: On `voice_join`, the recorded order is exactly `FGS_start → stream_open → mode_engage → effect_attach → start`. On `voice_leave`, the recorded order is exactly `stop → effect_release → mode_restore → stream_close → FGS_stop` per SDD-115 item 3 (reverse-order teardown). Any deviation fails the case. Test repeats 3 rapid join/leave cycles to surface refcount or sequencing races.
- Evidence: Integration test result + event-sink dump
- SDD-116 matrix rows satisfied: all
**SWE5-IV-023**: Verify SDD-115 full lifecycle sequencing forward and reverse.
- Source SAD: SAD-077, SAD-081
- Source SDD: SDD-107, SDD-108, SDD-111, SDD-113, SDD-115
- Source SRS: SRS-208, SRS-210, SRS-212, SRS-215
- Verification method: Instrumented integration test on physical device
- Pass criteria: Forward sequence: `FGS_start → streams_open → mode_engage → effects_bind → start_capture+playback`. Reverse sequence: `stop → effects_release → mode_restore → streams_close → FGS_stop`. Test asserts strict ordering AND that no event from one step occurs before the previous step completes (no overlap). Test re-runs across at least one API 28 device and one API 34 device to cover the FGS-type tightening branch.
- Evidence: Integration test result + ordered event log
- SDD-116 matrix rows satisfied: API 28 row, API 31 row, API 34 row
**SWE5-IV-024**: Verify audio-focus loss handling.
- Source SAD: SAD-077, SAD-081, SAD-085
- Source SDD: SDD-115 (audio focus event response)
- Source SRS: SRS-208, SRS-215
- Verification method: Integration test using a controllable secondary audio source (test app that requests focus with the three loss kinds)
- Pass criteria: `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` → capture and playback continue at unchanged level (Chanora does not duck itself; voice content is exempt per SDD-115). `AUDIOFOCUS_LOSS_TRANSIENT` → capture is paused (no frames produced upstream) and playback is paused; on focus regain, both resume without `voice_leave`/`voice_join`. `AUDIOFOCUS_LOSS` (permanent) → the session is left cleanly (effects released, mode restored, streams closed) and the user is surfaced an "audio focus lost" state; no auto-rejoin.
- Evidence: Integration test result + logcat
- SDD-116 matrix rows satisfied: at least API 31 + API 34 rows
**SWE5-IV-025**: Verify `ErrorDisconnected` callback marshals to tokio task and triggers reopen without blocking the callback thread.
- Source SAD: SAD-077, SAD-081
- Source SDD: SDD-111 (error/disconnect callback via bounded channel/watch), SDD-115 (recovery on disconnect)
- Source SRS: SRS-210, SRS-215
- Verification method: Integration test injecting a forced disconnect (e.g. unplug Bluetooth headset mid-session, or invoke an Oboe error-callback fault injection point)
- Pass criteria: The Oboe `onErrorBeforeClose` / `onErrorAfterClose` callback path enqueues a message onto the bounded channel and returns within the callback's documented latency budget (assert: callback returns in < 5 ms). Reopen is performed on a tokio task on the engine runtime, not on the callback thread (verified by thread-id capture in test). Reopen completes (`voice_join` round-trip on the new device route) within 2 s. The callback never holds a lock during reopen.
- Evidence: Integration test result + thread-id capture log
- SDD-116 matrix rows satisfied: all (regression surface)
**SWE5-IV-026**: Verify permission-denied `voice_join` reports listen-only and does NOT start the foreground service for capture.
- Source SAD: SAD-077
- Source SDD: SDD-106, SDD-107, SDD-111, SDD-115
- Source SRS: SRS-209, SRS-215
- Verification method: Integration test with `RECORD_AUDIO` denied via permission test double
- Pass criteria: `voice_join` opens the output (playback) stream and engine state becomes `RunningListenOnly`. `AndroidVoiceForegroundService` is NOT started with `foregroundServiceType="microphone"` for capture (the service may still run as a non-mic FGS for playback longevity per SDD-107 policy — if SDD-107 declines this, the FGS is not started at all). Backend reports `capture_state = ListenOnly`. No `AndroidVoiceUnit` input stream is opened. UI badge reflects listen-only. On runtime permission grant, the engine performs an internal upgrade to full duplex without process restart and without losing the playback stream. Cross-link: builds on SWE5-IV-017.
- Evidence: Integration test result + logcat + UI screenshot
- SDD-116 matrix rows satisfied: API 28 row, API 34 row (FGS-type tightening branch)
| Architecture / Design Area | SWE.5 Coverage |
|---|---|
| SAD-077, SAD-081 (mobile voice audio backend allocation) | SWE5-IV-021 through SWE5-IV-026 |
| SDD-111, SDD-112, SDD-113, SDD-115 | SWE5-IV-021 through SWE5-IV-026 |
| SDD-116 (device matrix coverage entry points) | SWE5-IV-021, SWE5-IV-023 |
## Baseline Candidate 0.9.4 Update
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-18 | Added Android P0 software-integration items SWE5-IV-016..SWE5-IV-020 covering JNI+FGS, permission+FGS+listen-only fallback, FGS+audio-mode round-trip, BackIntentService + Flutter route stack, and AAB build pipeline (bundletool + ABI splits). |
| 0.9.5 | 2026-05-18 | Added Android voice audio backend integration items SWE5-IV-021..SWE5-IV-026 covering MobileVoiceAudioBackend trait + engine wiring (SDD-111), SDD-108↔SDD-113 ordering and reverse-order teardown (SDD-115), full lifecycle sequencing across SDD-107/108/111/113, audio-focus loss handling, ErrorDisconnected callback marshaling, and permission-denied listen-only fallback. Cross-links: SWE5-IV-017 extended by SWE5-IV-026 (listen-only fallback). |
@@ -241,3 +241,160 @@ Software verification results shall be summarized in a software verification sum
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Added desktop PTT software-verification item SWE6-SV-017 covering end-to-end SRS-195..203 verification across Windows, macOS, and GNOME-on-Wayland Linux reference hosts. |
## 9. Android P0 Software Verification Addendum
**SWE6-SV-018**: Verify Android voice foreground service satisfies SRS-111.
- Source SRS: SRS-111
- Verification method: Device-level software-qualification test on API 28 floor device + mid-tier + current target (per `docs/verification/android-p0-acceptance.md` TC-3)
- Pass criteria: While connected to a voice channel the foreground service runs with `foregroundServiceType="microphone"` (API 30+) and a non-dismissible notification; mic capture and playback continue with the screen off for ≥ 10 minutes without being killed; service stops cleanly on disconnect.
- Evidence: Device test log + screen-recording
- Cross-link (added in 0.9.5): SRS-215 device-matrix expansion is covered by SWE6-SV-030. SWE6-SV-018 remains the single-device baseline; SWE6-SV-030 collects per-SDD-116-row evidence.
**SWE6-SV-019**: Verify RECORD_AUDIO request timing and listen-only fallback satisfy SRS-209.
- Source SRS: SRS-209
- Source SysRS: SysRS-306
- Verification method: Device-level test (android-p0-acceptance.md TC-2)
- Pass criteria: The mic permission is requested at the user gesture that first needs it (not at app launch); denial drops capture but keeps playback open (listen-only); permanently-denied state opens the system settings deep-link instead of re-prompting; re-grant re-enables capture without process restart.
- Evidence: Device test log
**SWE6-SV-020**: Verify Android in-call audio mode round-trip satisfies SRS-208.
- Source SRS: SRS-208
- Source SysRS: SysRS-305
- Verification method: Device-level test with `dumpsys audio` (android-p0-acceptance.md TC-6)
- Pass criteria: Pre-join mode is recorded; during voice session mode is `MODE_IN_COMMUNICATION`; on leave/disconnect prior mode is restored exactly. No permanent mode lock across multiple sessions. No crash if a Bluetooth headset is connected/disconnected mid-session (P0 minimum).
- Evidence: Device test log + dumpsys traces
**SWE6-SV-021**: Verify Android back navigation satisfies SRS-163.
- Source SRS: SRS-163
- Verification method: Device-level test (android-p0-acceptance.md TC-11 + TC-12) on API 28, an API ≤ 32 device, and an API 33+ device.
- Pass criteria: Modal-first close; one-pop-per-press; home-route back backgrounds; PTT-active ignores back. Predictive-back animation present on API 33+ and absent on pre-33 with identical end behaviour.
- Evidence: Device test log + screen-recording
**SWE6-SV-022**: Verify Android minimum API satisfies SRS-187 and DEC-004.
- Source SRS: SRS-187
- Source SysRS: SysRS-288
- Verification method: Manifest inspection + install attempt on API 28 device
- Pass criteria: Merged manifest declares `minSdkVersion = 28`. Install on API 28 succeeds. Install attempt on API 27 simulator/device is rejected with a clean error.
- Evidence: Manifest dump + install logs
**SWE6-SV-023**: Verify Android Play target API and AAB packaging satisfy SRS-119 and SRS-188.
- Source SRS: SRS-119, SRS-188
- Verification method: AAB inspection + Play Console upload to Internal Testing (android-p0-acceptance.md TC-13)
- Pass criteria: `targetSdkVersion` matches the currently-required Play target per SRS-188; release artifact is an AAB (not a bare APK) per SRS-119; Play Console accepts the bundle; signing key is the CI release key.
- Evidence: AAB dump + Play Console acceptance screenshot
**SWE6-SV-024**: Verify Android PTT capability badge reports `L0Focused` per SAD-086 / SDD-110.
- Source SRS: SRS-208 (mode boundary), SRS-209
- Verification method: Device-level UI test (android-p0-acceptance.md TC-4 + TC-10)
- Pass criteria: Capability badge shows `L0Focused` with Android-specific explanatory copy. No global hotkey is bound. Backgrounding the app stops PTT transitions from being delivered.
- Evidence: UI screenshot + log
| SRS Range | SWE.6 Coverage |
|---|---|
| SRS-111 | SWE6-SV-018 |
| SRS-119 | SWE6-SV-023 |
| SRS-163 | SWE6-SV-021 |
| SRS-187 | SWE6-SV-022 |
| SRS-188 | SWE6-SV-023 |
| SRS-208 | SWE6-SV-020, SWE6-SV-024 |
| SRS-209 | SWE6-SV-019, SWE6-SV-024 |
### Android voice audio backend qualification items (SRS-210..SRS-215, SDD-111..SDD-116 wave)
**SWE6-SV-025**: Verify SRS-210 mouth-to-ear latency target per device-class on the SDD-116 device matrix.
- Source SRS: SRS-210
- Source SDD: SDD-111, SDD-112, SDD-116
- Verification method: Glass-to-glass acoustic measurement on each SDD-116 matrix device (API 28, API 31, API 34; at least one Pixel + one Samsung). See "Glass-to-glass latency measurement procedure" below.
- Pass criteria: On devices where `achieved_performance_mode() == LowLatency` the measured mouth-to-ear latency is **≤ 150 ms** (median of 20 samples, 95th percentile ≤ 180 ms). On devices where the achieved mode is `None`/`PowerSaving` the measured latency is **≤ 250 ms** (median of 20 samples, 95th percentile ≤ 300 ms). Tier classification is derived from the SWE4-UV-051 unit-level mapping evaluated against the per-device `achieved_performance_mode` from SWE6-SV-026.
- Evidence: Per-device measurement log (CSV: device, API, achieved_mode, sample_index, latency_ms) + reference audio recording for spot-check + acceptance form rows TC-17, TC-18
- SDD-116 matrix rows satisfied: all (one row per device)
**SWE6-SV-026**: Verify SRS-211 input preset achieved value recorded per device.
- Source SRS: SRS-211
- Source SDD: SDD-112 (`achieved_input_preset()`), SDD-116
- Verification method: Device-level test reading the backend's diagnostics export after `voice_join`
- Pass criteria: For each SDD-116 matrix device the diagnostics record one of `{VoiceCommunication, VoicePerformance, Generic}` and that value is consistent with the fallback ladder verified by SWE4-UV-048. `VoiceCommunication` is preferred and any deviation must be accompanied by a recorded fallback rationale (e.g. preset rejected by OEM stack). No device reports an unrecorded/unknown preset.
- Evidence: Per-device diagnostics export
- SDD-116 matrix rows satisfied: all
**SWE6-SV-027**: Verify SRS-212 hardware AEC engagement (and NS/AGC where supported) per device.
- Source SRS: SRS-212
- Source SDD: SDD-113, SDD-116
- Verification method: Device-level test inspecting backend diagnostics + audible regression check (echo audibility) under speakerphone-routed playback
- Pass criteria: For each matrix device the diagnostics record per-effect engagement: `{aec: hw|sw|unavailable, ns: hw|sw|unavailable, agc: hw|sw|unavailable}`. Where `AcousticEchoCanceler.isAvailable()` returns true the recorded value MUST be `hw`. Where it returns false the recorded value MUST be `sw` (software fallback per SDD-113 item 5) — never silent. Audible echo evaluation (test sequence: 10 s of speakerphone playback with mic open at conversational distance) yields no operator-perceptible echo on at least one Pixel device and at least one Samsung device.
- Evidence: Per-device diagnostics export + audible-check operator note
- SDD-116 matrix rows satisfied: all (AEC/NS/AGC availability column)
**SWE6-SV-028**: Verify SRS-213 output usage and content type achieved per device.
- Source SRS: SRS-213
- Source SDD: SDD-112 (`achieved_output_usage`, `achieved_output_content_type`), SDD-116
- Verification method: Device-level test reading backend diagnostics; cross-checked with `dumpsys media.audio_flinger` where queryable
- Pass criteria: For each matrix device the diagnostics record `achieved_output_usage == VoiceCommunication` AND `achieved_output_content_type == Speech`. Where `dumpsys` exposes the stream attributes, the dump corroborates. Mismatch fails the case. Also verifies the in-call routing side-effect: volume slider during the session adjusts the call-volume stream, not media volume (operator check on at least one device).
- Evidence: Per-device diagnostics export + dumpsys excerpt + operator volume-routing note
- SDD-116 matrix rows satisfied: all
**SWE6-SV-029**: Verify SRS-214 sharing mode achieved per device.
- Source SRS: SRS-214
- Source SDD: SDD-112 (`achieved_sharing_mode`), SDD-116
- Verification method: Device-level test reading backend diagnostics on each matrix device
- Pass criteria: For each matrix device the diagnostics record `achieved_sharing_mode ∈ {Exclusive, Shared}`. The requested value is always `Exclusive`; deviation to `Shared` is acceptable and must be recorded with no operational regression (assert: no callback errors in the first 60 s of the session when the achieved mode is `Shared`). The aggregate device-matrix table in the test report cross-tabulates `requested vs achieved` per device.
- Evidence: Per-device diagnostics export
- SDD-116 matrix rows satisfied: all
**SWE6-SV-030**: Verify SRS-215 background mic capture under foreground-service-type=microphone per device.
- Source SRS: SRS-215
- Source SDD: SDD-107, SDD-114 (manifest declarations), SDD-115 (FGS-first sequencing), SDD-116
- Verification method: Device-level test (extends SWE6-SV-018 / android-p0-acceptance.md TC-3) executed on each SDD-116 matrix device
- Pass criteria: With the screen off (device locked or screen timed out) for ≥ 10 minutes the input stream remains open AND frames continue to be produced by the AAudio input (verified by a periodic per-minute frame-count probe in the diagnostics) on every matrix device. On API 34 devices the FGS-type is `microphone` (verified by `dumpsys activity services` excerpt). No `ForegroundServiceDidNotStartInTimeException` or `MissingForegroundServiceTypeException` in logcat. Cross-link annotation: this item extends SWE6-SV-018 with per-matrix-row evidence; SWE6-SV-018 remains the single-device baseline and SWE6-SV-030 is the device-matrix expansion.
- Evidence: Per-device test log + dumpsys excerpt + frame-count probe export
- SDD-116 matrix rows satisfied: all
| SRS Range | SWE.6 Coverage |
|---|---|
| SRS-210 | SWE6-SV-025 |
| SRS-211 | SWE6-SV-026 |
| SRS-212 | SWE6-SV-027 |
| SRS-213 | SWE6-SV-028 |
| SRS-214 | SWE6-SV-029 |
| SRS-215 | SWE6-SV-018 (single-device baseline), SWE6-SV-030 (device-matrix expansion) |
#### Glass-to-glass latency measurement procedure (SWE6-SV-025)
Mouth-to-ear (glass-to-glass) latency for SRS-210 shall be measured end-to-end across the Android device under test acting as the speaker AND a second Android device (or reference Linux host) acting as the listener, both joined to a controlled test TeamSpeak 3 server on the same LAN (no internet hop; LAN RTT must be measured and recorded separately, and subtracted as a baseline only if > 5 ms).
Measurement setup:
1. Connect the DUT and the reference listener device to the test server. The DUT is the talker; the reference is the listener.
2. Drive a short acoustic impulse (a 1 kHz tone burst, 20 ms duration, or a click track) into the DUT microphone from a controlled acoustic source (small loudspeaker at fixed distance, OR a wired line-in adapter where the DUT supports one).
3. Capture the listener device's loudspeaker output with a reference microphone connected to a stereo audio interface; the same audio interface ALSO captures the source impulse on the second channel via a hardwired tap from the impulse generator. Both channels are recorded into a single stereo WAV at ≥ 48 kHz, 16-bit minimum.
4. Latency is the time delta between the impulse-onset sample on channel A (source tap) and the impulse-onset sample on channel B (listener loudspeaker capture), expressed in milliseconds. Onset detection: first sample exceeding 10× the RMS noise floor of the preceding 100 ms.
5. Required precision: ≤ 1 ms (achievable at 48 kHz with single-sample onset detection). The measurement tool is unspecified; any DAW, `sox`, or custom script that meets the precision requirement is acceptable. The script and raw WAV files are part of the evidence and shall be archived.
6. Per-device run: 20 impulses spaced ≥ 2 s apart. Record median and 95th percentile.
7. Baseline subtraction: LAN one-way latency to the test server is measured (e.g. via ICMP / TS3 control-channel ping) and recorded but NOT subtracted from the SRS-210 result, because SRS-210's "mouth-to-ear" definition includes the network hop on a representative network. The LAN result is the ceiling case; WAN regression is out of scope for SRS-210 on this matrix.
8. Operator notes record: DUT model, Android API, `achieved_performance_mode`, `achieved_sharing_mode`, `achieved_input_preset`, headset/loudspeaker routing, ambient noise estimate, ambient temperature only if extreme.
This procedure is reproducible without proprietary fixtures. A loopback-cable-only variant (DUT line-out → DUT line-in on a single device with a wired headset adapter) is acceptable as a screening test but is NOT sufficient for SRS-210 sign-off because it does not exercise the network path.
## Baseline Candidate 0.9.4 Update
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-18 | Added Android P0 software-qualification items SWE6-SV-018..SWE6-SV-024 mapped to SRS-111, SRS-119, SRS-163, SRS-187, SRS-188, SRS-208, SRS-209; cross-linked to `docs/verification/android-p0-acceptance.md` test cases. |
| 0.9.5 | 2026-05-18 | Added Android voice audio backend software-qualification items SWE6-SV-025..SWE6-SV-030 covering SRS-210 (latency tier per SDD-116 matrix, with glass-to-glass measurement procedure), SRS-211 (input preset achieved), SRS-212 (hardware AEC engagement + software fallback evidence), SRS-213 (output usage/content type achieved), SRS-214 (sharing mode achieved), SRS-215 (FGS-hosted background capture per matrix row). Annotated SWE6-SV-018 with cross-link to SWE6-SV-030 (device-matrix expansion). |
@@ -258,3 +258,46 @@ System integration verification results shall be summarized in a system integrat
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Added desktop PTT system-integration verification item SYS4-SIV-016 covering SysDes-142..148 across Windows, macOS, and GNOME-on-Wayland Linux reference hosts. |
## 10. Android P0 System Integration Verification Addendum
SYS4-SIV-015 (Platform Decision gate) is strengthened to require the
Android-specific evidence below. Two new SYS.4 entries are added.
**SYS4-SIV-015 (strengthened — Android target gate)**: In addition to
the original platform-baseline checks, the Android target gate now
requires:
- A boot + connect + voice-channel run on a physical Android 9 (API 28) device per DEC-004 / SysRS-288.
- AAB upload acceptance on the Play Console Internal Testing track (or `bundletool` equivalent) per SRS-119 / SRS-188.
- Foreground service surviving lock-screen for ≥ 10 minutes per SRS-111.
- AudioManager mode snapshot/restore verified via `dumpsys audio` per SRS-208 / SysRS-305.
- Evidence: System integration result + Play Console acceptance record + dumpsys traces.
**SYS4-SIV-017**: Verify Android in-call audio mode round-trip with Bluetooth SCO route awareness.
- Source SysDes: SysDes-133 through SysDes-148 (Android-specific subset), SysRS-305
- Source SRS: SRS-208
- Verification method: System integration test on a physical Android device with at least one Bluetooth headset
- Pass criteria: Pre-session `AudioManager.mode` is captured; voice session sets `MODE_IN_COMMUNICATION`; disconnect restores the prior mode. Bluetooth headset connect/disconnect during a session does not crash the app and does not lock the audio route permanently (P0 minimum). Full SCO-route correctness is logged as a P1 follow-up.
- Evidence: Device test log + dumpsys + Bluetooth-event log
**SYS4-SIV-018**: Verify Android AAB install via Play Internal Track or bundletool.
- Source SysDes: SysDes-133 through SysDes-148 (Android-specific subset)
- Source SRS: SRS-119, SRS-187, SRS-188
- Verification method: Release-pipeline integration test
- Pass criteria: The exact AAB produced by the release CI job is accepted by Play Console Internal Testing (or by `bundletool install-apks --connected-device`). The installed app boots on an API 28 device and a current-target-API device. Signing key fingerprint matches the CI release-key fingerprint. ABI splits exist for `{arm64-v8a, armeabi-v7a, x86_64}`.
- Evidence: Play Console acceptance screenshot + bundletool report + adb install log
| SysDes Range | SYS.4 Coverage |
|---|---|
| SysDes-133 through SysDes-148 (Android subset) | SYS4-SIV-015 (strengthened), SYS4-SIV-017, SYS4-SIV-018 |
## Baseline Candidate 0.9.4 Update
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-18 | Strengthened SYS4-SIV-015 Android target gate (API 28 boot, FGS lock-screen longevity, AudioManager round-trip, Play AAB acceptance) and added SYS4-SIV-017 (in-call mode + Bluetooth SCO awareness) and SYS4-SIV-018 (AAB install via Play Internal Track / bundletool). |