Skip to content

Promote dev → staging (Android Auto restore + car dashboard, ROTA abandon) - #732

Merged
patrickrb merged 13 commits into
stagingfrom
dev
Aug 10, 2026
Merged

Promote dev → staging (Android Auto restore + car dashboard, ROTA abandon)#732
patrickrb merged 13 commits into
stagingfrom
dev

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Promotes the current dev to staging for the Play internal track (per the android.yml release flow: push to staging → internal track).

Included since staging

  • Restore Android Auto (Play-approved IOT shape) + POTA/ROTA status rows #729 — Restore Android Auto (Play-approved IOT shape) + POTA/ROTA status rows. Re-adds the IOT-category CarAppService wiring that cleared Play production review (versionCode 1327), deletes the rejected NAVIGATION map/Surface variant, and adds live POTA/ROTA activation rows to the car pane. Includes the Copilot fixes (onQsoLogged synchronized; manifest wiring test guards metaData null).
  • Car status dashboard (Android Auto) — design-doc pane with colored badges #731 — Car status dashboard (Android Auto). Implements the design-doc "1a" Pane: colored circular leading badges per row (status dot / band / POTA / ROTA / session), consolidated status row, band + "N decodes last cycle", POTA "N more to validate", ROTA miles, session-fallback row. Fixes a real crash (ForegroundCarColorSpan is not allowed on a Row title — it threw and killed the CarAppService process on every populated render). Includes the Copilot fixes (last-logged callsign from the worked-list; singular/plural counts).
  • Complete the server trip when the operator discards it locally #725 — ROTA: complete the server trip when the operator discards it locally.

Notes

  • targetSdk/compileSdk are already 36 on both dev and staging, so no SDK change rides along here.
  • Verified on-device/emulator where applicable; full testDebugUnitTest + instrumented tests green on the constituent PRs.

🤖 Generated with Claude Code

patrickrb and others added 12 commits August 4, 2026 06:54
Discard local trip was local-only: it dropped the queue and forgot the
trip, but a row already created on roadsontheair.com stayed active
forever — the site called the rover live for a day (the heard-from
window) and badged the trip active after that, and with the app's local
state gone no client could ever complete it. Exactly this stranded
K1AF 2026-08-03 in production.

abandonTrip now snapshots the trip id and credentials before clearTrip
wipes them and fires one best-effort completeTrip. Fire-and-forget is
deliberate: the button also serves the broken cases (bad key, deleted
trip) where the call fails and the row was unreachable anyway. A trip
still pending creation, or an install with no key, skips the doomed
attempt — shouldCompleteAbandonedTrip covers the decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A whitespace-only trip id now counts as no id (isNotBlank, matching the
key check), and rota_abandon_desc — which was defined but rendered
nowhere — is wired up as a caption under the active-trip buttons, since
Discard destroying undelivered data deserves saying before the tap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… removal)

Production release 2.0 (versionCode 1327, built from staging at 4aca0dc)
shipped with Android Auto enabled and cleared Play production review on
Aug 5, 2026. The later NAVIGATION-category map variant (PRs #456/#465) is
what Play rejected ("does not load map and user location"), forcing the
blanket removal in PR #600. Now that we know the IOT-category template
version passes review, bring exactly that shape back:

- Re-add the manifest wiring removed in #600: automotive_app_desc
  descriptor, minCarApiLevel 1, and FT8AFCarAppService exported under the
  IOT category — byte-for-byte the wiring of the approved release.
- Revert QsoStatusScreen, RecentDecodesScreen, and CarQsoStatus to their
  release-commit versions: read-only PaneTemplate QSO status + ListTemplate
  recent decodes, no surface rendering.
- Delete the rejected map-era code (CarMapProjection, CarMapSurfaceRenderer,
  CarPanelLayout, CarSurfaceState) and its tests.
- Flip CarAppManifestWiringTest from asserting AA-unwired back to asserting
  AA-wired, and add guards pinning the approved shape: IOT category only
  (no NAVIGATION/POI) and no androidx.car.app template permissions, so the
  rejected configuration can't silently return.

Debug-only AAOS scaffolding (CarAppActivity overlay, DebugInjectReceiver)
is untouched and unaffected.

Verified: full testDebugUnitTest, processReleaseMainManifest (merged
release manifest carries the service + both meta-data entries), and
assembleDebug all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds two conditional rows to the Android Auto QSO status pane, still inside
the Play-approved IOT/PaneTemplate shape:

- "POTA K-1234 · 3 QSOs" while a park activation is running (resurrects the
  buildCarPotaLine helper from the map-era branch; the car_pota_line string
  was still in-tree).
- "ROTA Route 66 · 12 QSOs · 45.3 mi" while a road trip is running. QSO
  count is sent+pending so contacts logged out of coverage still show;
  miles match the trip notification's one-decimal format.

Row limits: hosts cap pane rows (3 on car API < 2), so rows now carry
priorities and selectCarPaneRows() keeps the most important ones — on a
3-row host an active activation replaces the band line instead of being
silently dropped.

Freshness fix: pota_activation.qso_count was only re-read into
PotaSessionManager's StateFlow when the phone's POTA screen called
refreshCounter(), so the car row would have shown a stale count while
driving. DatabaseOpr now calls PotaSessionManager.onQsoLogged() right after
its SQL bump (mirroring the existing RotaTripManager.onQsoLogged hook), and
the bump predicate is extracted as qsoCountsForActivation() so it provably
matches the SQL's `park_ref = ? AND ended_at IS NULL` binding. ROTA state
was already live (RotaTripManager updates its flow on every QSO/fix).

Tests: buildCarPotaLine / buildCarRotaLine formatting and hide-when-idle,
selectCarPaneRows priority/tie/limit behavior, onQsoLogged idle no-op, and
qsoCountsForActivation vs the DB predicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the park

Two debug-harness additions so the new Android Auto POTA/ROTA rows can be
exercised on an emulator with no radio, server, or GPS:

- `--es rota "Route 66"` starts a genuine offline trip via
  RotaTripManager.startTrip (pendingCreate — the deferred-create path needs
  no API key or network), so the car pane's ROTA row, the notification, and
  the phone's ROTA screen all render real state. Demo QSOs inserted by the
  same broadcast queue into the trip through the normal onQsoLogged path.
- Demo QSOs are now stamped with the active POTA park via the app's real
  stampQso helper, so they bump the activation counter (DB + StateFlow)
  exactly like on-air contacts and the car's POTA row shows a live count.

Trip names stay free-text (no uppercasing); parse coverage added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- PotaSessionManager.onQsoLogged: add @synchronized so its read-modify-write
  of _currentActivation can't race end() (which is @synchronized) and
  resurrect an ended activation or bump the count after it ended.
- CarAppManifestWiringTest: assert metaData is non-null before reading keys,
  so a dropped meta-data block fails with an actionable message not an NPE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore Android Auto (Play-approved IOT shape) + POTA/ROTA status rows
…s on the pane

Ports the CarPlay/Android Auto status-dashboard design onto dev's pane path
(the surface/map variant was removed in #729). The activation block now shows,
via pure helpers in CarQsoStatus.kt:

- POTA row with a "N more to validate the activation" / "Activation validated"
  secondary (POTA_ACTIVATION_TARGET = 10).
- ROTA row with a "X.X mi driven this activation" secondary; car_rota_line is
  split to a 2-arg title + car_rota_miles secondary.
- Session-fallback row ("Session · N QSOs" / "Last logged JA1XYZ · 20m · 41 min")
  when neither POTA nor ROTA is active — the design's "activation rows drop out,
  session stats take the slot."
- Band row gains a "N decodes last cycle" secondary, read from currentMessages
  (per-cycle overlay, so it drops to 0 on a silent slot) rather than the
  cross-cycle mutableFt8MessageList.

Activation rows keep #729's row-priority selection, so on a tight host they
replace the band line instead of being dropped. buildCarActivationRows,
potaValidateSpec, buildCarSessionRow, formatMiles, minutesAgo, and
carDecodesSecondary are unit-tested in CarDashboardTest (17 cases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a render crash

Reworks the pane to the FT8AF Android Auto design doc's "1a" Pane variant
(claude.ai/design project): every row now carries a colored circular leading
badge and the rows are restructured to the design's shape —

- Status row: green dot badge (gray when TX off), "<state> · <countdown>" title,
  and a "<target> · <SNR> · <TX-queue>" secondary.
- Band row: blue "20m" badge, "14.074 MHz · FT8" title, "N decodes last cycle".
- POTA row: amber "P" badge, green-count title, "N more to validate" secondary.
- ROTA row: amber "R" badge, miles secondary.
- Session-fallback row: gray "Σ" badge, "Last logged …" secondary, when idle.

Decision/format/color logic is pure (CarQsoStatus.kt) and unit-tested in
CarDashboardTest; QsoStatusScreen rasterizes the badges to CarIcons and applies
ForegroundCarColorSpan.

Crash fix: the Car App Library rejects ForegroundCarColorSpan on a Row *title*
(Row.setTitle validates against a no-color constraint and throws
IllegalArgumentException, killing the CarAppService process the moment a
populated pane renders). Titles are now plain text; color spans are applied only
to secondary lines, where the library permits them. Verified on an Android
Automotive emulator: the pane renders with the badges and the POTA
"to validate" secondary, no crash.

Also removes the now-unused car_* string resources (rows build their text in
code, matching the existing carDecodeSecondary pattern).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Session row "Last logged …" now uses GeneralVariables.QSL_Callsign_list
  .lastOrNull() (the worked-list, appended in completion order) instead of the
  current TX target, which differs right after a QSO completes while calling CQ —
  the target would then wrongly collapse the row to "No QSOs logged yet".
- Add qsosLabel()/decodesLabel() so counts read "1 QSO"/"1 decode" at exactly one
  instead of "1 QSOs"/"1 decodes"; applied to the POTA/ROTA/session titles and the
  band decode-count secondary. Covered by new CarDashboardTest cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Car status dashboard (Android Auto) — design-doc pane with colored badges
Complete the server trip when the operator discards it locally
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 38.50575% with 107 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.23%. Comparing base (7052afc) to head (08b6426).
⚠️ Report is 4 commits behind head on staging.

Files with missing lines Patch % Lines
...n/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt 0.00% 79 Missing ⚠️
.../kotlin/radio/ks3ckc/ft8af/rota/RotaTripManager.kt 10.00% 9 Missing ⚠️
...debug/kotlin/radio/ks3ckc/ft8af/car/DebugInject.kt 22.22% 7 Missing ⚠️
...main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt 92.64% 0 Missing and 5 partials ⚠️
...otlin/radio/ks3ckc/ft8af/ui/rota/RoadTripScreen.kt 0.00% 4 Missing ⚠️
...tlin/radio/ks3ckc/ft8af/pota/PotaSessionManager.kt 25.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##             staging     #732      +/-   ##
=============================================
+ Coverage      35.89%   36.23%   +0.34%     
- Complexity       289      290       +1     
=============================================
  Files            248      244       -4     
  Lines          30512    30264     -248     
  Branches        4031     3982      -49     
=============================================
+ Hits           10952    10967      +15     
+ Misses         19208    18941     -267     
- Partials         352      356       +4     
Flag Coverage Δ
android 17.96% <38.50%> (+0.31%) ⬆️
desktop 63.06% <ø> (ø)
ios 96.51% <ø> (ø)
native 9.93% <ø> (ø)

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

Files with missing lines Coverage Δ
.../kotlin/radio/ks3ckc/ft8af/ui/map/WorldOutlines.kt 64.63% <ø> (ø)
...tlin/radio/ks3ckc/ft8af/pota/PotaSessionManager.kt 13.63% <25.00%> (+0.73%) ⬆️
...otlin/radio/ks3ckc/ft8af/ui/rota/RoadTripScreen.kt 3.76% <0.00%> (-0.04%) ⬇️
...main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt 93.89% <92.64%> (-0.31%) ⬇️
...debug/kotlin/radio/ks3ckc/ft8af/car/DebugInject.kt 58.45% <22.22%> (-0.25%) ⬇️
.../kotlin/radio/ks3ckc/ft8af/rota/RotaTripManager.kt 5.49% <10.00%> (+0.13%) ⬆️
...n/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt 0.00% <0.00%> (ø)
🚀 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

Promotes the current dev branch state to staging for the Play internal track, bringing back the Play-approved Android Auto (IOT) wiring with a new status-dashboard Pane, plus a fix to ensure abandoned ROTA trips are still completed server-side and POTA activation counters stay live in-car.

Changes:

  • Restore Android Auto manifest wiring (IOT-category CarAppService + descriptor) and replace the prior map/surface approach with a read-only dashboard Pane (badges, spans, row selection).
  • Keep POTA activation QSO counts in sync in-memory on each QSO insert; expand debug injection + tests to cover ROTA trip naming and new dashboard behaviors.
  • Ensure “Discard local trip” (ROTA abandon) makes a best-effort completeTrip call to avoid stranding server rows as active.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ft8af/app/src/testDebug/kotlin/radio/ks3ckc/ft8af/car/DebugInjectTest.kt Adds rota extra coverage and ensures blank/trim behavior is tested.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/rota/RotaAbandonTest.kt New unit tests for abandon → server-complete eligibility logic.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/pota/PotaSessionManagerTest.kt Adds tests for onQsoLogged no-op when idle and bump predicate matching.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatusTest.kt Replaces removed surface-era tests with pane row-selection tests.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarPanelLayoutTest.kt Removes tests for deleted surface banner placement math.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarMapProjectionTest.kt Removes tests for deleted surface map projection code.
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarDashboardTest.kt New unit tests for the pure dashboard row builders (badges/spans/labels).
ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarAppManifestWiringTest.kt Updates manifest wiring tests to assert Android Auto is present and pinned to the approved shape.
ft8af/app/src/main/res/xml/automotive_app_desc.xml Adds the Android Auto app descriptor referenced by manifest meta-data.
ft8af/app/src/main/res/values/strings_compose.xml Updates/removes car-related strings and updates the ROTA abandon description.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/rota/RoadTripScreen.kt Shows the updated abandon description in the active-trip card UI.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/map/WorldOutlines.kt Comment update reflecting removal of map/surface renderer path.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/rota/RotaTripManager.kt Implements best-effort server completion on abandon + adds pure helper.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/pota/PotaSessionManager.kt Adds synchronized onQsoLogged + extracted bump predicate helper.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/QsoStatusScreen.kt Replaces surface-map template with dashboard pane rendering and badge rasterization cache.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarSurfaceState.kt Removes surface snapshot model (no longer used).
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt Adds dashboard row model/builders + row selection priorities and helpers.
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarPanelLayout.kt Removes surface banner layout helpers (no longer used).
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarMapSurfaceRenderer.kt Removes surface renderer implementation (NAVIGATION map path removed).
ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarMapProjection.kt Removes surface map projection geometry (no longer used).
ft8af/app/src/main/java/com/k1af/ft8af/database/DatabaseOpr.java Calls PotaSessionManager.onQsoLogged after bumping activation qso_count.
ft8af/app/src/main/AndroidManifest.xml Restores Android Auto meta-data + IOT-category CarAppService wiring.
ft8af/app/src/debug/kotlin/radio/ks3ckc/ft8af/car/DebugInject.kt Adds rota extra to start an offline trip + ensures demo QSOs stamp POTA via real helper.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/car/CarAppManifestWiringTest.kt Outdated
Comment thread ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/car/CarQsoStatus.kt
… local

CarAppManifestWiringTest re-accessed the platform-nullable ApplicationInfo.metaData
on each getInt(); read it once via checkNotNull into a non-null local so a dropped
meta-data block fails with an actionable message instead of an NPE mid-assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@patrickrb
patrickrb merged commit 8f96592 into staging Aug 10, 2026
36 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