Skip to content

Add Android Auto read-only QSO status display#431

Merged
patrickrb merged 3 commits into
devfrom
feat/android-auto-status
Jul 4, 2026
Merged

Add Android Auto read-only QSO status display#431
patrickrb merged 3 commits into
devfrom
feat/android-auto-status

Conversation

@patrickrb

@patrickrb patrickrb commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Implements #209 (read-only first pass).

Adds Android Auto (phone projection) support: a read-only status display for the running FT8 QSO sequence on the car head unit.

  • Main screen (QsoStatusScreen, PaneTemplate): headline (Calling CQ / QSOing with W1XYZ / Waiting for W1XYZ / Monitoring — TX off, with target SNR), the message currently being transmitted, sequence step (Step RR73 (4/6)), TX/RX slot countdown, and band · frequency · mode.
  • Decodes screen (RecentDecodesScreen, ListTemplate): most recent decoded messages, newest first, with UTC time and SNR. Reached from the action strip; rows are not clickable.
  • Read-only by design — no radio control from the car. The only action is screen navigation.

How it works

  • The engine state is already exposed as MutableLiveData on the process-scoped MainViewModel/FT8TransmitSignal singletons (kept alive in the background by RxForegroundService), so the CarAppService runs in the same process and observes it directly — no IPC, no engine changes.
  • New nullable MainViewModel.peekInstance(): the car UI never boots the engine. Until ComposeMainActivity has created the singleton, the car shows "Open FT8AF on your phone" and re-checks on its 1 Hz tick.
  • All display decisions are in pure mappers (buildCarQsoStatus, buildCarDecodeRows, txFunctionLabel, carBandLine, …) in CarQsoStatus.kt; the Screens only observe and render. Reuses slotTimerState, formatMhz, and the localized qsopanel_* headline strings.
  • Countdown refresh: same-template invalidate() is an unquoted refresh on AA hosts, and the tick only fires when the visible second changes (shouldInvalidateForTick).
  • Category: androidx.car.app.category.IOT (status-style app; not nav/media/messaging). minCarApiLevel 1; ConstraintManager row limits used when the host supports them (API ≥ 2).

Testing

  • CarQsoStatusTest (plain JVM): headline selection incl. no-target/"CQ" rule, TX message gating, seq-step labels 1–6 + fallback, slot TX/RX parity, band line formatting, decode rows ordering/truncation/SNR, tick guard, UTC secondary line.
  • CarAppManifestWiringTest (Robolectric): CarAppService declared+exported with the IOT category; automotive_app_desc + minCarApiLevel meta-data present — a dropped entry only manifests as "app missing from the car launcher", which nothing else catches.
  • peekInstance() is deliberately not unit-tested in isolation: the process-lifetime static would leak across tests in the same JVM (order-dependent flakes); its null branch is exercised by the Screens' open-phone template.
  • Full testDebugUnitTest and assembleDebug pass. detekt/ktlint report no findings in the new files (existing findings are pre-baseline / unformatted legacy code; CI runs both warn-only).

Trying it without a car

Desktop Head Unit: install "Android Auto Desktop Head Unit Emulator" from SDK Manager, enable AA developer mode + Unknown sources on the phone (required for sideloaded car apps), adb forward tcp:5277 tcp:5277, run desktop-head-unit.exe. Launch FT8AF on the phone to start the engine; the car screen goes live and keeps updating while the phone app is backgrounded.

Play-distribution note: shipping this on Play later requires opting into the Android Auto form in Play Console + car-quality review for IOT apps. Sideloaded builds work now with the developer setting above.

🤖 Generated with Claude Code

https://claude.ai/code/session_014B1egpdNWafvAksJCn1tMA

A CarAppService (IOT category) projects the running FT8 sequence onto the
car head unit: headline (Calling CQ / QSOing with X / Waiting for X),
current TX message, sequence step, TX/RX slot countdown, band/freq/mode,
plus a Recent Decodes list screen. Read-only by design — no radio control
from the car.

The screens observe the existing MainViewModel/FT8TransmitSignal LiveData
in-process via a new nullable MainViewModel.peekInstance(); the car UI
never boots the engine, showing "Open FT8AF on your phone" until the phone
activity has created the singleton. All display decisions live in pure
mappers (buildCarQsoStatus, buildCarDecodeRows) with plain-JVM unit tests;
a Robolectric test pins the manifest wiring the AA host discovers us by.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014B1egpdNWafvAksJCn1tMA
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 30.52632% with 132 lines in your changes missing coverage. Please review.
✅ Project coverage is 21.57%. Comparing base (dbe4e03) to head (f9665ba).

Files with missing lines Patch % Lines
...n/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt 0.00% 92 Missing ⚠️
...tlin/radio/ks3ckc/ft8af/car/RecentDecodesScreen.kt 0.00% 30 Missing ⚠️
...otlin/radio/ks3ckc/ft8af/car/FT8AFCarAppService.kt 0.00% 10 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #431      +/-   ##
============================================
+ Coverage     21.47%   21.57%   +0.09%     
- Complexity      133      140       +7     
============================================
  Files           150      154       +4     
  Lines         19755    19945     +190     
  Branches       2909     2947      +38     
============================================
+ Hits           4243     4303      +60     
- Misses        15344    15474     +130     
  Partials        168      168              
Flag Coverage Δ
android 12.42% <30.52%> (+0.26%) ⬆️
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt 100.00% <100.00%> (ø)
...otlin/radio/ks3ckc/ft8af/car/FT8AFCarAppService.kt 0.00% <0.00%> (ø)
...tlin/radio/ks3ckc/ft8af/car/RecentDecodesScreen.kt 0.00% <0.00%> (ø)
...n/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Android Auto (phone projection) support by introducing a read-only car UI that mirrors the live FT8 QSO state (status pane + recent decodes list), plus manifest/resources and tests to ensure the app is discoverable by Android Auto hosts.

Changes:

  • Introduces CarAppService/Session and two AA screens (QsoStatusScreen, RecentDecodesScreen) backed by pure mapping helpers (CarQsoStatus.kt).
  • Wires Android Auto discovery via manifest meta-data and automotive_app_desc.xml, and adds new UI strings for car templates.
  • Adds JVM and Robolectric tests to pin mapper logic and manifest wiring.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/FT8AFCarAppService.kt Adds the Android Auto entry service and host validation.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt Main AA status pane screen observing engine LiveData and rendering a PaneTemplate.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/RecentDecodesScreen.kt AA “Recent decodes” list screen rendering decoded messages in a ListTemplate.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt Pure mappers for headline/rows/labels + decode row mapping and tick guard.
ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java Adds peekInstance() to allow AA UI to observe without booting the engine.
ft8af/app/src/main/AndroidManifest.xml Declares CarAppService + required Android Auto meta-data.
ft8af/app/src/main/res/xml/automotive_app_desc.xml Declares template-based AA support for host discovery.
ft8af/app/src/main/res/values/strings_compose.xml Adds AA screen strings (title, messages, row labels).
ft8af/app/build.gradle Adds androidx.car.app:app:1.4.0 dependency.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatusTest.kt Plain-JVM tests for mapper/formatting/tick guard.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarAppManifestWiringTest.kt Robolectric test ensuring service + meta-data wiring is present.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt Outdated
- RecentDecodesScreen: attach the mutableFt8MessageList observer lazily
  from onGetTemplate instead of init, so a screen rendered before the
  engine singleton exists still hooks the list on a later render.
- Drop the redundant toList() and the false "same mutated ArrayList"
  comment — publishFt8MessageList() already posts a defensive copy that
  is never mutated after posting.
- Fix KDoc SNR examples to the ASCII hyphen the formatter actually emits.
- Note why hosts_allowlist_sample is intentional for release builds (the
  library-maintained official-host list, per the Android for Cars docs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014B1egpdNWafvAksJCn1tMA
@patrickrb patrickrb mentioned this pull request Jul 4, 2026
7 tasks
@patrickrb patrickrb merged commit f23faaa into dev Jul 4, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants