From 8f910d692f8ccfbb38d0bc070c2dd794aeb9af8e Mon Sep 17 00:00:00 2001 From: James Rich Date: Thu, 21 May 2026 16:54:57 -0500 Subject: [PATCH] feat(specs): Car App Library 1.9.0-alpha01 integration specification Complete SDD specification for Android Auto / AAOS integration: - spec.md: 7 user stories, 22 FRs, 11 NFRs, 10 success criteria - plan.md: Implementation plan with research decisions, data model, contracts - tasks.md: 40 dependency-ordered tasks across 10 phases - research.md: 10 technical decisions with alternatives considered - contracts/: Service and manifest declaration contracts - checklists/: 65-item implementation checklist - quickstart.md: Developer setup and DHU testing guide Key decisions: - CAL 1.9.0-alpha01 with all 7 new components (alpha risk accepted) - MESSAGING + POI categories (no NAVIGATION) - PlaceListMapTemplate for node positions (6-item cap) - CAL built-in voice input (AppFunctions handles system AI separately) - Shared BLE connection (Application-scoped via Koin) - Crashlytics car_session tagging for observability - Google flavor only distribution - No parked-mode differentiation (per official docs) - Cross-platform parity audit vs Meshtastic-Apple CarPlay Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 3 +- .specify/feature.json | 4 +- .../checklists/car-integration.md | 107 ++++ .../checklists/requirements.md | 36 ++ .../contracts/car-app-service.md | 230 +++++++++ .../contracts/manifest-declarations.md | 133 +++++ .../data-model.md | 226 +++++++++ .../plan.md | 134 +++++ .../quickstart.md | 150 ++++++ .../research.md | 164 +++++++ .../spec.md | 460 ++++++++++++++++++ .../tasks.md | 273 +++++++++++ 12 files changed, 1918 insertions(+), 2 deletions(-) create mode 100644 specs/20260521-153452-car-app-library-integration/checklists/car-integration.md create mode 100644 specs/20260521-153452-car-app-library-integration/checklists/requirements.md create mode 100644 specs/20260521-153452-car-app-library-integration/contracts/car-app-service.md create mode 100644 specs/20260521-153452-car-app-library-integration/contracts/manifest-declarations.md create mode 100644 specs/20260521-153452-car-app-library-integration/data-model.md create mode 100644 specs/20260521-153452-car-app-library-integration/plan.md create mode 100644 specs/20260521-153452-car-app-library-integration/quickstart.md create mode 100644 specs/20260521-153452-car-app-library-integration/research.md create mode 100644 specs/20260521-153452-car-app-library-integration/spec.md create mode 100644 specs/20260521-153452-car-app-library-integration/tasks.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f6f0e772a..9288ce8d4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -54,6 +54,7 @@ KMP modules have different task names than pure-Android modules. Using the wrong For additional context about technologies to be used, project structure, -shell commands, and other important information, read the current plan +shell commands, and other important information, read the current plan at +specs/20260521-153452-car-app-library-integration/plan.md diff --git a/.specify/feature.json b/.specify/feature.json index cd2b73f68..bc524dd91 100644 --- a/.specify/feature.json +++ b/.specify/feature.json @@ -1 +1,3 @@ -{"feature_directory":"specs/20260520-153412-nav-tab-labels"} +{ + "feature_directory": "specs/20260521-153452-car-app-library-integration" +} diff --git a/specs/20260521-153452-car-app-library-integration/checklists/car-integration.md b/specs/20260521-153452-car-app-library-integration/checklists/car-integration.md new file mode 100644 index 000000000..ab95f2d83 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/checklists/car-integration.md @@ -0,0 +1,107 @@ +# Car App Library Integration Checklist: Car App Library Integration + +**Purpose**: Validate requirements quality, completeness, and clarity for the Car App Library 1.9.0-alpha01 integration — covering automotive safety, component usage, connectivity, distribution, and testability +**Created**: 2026-05-21 +**Feature**: [spec.md](../spec.md) + +## Requirement Completeness + +- [ ] CHK001 — Are CarAppService lifecycle requirements specified (onCreateSession, onDestroy, multi-session behavior)? [Completeness, Gap] +- [ ] CHK002 — Are requirements defined for all 7 new 1.9.0-alpha01 components (Spotlight, Condensed Items, Chips, Minimized Control Panel, Banners, Section Headers, Expanded Headers)? [Completeness, Spec §FR-002–FR-013] +- [ ] CHK003 — Are Screen navigation graph requirements documented (which screens link to which, back-stack behavior)? [Completeness, Gap] +- [ ] CHK004 — Are ConversationItem requirements specified (message grouping, read/unread state, sender avatar rendering)? [Completeness, Spec §FR-002] +- [ ] CHK005 — Are TTS readback requirements defined with language, speed, and fallback behavior? [Completeness, Spec §US-7] +- [ ] CHK006 — Are quick-reply template storage and configuration requirements specified? [Completeness, Spec §FR-004] +- [ ] CHK007 — Are Koin module registration requirements documented for the `feature/car` DI graph? [Completeness, Gap] +- [ ] CHK008 — Are requirements defined for the google-flavor-only build gate (how other flavors exclude the car module)? [Completeness, Gap] +- [ ] CHK009 — Are requirements specified for CarAppService `onNewIntent` handling and deep-link entry points? [Completeness, Gap] +- [ ] CHK010 — Are node detail view content requirements exhaustively enumerated (last heard, distance, hardware model, firmware version, hops)? [Completeness, Spec §FR-012] + +## Requirement Clarity + +- [ ] CHK011 — Is "within 3 seconds" latency (FR-002/NFR-002) measured from radio receipt, BLE delivery, or repository emission? [Clarity, Spec §NFR-002] +- [ ] CHK012 — Is "high-priority banner" (FR-005) defined with specific CAL Banner priority level and duration? [Clarity, Spec §FR-005] +- [ ] CHK013 — Is "signal quality indicator" quantified — specific icon set, numeric dBm ranges, or named levels (excellent/good/fair/poor)? [Clarity, Spec §FR-007] +- [ ] CHK014 — Is "< 10% battery drain" measured under defined conditions (screen brightness, BLE activity, message frequency)? [Clarity, Spec §NFR-003] +- [ ] CHK015 — Is "visually distinguished" for offline nodes defined with specific styling (opacity, icon, sort order)? [Clarity, Spec §US-3 Scenario 3] +- [ ] CHK016 — Is "distinct color treatment" for emergency banners specified with concrete color values or semantic tokens? [Clarity, Spec §US-2 Scenario 1] +- [ ] CHK017 — Is "6+ nodes visible simultaneously without scrolling" dependent on a specific screen density or display size? [Clarity, Spec §SC-003] +- [ ] CHK018 — Is "configurable template responses" clear on who configures them, where they're stored, and defaults? [Clarity, Spec §FR-004] +- [ ] CHK019 — Is Car API Level 8 minimum clearly justified — which specific 1.9.0 APIs require it? [Clarity, Spec §NFR-004] + +## Requirement Consistency + +- [ ] CHK020 — Do FR-009 (PlaceListMapTemplate under POI) and Non-Goals (NAVIGATION deferred to v2) consistently align with map update latency requirement SC-009 (< 5s)? [Consistency, Spec §FR-009, §SC-009] +- [ ] CHK021 — Are voice input requirements consistent between US-1 (reply), US-7 (in-context), and FR-003 (primary method)? [Consistency] +- [ ] CHK022 — Does "no parked-mode differentiation" (Clarifications) conflict with any acceptance scenario implying driving-only behavior? [Consistency, Spec §Edge Cases] +- [ ] CHK023 — Are emergency handling requirements consistent between FR-005 (banner), FR-006 (spotlight), and US-2 (all scenarios)? [Consistency] +- [ ] CHK024 — Is the shared BLE connection assumption consistent with CarAppService lifecycle — what happens when phone app is force-stopped? [Consistency, Spec §Assumptions] +- [ ] CHK025 — Are "within 1 second" requirements (FR-005 emergency, SC-006 channel switch) measured consistently with NFR-002's 3-second messaging latency? [Consistency] + +## Acceptance Criteria Quality + +- [ ] CHK026 — Is SC-008 ("95% voice replies succeed") measurable without defining what "success" means (sent vs. accurately transcribed vs. delivered)? [Measurability, Spec §SC-008] +- [ ] CHK027 — Is SC-010 ("zero crashes in 2-hour session") sufficient as a release gate — what about ANRs, OOM, or session drops? [Measurability, Spec §SC-010] +- [ ] CHK028 — Is SC-007 ("passes Android Auto App Quality review") measurable before actual store submission? [Measurability, Spec §SC-007] +- [ ] CHK029 — Is SC-001 ("15 seconds total interaction time") measured from notification appearance or screen wake? [Measurability, Spec §SC-001] +- [ ] CHK030 — Are acceptance scenarios for US-5 (map) testable on DHU given DHU's limited map rendering capabilities? [Measurability, Spec §US-5] + +## Scenario Coverage + +- [ ] CHK031 — Are requirements defined for initial onboarding flow when no radio is paired? [Coverage, Gap] +- [ ] CHK032 — Are requirements specified for behavior when Android Auto host disconnects mid-session (cable pull, Bluetooth drop)? [Coverage, Gap] +- [ ] CHK033 — Are requirements defined for multi-device scenario (phone switches between two radios)? [Coverage, Gap] +- [ ] CHK034 — Are requirements specified for app behavior during phone call interruption on the head unit? [Coverage, Gap] +- [ ] CHK035 — Are requirements defined for Screen refresh/invalidation cadence (how often templates re-render)? [Coverage, Gap] +- [ ] CHK036 — Are data freshness requirements defined for cached messages shown during disconnection? [Coverage, Spec §FR-015] +- [ ] CHK037 — Are requirements specified for ConversationItem threading — flat list or grouped by conversation? [Coverage, Spec §FR-002] + +## Edge Case Coverage + +- [ ] CHK038 — Is behavior defined when PlaceListMapTemplate's item limit is reached (max 6 items per CAL docs)? [Edge Case, Spec §FR-009] +- [ ] CHK039 — Is behavior defined when a channel has zero messages (empty state for messaging screen per channel)? [Edge Case, Gap] +- [ ] CHK040 — Are requirements defined for handling very long node names that exceed Condensed Item text bounds? [Edge Case, Spec §FR-007] +- [ ] CHK041 — Is behavior defined when voice recognition returns empty/null result or times out? [Edge Case, Spec §FR-003] +- [ ] CHK042 — Is behavior defined for rapid consecutive emergency alerts from multiple nodes? [Edge Case, Spec §Edge Cases] +- [ ] CHK043 — Are requirements defined for handling GPS-less nodes on the map screen (nodes without position data)? [Edge Case, Spec §FR-009] +- [ ] CHK044 — Is behavior defined when the message being composed via voice exceeds mesh packet size limit (228 bytes)? [Edge Case, Gap] +- [ ] CHK045 — Is behavior defined when Minimized Control Panel data sources become stale (BLE connected but no mesh traffic)? [Edge Case, Spec §FR-010] + +## Non-Functional Requirements + +- [ ] CHK046 — Are memory usage requirements specified for the car module (AAOS devices may have constrained RAM)? [NFR, Gap] +- [ ] CHK047 — Are cold-start performance requirements defined for CarAppService (time from launch to first screen rendered)? [NFR, Gap] +- [ ] CHK048 — Are requirements specified for Crashlytics `car_session` key format, lifecycle (set/clear), and what constitutes a "session"? [NFR, Spec §NFR-009] +- [ ] CHK049 — Are ProGuard/R8 keep rules requirements documented for the car module (CAL uses reflection for template inflation)? [NFR, Gap] +- [ ] CHK050 — Are requirements defined for handling Android Auto's 10-second ANR threshold on the main thread? [NFR, Gap] +- [ ] CHK051 — Is backward-compatibility behavior specified for hosts below Car API Level 8 (graceful absence vs. crash vs. fallback)? [NFR, Spec §NFR-004, §Assumptions] +- [ ] CHK052 — Are requirements specified for process priority / foreground service behavior to keep BLE alive when phone app is backgrounded? [NFR, Gap] + +## Dependencies & Assumptions + +- [ ] CHK053 — Is the alpha stability risk (1.9.0-alpha01) quantified with a fallback plan if APIs change before stable release? [Assumption, Spec §Assumptions] +- [ ] CHK054 — Is the assumption "users configure channels on phone first" validated — what if a user only has AAOS with no phone? [Assumption, Spec §Assumptions] +- [ ] CHK055 — Are Play Store review requirements for MESSAGING category documented (conversation API compliance, notification delegation)? [Dependency, Gap] +- [ ] CHK056 — Is the relationship with AppFunctions feature clearly bounded — are there shared components or only independent parallel features? [Dependency, Spec §Clarifications] +- [ ] CHK057 — Are DHU and Automotive Emulator API 35-ext15 testing environment requirements documented as a verification prerequisite? [Dependency, Gap] +- [ ] CHK058 — Is the Koin Application-scoped BleConnectionManager's threading model documented (which dispatcher, coroutine scope)? [Assumption, Spec §Architecture] + +## Distribution & Build Integration + +- [ ] CHK059 — Are Play Store listing requirements specified for the car app (screenshots, description, category metadata)? [Completeness, Gap] +- [ ] CHK060 — Are internal/closed testing track progression criteria defined (when to promote from internal → closed → open → production)? [Completeness, Gap] +- [ ] CHK061 — Is the manifest merger strategy documented for adding `` and `` entries only in the google flavor? [Completeness, Gap] +- [ ] CHK062 — Are automotive-specific permission requirements documented (e.g., `androidx.car.app.ACCESS_SURFACE`)? [Completeness, Gap] + +## Cross-Artifact Consistency + +- [ ] CHK063 — Do architecture component names in spec match planned module/package structure in plan.md? [Consistency] +- [ ] CHK064 — Are all 7 user stories reflected as distinct implementation tasks in tasks.md? [Consistency] +- [ ] CHK065 — Do NFR metrics (latency, battery, build time) have corresponding verification methods defined? [Traceability] + +## Notes + +- Check items off as they are resolved (requirement clarified, gap filled, or explicitly marked N/A) +- Items marked [Gap] indicate missing requirements that should be added to spec.md +- Items marked [Assumption] should be validated or converted to explicit requirements +- 80%+ items include traceability references to spec sections or gap markers diff --git a/specs/20260521-153452-car-app-library-integration/checklists/requirements.md b/specs/20260521-153452-car-app-library-integration/checklists/requirements.md new file mode 100644 index 000000000..00c1aff75 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Car App Library Integration + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-21 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass validation. Spec is ready for `/speckit.clarify` or `/speckit.plan`. +- Architecture section references module paths and component names for planning context — these describe *what* exists, not *how* to implement. +- Alpha library risk explicitly acknowledged in Assumptions section per user directive. diff --git a/specs/20260521-153452-car-app-library-integration/contracts/car-app-service.md b/specs/20260521-153452-car-app-library-integration/contracts/car-app-service.md new file mode 100644 index 000000000..dae0c52f1 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/contracts/car-app-service.md @@ -0,0 +1,230 @@ +# Car App Service Contract + +**Feature**: Car App Library Integration +**Date**: 2026-05-21 + +## Service Declaration + +The `MeshtasticCarAppService` is the entry point for Android Auto and AAOS hosts. + +### AndroidManifest.xml Contract + +```xml + + + + + + + +``` + +### Categories + +| Category | Purpose | Justification | +|----------|---------|---------------| +| `MESSAGING` | Primary — enables ConversationItem, voice reply | Core use case: read/reply to mesh messages | +| `POI` | Secondary — enables PlaceListMapTemplate | Node map with static pins (not navigation) | + +### Car API Level + +```xml + +``` + +Car API Level 8 is required for: +- Spotlight Sections +- Condensed Items +- Minimized Control Panel +- Banners +- Chips +- Section Headers +- Expanded Header Layout + +Hosts below API Level 8 will not display the app (graceful absence). + +## Session Contract + +### MeshtasticCarSession + +```kotlin +class MeshtasticCarSession(private val sessionInfo: SessionInfo) : Session() { + + override fun onCreateScreen(intent: Intent): Screen + // Returns: HomeScreen (tab-based root) + // Side effects: + // - Sets Crashlytics "car_session" custom key + // - Starts collecting emergency message flow + // - Registers MeshStatusPanel + + override fun onNewIntent(intent: Intent) + // Handles deep links (e.g., open specific conversation from notification) + + override fun onCarConfigurationChanged(newConfiguration: Configuration) + // Handles theme/density changes (dark mode, etc.) +} +``` + +### Screen Stack Contract + +``` +HomeScreen (root, never popped) + ├── MessagingScreen (tab 1) + │ └── ConversationScreen (push on conversation tap) + ├── NodeDashboardScreen (tab 2) + │ └── NodeDetailScreen (push on node tap) + └── MapScreen (tab 3) + └── NodeDetailScreen (push on map item tap) +``` + +Maximum screen depth: 3 (compliant with CAL template depth limits). + +## Template Contracts + +### HomeScreen → TabTemplate (proposed, falls back to ListTemplate if tabs unavailable) + +``` +TabTemplate { + tabs: [ + Tab("Messages", messagingIcon), + Tab("Nodes", nodeIcon), + Tab("Map", mapIcon), + ] + headerAction: Action.APP_ICON +} +``` + +### MessagingScreen → ListTemplate with Chips + Spotlight Section + +``` +ListTemplate { + header: Header { + title: "Messages" + chipActions: [ChannelChip(name, unreadBadge) for each channel] + } + spotlightSection: SpotlightSection { // Only if activeEmergencies.isNotEmpty() + items: [emergencyConversationItems...] + } + sections: [ + SectionHeader("Channel: {name}"), + ConversationItem(name, lastMessage, time, unread) for each conversation + ] +} +``` + +### ConversationScreen → MessageTemplate / ListTemplate + +``` +MessageTemplate { + // For the selected conversation + messages: [MessageItem(text, sender, time) ...] + actions: [ + Action("Reply", voiceIcon) → triggers CAL voice input + Action("Quick Reply", listIcon) → shows quick-reply list + Action("Read Aloud", speakerIcon) → triggers TTS + ] +} +``` + +### NodeDashboardScreen → ListTemplate with Expanded Header + Condensed Items + +``` +ListTemplate { + header: ExpandedHeader { + title: "Mesh Network" + subtitle: "{onlineNodes}/{totalNodes} nodes online" + image: meshTopologyIcon + } + items: [ + CondensedItem( + title: node.longName, + subtitle: "Signal: {quality} • Battery: {percent}%", + image: signalIcon(quality), + onClickListener: → push NodeDetailScreen + ) for each node, sorted online-first + ] +} +``` + +### NodeDetailScreen → PaneTemplate + +``` +PaneTemplate { + title: node.longName + pane: Pane { + rows: [ + Row("Last Heard", formatTimeAgo(node.lastHeard)), + Row("Distance", formatDistance(distanceMeters)), + Row("Hardware", node.hwModel.name), + Row("Battery", "${node.batteryPercent}%"), + Row("Signal", formatSnr(node.snr)), + ] + actions: [ + Action("Message", messageIcon) → push ConversationScreen for DM + ] + } +} +``` + +### MapScreen → PlaceListMapTemplate + +``` +PlaceListMapTemplate { + title: "Node Map" + itemList: ItemList { + items: [ + Row( + title: node.name, + text: "Updated {timeAgo} • {distanceFormatted}", + metadata: Place(LatLng(lat, lng)), + onClickListener: → push NodeDetailScreen + ) for each node with position + ] + } + anchor: LatLng(ownLat, ownLng) // if own position available + isCurrentLocationEnabled: true +} +``` + +### MeshStatusPanel → Minimized Control Panel + +``` +// Attached to Session, visible across all screens +MinimizedControlPanel { + icon: connectionStatusIcon + title: "{onlineNodeCount} nodes online" + subtitle: "Last msg: {timeAgo}" + onClickListener: → expand to full detail panel +} +``` + +### Emergency Banner + +``` +// Triggered by EmergencyHandler when emergency packet received +AppManager.showAlert( + Alert { + title: "⚠️ EMERGENCY" + subtitle: "{senderName}: {messagePreview}" + icon: emergencyIcon + actions: [Action("View", → push emergency detail)] + duration: Alert.DURATION_LONG + } +) +``` + +## Error Contracts + +| Condition | Behavior | +|-----------|----------| +| BLE disconnected | Banner shown; screens degrade to cached data (read-only) | +| No channels configured | Show onboarding PaneTemplate directing to phone app | +| No nodes in range | Empty state in NodeDashboard: "No nodes heard" | +| No positions available | MapScreen shows empty map with "No positions reported" | +| Template item limit exceeded | Paginate with "Load more" action row | +| Voice input fails | Fall back to quick-reply template list | +| Session crash | Crashlytics captures with `car_session` tag; session restarts cleanly | diff --git a/specs/20260521-153452-car-app-library-integration/contracts/manifest-declarations.md b/specs/20260521-153452-car-app-library-integration/contracts/manifest-declarations.md new file mode 100644 index 000000000..d1f1d1306 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/contracts/manifest-declarations.md @@ -0,0 +1,133 @@ +# Manifest Declarations Contract + +**Feature**: Car App Library Integration +**Date**: 2026-05-21 + +## feature/car/src/main/AndroidManifest.xml + +```xml + + + + + + + + + + + + + + + + + +``` + +## AAOS Support: automotive_app_desc.xml + +Located at `feature/car/src/main/res/xml/automotive_app_desc.xml`: + +```xml + + + + +``` + +## androidApp Manifest Additions (google flavor only) + +In `androidApp/src/google/AndroidManifest.xml` (or merged automatically via manifest merger): + +```xml + +``` + +## Gradle Dependency Declaration + +In `androidApp/build.gradle.kts`: + +```kotlin +dependencies { + // Car module (google flavor only - CAL requires Play Services) + "googleImplementation"(projects.feature.car) +} +``` + +In `settings.gradle.kts` (new include): + +```kotlin +include(":feature:car") +``` + +## Version Catalog Additions (gradle/libs.versions.toml) + +```toml +[versions] +car-app = "1.9.0-alpha01" + +[libraries] +androidx-car-app = { module = "androidx.car.app:app", version.ref = "car-app" } +androidx-car-app-projected = { module = "androidx.car.app:app-projected", version.ref = "car-app" } +androidx-car-app-automotive = { module = "androidx.car.app:app-automotive", version.ref = "car-app" } +androidx-car-app-testing = { module = "androidx.car.app:app-testing", version.ref = "car-app" } +``` + +## feature/car/build.gradle.kts + +```kotlin +plugins { + alias(libs.plugins.meshtastic.android.library) + alias(libs.plugins.meshtastic.android.library.flavors) + alias(libs.plugins.meshtastic.koin) +} + +android { + namespace = "org.meshtastic.feature.car" + + defaultConfig { + minSdk = 23 // Android Auto projection minimum + } +} + +dependencies { + implementation(projects.core.common) + implementation(projects.core.data) + implementation(projects.core.domain) + implementation(projects.core.model) + implementation(projects.core.repository) + implementation(projects.core.ble) + + implementation(libs.androidx.car.app) + implementation(libs.androidx.car.app.projected) + + implementation(libs.koin.android) + implementation(libs.koin.annotations) + + implementation(libs.firebase.crashlytics) + + testImplementation(libs.androidx.car.app.testing) + testImplementation(libs.koin.test) + testImplementation(kotlin("test")) +} +``` + +## Permissions + +No additional permissions required. The car module: +- Does NOT request `BLUETOOTH` permissions (handled by `core/ble` at the app level) +- Does NOT request location permissions (handled by existing app permissions) +- Does NOT request microphone permissions (CAL voice input is delegated to the system) + +## ProGuard / R8 Rules + +```proguard +# Car App Library service must not be obfuscated (resolved by exported service) +-keep class org.meshtastic.feature.car.service.MeshtasticCarAppService { *; } +``` diff --git a/specs/20260521-153452-car-app-library-integration/data-model.md b/specs/20260521-153452-car-app-library-integration/data-model.md new file mode 100644 index 000000000..f47963efb --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/data-model.md @@ -0,0 +1,226 @@ +# Data Model: Car App Library Integration + +**Feature**: Car App Library Integration +**Date**: 2026-05-21 + +## Overview + +The car module introduces **no new persistent entities**. All data is consumed from existing `core/` repositories. This document defines the **presentation state models** and **UI state containers** used within the car module to bridge repository data to CAL templates. + +## Existing Entities (consumed, not modified) + +### Node (core/model) +| Field | Type | Car Usage | +|-------|------|-----------| +| `num` | `Int` | Unique identifier, key for node DB | +| `user.id` | `String` | User ID (e.g., "!1234abcd") | +| `user.longName` | `String` | Display name in Condensed Items | +| `user.shortName` | `String` | Abbreviated name for compact views | +| `user.hwModel` | `HardwareModel` | Shown in node detail | +| `position.latitude` | `Double` | Map pin latitude | +| `position.longitude` | `Double` | Map pin longitude | +| `position.time` | `Int` | Last position update epoch | +| `lastHeard` | `Int` | Last communication epoch | +| `snr` | `Float` | Signal-to-noise ratio display | +| `deviceMetrics.batteryLevel` | `Int?` | Battery indicator | +| `isFavorite` | `Boolean` | Priority in node list | + +### DataPacket (core/model) +| Field | Type | Car Usage | +|-------|------|-----------| +| `from` | `String` | Sender identifier | +| `to` | `String` | Destination identifier | +| `channel` | `Int` | Channel index for grouping | +| `bytes` | `ByteArray?` | Message content | +| `dataType` | `Int` | Message type classification | +| `time` | `Long` | Timestamp for display | +| `id` | `Int` | Unique packet ID | +| `status` | `MessageStatus` | Delivery status indicator | + +### QuickChatAction (core/database) +| Field | Type | Car Usage | +|-------|------|-----------| +| `uuid` | `Long` | Unique ID | +| `name` | `String` | Display label for quick-reply button | +| `message` | `String` | Text to send when tapped | +| `mode` | `Int` | Instant vs append mode | +| `position` | `Int` | Sort order | + +### MyNodeInfo (core/model) +| Field | Type | Car Usage | +|-------|------|-----------| +| `myNodeNum` | `Int` | Our node number | +| `firmwareVersion` | `String?` | Display in expanded status panel | +| `model` | `String?` | Hardware model display | + +## Presentation State Models (new, car module only) + +### CarSessionState + +Top-level state for a car session lifecycle. + +```kotlin +data class CarSessionState( + val connectionStatus: ConnectionStatus, + val onlineNodeCount: Int, + val lastMessageTime: Long?, // epoch millis, null if no messages + val activeEmergencies: List, + val meshName: String?, +) + +enum class ConnectionStatus { + CONNECTED, + CONNECTING, + DISCONNECTED, +} +``` + +**Source**: Derived from `BleConnectionState`, `NodeRepository.onlineNodeCount`, `PacketRepository` + +### MessagingUiState + +State for the messaging screen template builder. + +```kotlin +data class MessagingUiState( + val channels: List, + val selectedChannelIndex: Int, + val conversations: List, + val emergencySpotlight: List?, +) + +data class ChannelUi( + val index: Int, + val name: String, + val unreadCount: Int, +) + +data class ConversationUi( + val contactKey: String, + val displayName: String, + val lastMessage: String, + val lastMessageTime: Long, + val unreadCount: Int, + val isEmergency: Boolean, +) +``` + +**Source**: `PacketRepository.getContacts()`, `PacketRepository.getUnreadCountFlow()`, channel config from radio + +### NodeDashboardUiState + +State for the node dashboard condensed items grid. + +```kotlin +data class NodeDashboardUiState( + val nodes: List, + val topologyHeader: TopologyHeader, +) + +data class NodeUi( + val nodeNum: Int, + val longName: String, + val shortName: String, + val signalQuality: SignalQuality, + val batteryPercent: Int?, + val isOnline: Boolean, + val lastHeard: Long, + val hasPosition: Boolean, +) + +enum class SignalQuality { EXCELLENT, GOOD, FAIR, POOR, UNKNOWN } + +data class TopologyHeader( + val totalNodes: Int, + val onlineNodes: Int, + val meshName: String?, +) +``` + +**Source**: `NodeRepository.nodeDBbyNum`, `NodeRepository.onlineNodeCount` + +### MapUiState + +State for the PlaceListMapTemplate. + +```kotlin +data class MapUiState( + val places: List, + val ownPosition: LatLngWrapper?, +) + +data class NodePlace( + val nodeNum: Int, + val name: String, + val latitude: Double, + val longitude: Double, + val lastUpdateTime: Long, + val distanceMeters: Float?, // from own position, null if own position unknown +) + +data class LatLngWrapper( + val latitude: Double, + val longitude: Double, +) +``` + +**Source**: `NodeRepository.nodeDBbyNum` filtered to nodes with valid positions + +### EmergencyAlert + +Model for emergency messages requiring banner treatment. + +```kotlin +data class EmergencyAlert( + val packetId: Int, + val senderName: String, + val senderNodeNum: Int, + val message: String, + val timestamp: Long, + val latitude: Double?, + val longitude: Double?, + val acknowledged: Boolean, +) +``` + +**Source**: `PacketRepository` flow filtered by emergency message type/priority + +## State Transitions + +### Car Session Lifecycle + +``` +[App Not Visible] → onCreateScreen() → [Active Session] + ↓ ↓ + ↓ Screens pushed/popped via ScreenManager + ↓ ↓ +[App Not Visible] ← onDestroy() ← [Active Session] +``` + +### Connection Status + +``` +DISCONNECTED → (BLE scan + connect) → CONNECTING → (handshake complete) → CONNECTED + ↑ | + └──────────────────── (link lost / timeout) ──────────────────────────────┘ +``` + +### Emergency Alert Flow + +``` +[Message received] → (priority == EMERGENCY?) → YES → Add to activeEmergencies + → Show Banner + → Play notification sound + → NO → Normal message flow +``` + +## Validation Rules + +| Rule | Enforcement | +|------|-------------| +| Node name display ≤ 30 chars | Truncated by CAL host automatically | +| Message content ≤ 300 chars in list | Truncate with "…"; full on tap/TTS | +| Channel name ≤ 12 chars for Chip | Truncated with "…" | +| Max 6 conversations visible | CAL template item limit; paginate | +| Map pins require valid lat/lng | Filter nodes without position | +| Emergency banner requires non-empty message | Skip silent emergency packets | diff --git a/specs/20260521-153452-car-app-library-integration/plan.md b/specs/20260521-153452-car-app-library-integration/plan.md new file mode 100644 index 000000000..2f0a71ec5 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/plan.md @@ -0,0 +1,134 @@ +# Implementation Plan: Car App Library Integration + +**Branch**: `feature/20260521-153452-car-app-library-integration` | **Date**: 2026-05-21 | **Spec**: [spec.md](spec.md) + +**Input**: Feature specification from `specs/20260521-153452-car-app-library-integration/spec.md` + +## Summary + +Integrate Android Car App Library 1.9.0-alpha01 into Meshtastic-Android as a new `feature/car` module, delivering a complete automotive mesh radio interface with 7 screens (messaging, node dashboard, channel management, emergency alerts, map, quick actions, mesh status panel). The module is Android-only, reuses all existing `core/` business logic via Koin DI, and leverages CAL's template-based rendering (no Compose). Voice reply uses CAL's built-in ConversationItem voice input; system-level "Hey Google" commands are handled separately by the AppFunctions feature. + +## Technical Context + +**Language/Version**: Kotlin 2.3+ targeting JDK 21, Car API Level 8+ + +**Primary Dependencies**: `androidx.car.app:app:1.9.0-alpha01`, `androidx.car.app:app-projected:1.9.0-alpha01`, `androidx.car.app:app-automotive:1.9.0-alpha01`, Koin 4.2.1 (Koin Annotations + K2 Plugin), Firebase Crashlytics (BOM 34.13.0) + +**Storage**: Room KMP (existing), DataStore KMP (existing) — no new storage + +**Testing**: `./gradlew :feature:car:testGoogleDebugUnitTest` (Android-only module), `androidx.car.app:app-testing:1.9.0-alpha01` for host simulation, Robolectric for unit tests + +**Target Platform**: Android Auto (projection, API 23+) and AAOS (embedded), Car API Level 8 minimum + +**Project Type**: Mobile app — new Android-only feature module within KMP project + +**Performance Goals**: Message display latency ≤ 3s, emergency banner ≤ 1s, channel switch ≤ 1s, map pin update ≤ 5s + +**Constraints**: ≤ 2 taps for all primary actions, < 10% battery overhead, zero crashes/ANRs in 2-hour sessions, `google` flavor only + +**Scale/Scope**: 7 car screens, ~15-20 new source files, 1 new Gradle module, 0 changes to existing modules' APIs + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- **I. Kotlin Multiplatform Core**: ✅ PASS — No `commonMain` changes. All new code resides in `feature/car/src/main/` (Android-only module). Business logic is consumed from existing `core/repository`, `core/data`, `core/domain`, `core/ble` KMP modules via their public interfaces. No new business logic is introduced in the car module — it is purely a presentation layer adapting existing repositories to CAL templates. + +- **II. Zero Lint Tolerance**: ✅ PASS — Will run: + - `./gradlew :feature:car:spotlessApply :feature:car:spotlessCheck` + - `./gradlew :feature:car:detekt` + - Module is Android-only so uses standard detekt tasks (not KMP variants) + +- **III. Compose Multiplatform UI**: ✅ N/A — Car App Library uses its own template-based rendering system, not Compose. No `@Composable` functions are introduced. `MeshtasticNavDisplay` and `NavigationBackHandler` do not apply to CAL's `ScreenManager` navigation. No floats displayed (all text pre-formatted by existing `MetricFormatter`/`NumberFormatter` in core modules). + +- **IV. Privacy First**: ✅ PASS — No new data collection or network calls. Reuses existing repositories with their privacy controls. Location data on map uses existing user-opt-in position sharing. No PII/keys in logs. Crashlytics tagging uses session ID only (no PII). `core/proto` submodule not modified. + +- **V. Design Standards Compliance**: ✅ N/A (justified) — CAL apps use automotive-specific template design language enforced by the Android Auto host, not the Meshtastic Client Design Standards which target phone/desktop Compose UI. The host enforces readability (font sizes, item limits, distraction guidelines). Cross-Platform Spec field is N/A because CAL is Android-only with no cross-platform equivalent. Emergency alert visual treatment follows NHTSA Phase 2 automotive HMI guidelines via CAL Banner APIs. + +- **VI. Verify Before Push**: ✅ Commands recorded: + ```bash + # Local verification + ./gradlew spotlessApply :feature:car:spotlessCheck :feature:car:detekt :feature:car:testGoogleDebugUnitTest + + # Post-push CI check + gh pr checks || gh run list --branch feature/20260521-153452-car-app-library-integration --limit 5 + ``` + +## Project Structure + +### Documentation (this feature) + +```text +specs/20260521-153452-car-app-library-integration/ +├── plan.md # This file +├── research.md # Phase 0: CAL API research, architecture decisions +├── data-model.md # Phase 1: Entities and state models +├── quickstart.md # Phase 1: Developer onboarding guide +├── contracts/ # Phase 1: CAL service contracts and manifest declarations +│ ├── car-app-service.md +│ └── manifest-declarations.md +└── tasks.md # Phase 2 output (/speckit.tasks command) +``` + +### Source Code (repository root) + +```text +feature/car/ +├── build.gradle.kts # Android-only library, google flavor only +├── src/ +│ ├── main/ +│ │ ├── AndroidManifest.xml # CarAppService declaration, categories +│ │ ├── kotlin/org/meshtastic/feature/car/ +│ │ │ ├── di/ +│ │ │ │ └── FeatureCarModule.kt # Koin module for car DI +│ │ │ ├── service/ +│ │ │ │ ├── MeshtasticCarAppService.kt # CarAppService entry point +│ │ │ │ └── MeshtasticCarSession.kt # Session lifecycle, screen manager +│ │ │ ├── screens/ +│ │ │ │ ├── HomeScreen.kt # Tab-based entry (messaging, nodes, map) +│ │ │ │ ├── MessagingScreen.kt # ConversationItem list, channel chips +│ │ │ │ ├── ConversationScreen.kt # Single conversation with voice reply +│ │ │ │ ├── NodeDashboardScreen.kt # Condensed Items node grid +│ │ │ │ ├── NodeDetailScreen.kt # Expanded node info +│ │ │ │ ├── MapScreen.kt # PlaceListMapTemplate +│ │ │ │ └── ChannelManagementScreen.kt # Channel selection/switching +│ │ │ ├── alerts/ +│ │ │ │ └── EmergencyHandler.kt # Banner management for emergencies +│ │ │ ├── panels/ +│ │ │ │ └── MeshStatusPanel.kt # Minimized Control Panel +│ │ │ └── util/ +│ │ │ ├── CrashlyticsCarTagger.kt # car_session key tagging +│ │ │ └── TemplateBuilders.kt # Helper extensions for CAL templates +│ │ └── res/ +│ │ ├── values/ +│ │ │ └── strings.xml # Car-specific strings +│ │ └── xml/ +│ │ └── automotive_app_desc.xml # AAOS app description +│ └── test/ +│ └── kotlin/org/meshtastic/feature/car/ +│ ├── service/ +│ │ └── MeshtasticCarSessionTest.kt +│ ├── screens/ +│ │ ├── MessagingScreenTest.kt +│ │ ├── NodeDashboardScreenTest.kt +│ │ └── MapScreenTest.kt +│ └── alerts/ +│ └── EmergencyHandlerTest.kt + +# Existing modules (consumed, NOT modified): +core/repository/ # PacketRepository, NodeRepository, QuickChatActionRepository, SendMessageUseCase +core/data/ # NodeRepositoryImpl, PacketRepositoryImpl +core/ble/ # BleConnection (Application-scoped singleton) +core/model/ # Node, DataPacket, MyNodeInfo, etc. +core/domain/ # Use cases (SendMessageUseCase, etc.) +``` + +**Structure Decision**: New `feature/car` module as an Android-only library (not KMP). Follows existing feature module pattern but uses `AndroidLibraryFlavorsConventionPlugin` instead of KMP plugin since CAL has no multiplatform support. Only the `google` flavor includes this module (mirrors Maps/Crashlytics flavor split). + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| III. Compose Multiplatform UI — N/A | CAL uses proprietary template system, not Compose | Cannot render Compose inside automotive templates; CAL enforces distraction-safe UI via templates exclusively | +| V. Design Standards — N/A | Automotive design is governed by NHTSA + host-enforced constraints | Meshtastic Design Standards target phone/desktop Compose; applying them to CAL templates would conflict with automotive safety requirements | +| Android-only module in KMP project | CAL SDK is Android-exclusive | No KMP equivalent exists; all business logic remains in `commonMain` — only the thin presentation adapter is platform-specific | diff --git a/specs/20260521-153452-car-app-library-integration/quickstart.md b/specs/20260521-153452-car-app-library-integration/quickstart.md new file mode 100644 index 000000000..7def3bb44 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/quickstart.md @@ -0,0 +1,150 @@ +# Quickstart: Car App Library Integration + +**Feature**: Car App Library Integration +**Date**: 2026-05-21 + +## Prerequisites + +- Android Studio Ladybug or newer (for CAL preview tools) +- JDK 21 (`JAVA_HOME` set) +- `ANDROID_HOME` set with API 35+ SDK installed +- Proto submodule initialized: `git submodule update --init` +- `local.properties` configured: `cp secrets.defaults.properties local.properties` +- Android Auto Desktop Head Unit (DHU) installed via SDK Manager → SDK Tools → Android Auto Desktop Head Unit + +## Setup + +### 1. Sync and Build + +```bash +# Full sync (includes new :feature:car module) +./gradlew sync + +# Build google flavor (required — car module is google-only) +./gradlew assembleGoogleDebug +``` + +### 2. Install DHU for Testing + +The Desktop Head Unit simulates Android Auto on your development machine. + +```bash +# Install via SDK Manager (or command line) +sdkmanager "extras;google;auto" + +# Start DHU (after connecting a device/emulator with the app installed) +$ANDROID_HOME/extras/google/auto/desktop-head-unit +``` + +### 3. Run on Android Auto (Projection Mode) + +1. Install the google debug build on a physical device: `./gradlew installGoogleDebug` +2. Enable Developer Mode in Android Auto settings on the phone +3. Start the DHU: `desktop-head-unit` +4. The Meshtastic car app appears in the DHU's app launcher under "Messaging" category + +### 4. Run on AAOS Emulator + +```bash +# Create AAOS emulator (API 33+ automotive system image) +avdmanager create avd -n "AAOS_Test" -k "system-images;android-33;google_apis_playstore;x86_64" --device "automotive_1024p_landscape" + +# Start emulator +emulator -avd AAOS_Test + +# Install +./gradlew installGoogleDebug +``` + +## Development Workflow + +### Module Location + +All car-specific code lives in `feature/car/`: + +``` +feature/car/src/main/kotlin/org/meshtastic/feature/car/ +├── di/ → Koin DI module +├── service/ → CarAppService + Session +├── screens/ → CAL Screen implementations +├── alerts/ → Emergency banner handler +├── panels/ → Minimized Control Panel +└── util/ → Helpers (Crashlytics tagger, template builders) +``` + +### Key Development Patterns + +**Screen implementation**: +```kotlin +class MessagingScreen(carContext: CarContext) : Screen(carContext) { + // Inject repositories via Koin + private val packetRepository: PacketRepository by inject() + + override fun onGetTemplate(): Template { + // Build template from current state + // Call invalidate() when data changes to trigger re-render + } +} +``` + +**Data observation** (CAL doesn't use Compose — use coroutine collection): +```kotlin +// In Screen's lifecycle, collect flows and call invalidate() +lifecycleScope.launch { + repository.getContacts().collect { contacts -> + cachedContacts = contacts + invalidate() // Triggers onGetTemplate() re-call + } +} +``` + +**Template refresh**: CAL screens are invalidated manually — no reactive binding. Call `invalidate()` whenever backing data changes. + +### Testing + +```bash +# Unit tests (uses androidx.car.app:app-testing) +./gradlew :feature:car:testGoogleDebugUnitTest + +# Lint + formatting +./gradlew :feature:car:spotlessApply :feature:car:spotlessCheck :feature:car:detekt +``` + +**Test approach**: Use `SessionController` and `TestCarContext` from `app-testing` artifact to simulate host interactions without a real car/DHU. + +```kotlin +@Test +fun `messaging screen shows conversations`() { + val controller = SessionController( + MeshtasticCarSession(testSessionInfo), + TestCarContext(ApplicationProvider.getApplicationContext()) + ) + // Push screen, assert template content +} +``` + +### Debugging + +- **CAL Logcat filter**: `tag:CarApp OR tag:CarService` +- **Template errors**: CAL validates templates at runtime — check logcat for `TemplateValidationException` +- **Screen stack**: Use `ScreenManager.getTop()` to inspect current screen +- **Crashlytics**: Filter by `car_session` custom key in Firebase Console + +## Common Tasks + +| Task | Command / Action | +|------|------------------| +| Add a new screen | Create `Screen` subclass in `screens/`, register in navigation | +| Add a CAL dependency | Update `gradle/libs.versions.toml` + `feature/car/build.gradle.kts` | +| Test with DHU | `desktop-head-unit` after installing google debug build | +| Check template compliance | Run app on DHU; host validates template constraints | +| Filter car crashes | Firebase Console → Crashlytics → Filter: `car_session` is not empty | +| Full verification | `./gradlew spotlessApply :feature:car:spotlessCheck :feature:car:detekt :feature:car:testGoogleDebugUnitTest` | + +## Architecture Notes + +- **No Compose**: CAL uses its own template-based rendering. Don't mix Compose APIs. +- **No `commonMain`**: This is an Android-only module. All code in `src/main/kotlin/`. +- **Shared BLE**: Don't create new BLE connections. Inject existing `BleConnection` singleton. +- **Koin DI**: All core repositories are already in the graph. Just `inject()` them. +- **Flavor**: Only `google` flavor includes this module. Never reference it from `fdroid` code. diff --git a/specs/20260521-153452-car-app-library-integration/research.md b/specs/20260521-153452-car-app-library-integration/research.md new file mode 100644 index 000000000..e6fb0e748 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/research.md @@ -0,0 +1,164 @@ +# Research: Car App Library Integration + +**Feature**: Car App Library Integration +**Date**: 2026-05-21 + +## R1: Car App Library 1.9.0-alpha01 New Components + +**Decision**: Use all 7 new CAL 1.9.0-alpha01 components as specified + +**Rationale**: The alpha release provides modern automotive UI components that directly map to Meshtastic use cases. The user explicitly accepted alpha risk. + +**Components and their application**: + +| CAL Component | Meshtastic Screen | Purpose | +|---------------|-------------------|---------| +| Spotlight Section | Messaging (emergency) | Emergency messages pinned at top of message list | +| Condensed Items | Node Dashboard | Dense node list showing 6+ nodes without scroll | +| Chips | Messaging (channels) | Channel switching with unread badges | +| Minimized Control Panel | All screens (persistent) | Mesh status: radio connection, node count, last message time | +| Banners | Emergency alerts | Full-screen overlay for emergency broadcasts | +| Section Headers | Messaging | Group messages by channel within conversation list | +| Expanded Header Layout | Node Dashboard | Mesh topology summary at top of node grid | + +**Alternatives considered**: +- Wait for stable 1.9.0 release → Rejected: Timeline unknown; alpha APIs are functionally complete +- Use legacy ListTemplate/MessageTemplate → Rejected: Misses density benefits (Condensed Items) and visual hierarchy (Spotlight/Headers) + +**API Level requirement**: Car API Level 8 (maps to `minCarApiLevel 8` in manifest). Older hosts gracefully hide the app. + +## R2: Module Architecture — Android-Only vs KMP + +**Decision**: Create `feature/car` as an Android-only library module (not KMP) + +**Rationale**: CAL SDK is exclusively Android. Creating a KMP module with only `androidMain` source sets would add unnecessary complexity (empty `commonMain`, unused KMP plugin overhead). The project already has Android-only modules (`core/api`, `core/barcode`, `androidApp`) as precedent. + +**Build plugin**: `AndroidLibraryFlavorsConventionPlugin` (not `KmpLibraryConventionPlugin`) — ensures proper flavor-aware configuration consistent with existing Android-only modules. + +**Alternatives considered**: +- KMP module with `androidMain` only → Rejected: No cross-platform value; KMP plugin adds 2-3s build overhead with zero benefit +- Inline within `androidApp` module → Rejected: Violates separation of concerns; feature modules should be independent + +## R3: BLE Connection Sharing Strategy + +**Decision**: Shared Application-scoped `BleConnection` singleton via Koin, no new connection management + +**Rationale**: The existing `BleConnection` in `core/ble` is already scoped to the Application lifecycle via Koin's singleton scope. When Android Auto starts the `CarAppService`, it runs in the same process as the phone app (projection mode) — the Koin graph is shared naturally. The `CarAppService` keeps the process alive via the Android Auto host binding, ensuring the BLE connection persists. + +**Key implementation detail**: `KableBleConnection` is instantiated by `KableBleConnectionFactory` and held as a Koin singleton. The car module simply injects the same instance — no reconnection logic needed. + +**AAOS (embedded) consideration**: On AAOS, the app runs as a standalone process. The same Koin graph initializes in `Application.onCreate()`. BLE connection management is identical because it's Application-scoped regardless of entry point. + +**Alternatives considered**: +- Dedicated car BLE connection → Rejected: Would conflict with phone app's connection; BLE to Meshtastic radio is single-link +- Service binding to phone app → Rejected: Unnecessary IPC; same process in projection mode; AAOS doesn't have the phone app + +## R4: Crashlytics car_session Tagging + +**Decision**: Tag all Crashlytics events with `car_session` custom key during car session lifecycle + +**Rationale**: Enables filtering car-specific crashes/ANRs in Firebase console without new infrastructure. The `MeshtasticCarSession` sets the key on `onCreateScreen()` and clears on `onDestroy()`. + +**Implementation**: +```kotlin +// In MeshtasticCarSession.onCreateScreen(): +FirebaseCrashlytics.getInstance().setCustomKey("car_session", sessionInfo.sessionId.toString()) + +// In MeshtasticCarSession lifecycle end: +FirebaseCrashlytics.getInstance().setCustomKey("car_session", "") +``` + +**Alternatives considered**: +- Separate Crashlytics instance → Not possible; Firebase is process-wide singleton +- DataDog APM → Rejected: Project uses Crashlytics; DataDog not in dependency graph + +## R5: Messaging via ConversationItem + Voice Reply + +**Decision**: Use `ConversationItem` API with CAL's built-in voice input for reply + +**Rationale**: CAL's `ConversationItem` is purpose-built for messaging apps on Android Auto. It handles: +- Message display with sender avatar, name, timestamp +- Unread indicators +- Voice reply flow (tap → record → send) with no custom speech recognition needed +- Quick-reply suggestions + +The existing `SendMessageUseCase` in `core/repository` accepts `(text, contactKey, replyId)` — the car module calls this directly after voice transcription completes. + +**Data flow**: `ConversationItem.onReply { text -> sendMessageUseCase(text, contactKey) }` + +**Alternatives considered**: +- Custom speech recognition → Rejected: CAL handles this automatically; would duplicate system capabilities +- Google Assistant App Actions → Rejected: Separate concern handled by AppFunctions feature + +## R6: PlaceListMapTemplate for Node Map (POI Category) + +**Decision**: Use `PlaceListMapTemplate` under POI category for static node position display + +**Rationale**: POI category avoids NAVIGATION category requirements (turn-by-turn guidance, active routing), which would trigger additional Play Store review burden and potential conflicts with navigation apps. `PlaceListMapTemplate` renders a map with place items (pins) + a scrollable list — perfect for showing node positions. + +**Implementation approach**: +- Each node with known GPS position becomes a `Place` item with `LatLng` +- List items show node name + distance + last update time +- Map auto-zooms to fit all visible pins +- Tap a list item → NodeDetailScreen with message option +- Refresh interval: 5 seconds (matches NFR map update latency requirement) + +**Limitation**: No live tracking line or animated position updates (NAVIGATION category feature, deferred to v2) + +**Alternatives considered**: +- MapWithContentTemplate + NAVIGATION category → Rejected by spec decision; deferred to v2 +- No map at all → Rejected: Location awareness is core Meshtastic differentiator + +## R7: Koin DI Integration for Car Module + +**Decision**: New `FeatureCarModule` using Koin Annotations, registered in app's module graph + +**Rationale**: Consistent with project's DI pattern. All feature modules declare a Koin module that is included by the `androidApp` module graph. The car module's DI graph is simple — it only needs to declare car-specific Screen factories and the EmergencyHandler; all business logic comes from existing core modules. + +**Registration**: `androidApp/src/googleMain/` includes `FeatureCarModule` in the Koin application configuration (google flavor only). + +**Key bindings**: +- `MeshtasticCarSession` → factory (new per session) +- `EmergencyHandler` → singleton (one per process) +- `CrashlyticsCarTagger` → singleton +- All repositories, use cases → inherited from existing core modules (already in graph) + +## R8: AppFunctions Interop — Shared Interface Reuse + +**Decision**: Reuse `FuzzyNameResolver` pattern from AppFunctions for node name matching in voice replies + +**Rationale**: When a driver sends a direct message via voice, they may say a node name imprecisely. The AppFunctions feature (in-flight) implements fuzzy node name resolution. While the `AiFunctionProvider` interface is not yet merged, the car module can implement the same fuzzy matching logic directly using `NodeRepository.nodeDBbyNum` and Levenshtein distance or substring matching. + +**Implementation**: Standalone `FuzzyNodeNameResolver` utility class in `feature/car/util/` that queries `NodeRepository` and performs case-insensitive substring + edit-distance matching. If/when AppFunctions lands and exposes a shared resolver in `core/data/commonMain`, the car module can delegate to it. + +**Alternatives considered**: +- Wait for AppFunctions to land first → Rejected: Unclear timeline; car module should not block on it +- Exact match only → Rejected: Poor voice UX ("node exclamation one two three four" vs "James") + +## R9: Emergency Alert Banner Strategy + +**Decision**: Observe emergency messages via `PacketRepository` Flow, trigger CAL Banner API + +**Rationale**: Emergency messages are already classified in the packet data layer (message type/priority). The `EmergencyHandler` subscribes to the message flow, filters for emergency-priority packets, and immediately invokes `CarToast` + `AppManager.showAlert()` to display a Banner. The Banner overlays any active screen within CAL's rendering pipeline. + +**Audio**: Use `NotificationManager` to play a notification sound on the car's notification audio channel (`AudioAttributes.USAGE_NOTIFICATION`), not media channel (per NFR-008). + +**Alternatives considered**: +- Poll for emergencies on timer → Rejected: Violates 1-second latency requirement +- Use Android notifications only → Rejected: Would not overlay within CAL UI; needs in-app Banner + +## R10: Build Configuration — Google Flavor Only + +**Decision**: `feature/car` module included only in the `google` product flavor + +**Rationale**: CAL apps require Google Play Services for Android Auto projection. The F-Droid flavor explicitly excludes Google dependencies. The module is conditionally included via flavor-based dependency in `androidApp/build.gradle.kts`: + +```kotlin +"googleImplementation"(projects.feature.car) +``` + +This mirrors existing patterns like Firebase/Maps dependencies being google-flavor-only. + +**Alternatives considered**: +- Include in all flavors → Rejected: CAL requires Google Play Services; F-Droid builds would fail +- Separate app module for car → Rejected: Adds unnecessary complexity; flavor separation is simpler diff --git a/specs/20260521-153452-car-app-library-integration/spec.md b/specs/20260521-153452-car-app-library-integration/spec.md new file mode 100644 index 000000000..a488130f8 --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/spec.md @@ -0,0 +1,460 @@ +# Feature Specification: Car App Library Integration + +**Feature Branch**: `feature/20260521-153452-car-app-library-integration` +**Created**: 2026-05-21 +**Status**: Draft +**Input**: Integrate Android Car App Library 1.9.0-alpha01 as a fully-featured, first-class car app +**Cross-Platform Spec**: N/A — platform-specific only (Android Auto / AAOS exclusive; CAL has no cross-platform equivalent) + +## Summary + +Integrate the Android Car App Library 1.9.0-alpha01 into Meshtastic-Android to deliver a fully-featured, first-class automotive experience for Android Auto and Android Automotive OS. The integration creates a distraction-optimized, safety-first mesh radio interface for vehicles — enabling drivers to monitor mesh network status, read and reply to messages via voice, view node locations on maps, and receive emergency alerts with immediate prominence. A new `feature/car` module houses the Android-only CAL layer while reusing all shared business logic from existing core and feature modules. + +## Clarifications + +### Session 2026-05-21 + +- Q: How should voice commands be implemented — CAL built-in voice input, full Assistant App Actions, or both? → A: CAL built-in voice input only (tap reply → dictate → send). System-level "Hey Google" commands are handled separately by the AppFunctions feature (`specs/20260521-091500-app-functions/`), which exposes `sendMessage`, `getMeshStatus`, `listNodes`, `getRecentMessages`, and `getNodePosition` to Android system AI (Gemini) automatically — including on car displays. +- Q: Should the app declare NAVIGATION category for MapWithContentTemplate, or use PlaceListMapTemplate under POI? → A: Stay with POI category, use PlaceListMapTemplate (static pin list, refreshable). Avoids nav app conflicts and Play Store review burden. Live position tracking under NAVIGATION category deferred to v2. +- Q: Should the CarAppService maintain an independent BLE connection or share the phone app's existing connection? → A: Shared connection — single Application-scoped BleConnectionManager instance via Koin. CarAppService keeps the process alive via Android Auto host; BLE connection persists at the Service/Application level, not Activity level. +- Q: What observability approach should the car module use? → A: Reuse existing Crashlytics with `car_session` custom key tagging for car-specific filtering. No new observability infrastructure; tag existing analytics paths. +- Q: Should the car app unlock additional features when the vehicle is parked? → A: No parked-mode differentiation. Templated messaging apps provide a uniform experience regardless of driving state. Voice reply is built into ConversationItem. The Android Auto host enforces its own driving restrictions; the app just provides templates. + +## Goals + +1. **Complete automotive mesh experience** — Deliver all seven core screens (messaging, node dashboard, channel management, emergency alerts, map, quick actions, mesh status panel) as a single release +2. **Safety-first interaction model** — Every interaction completes in ≤ 2 taps or via voice, meeting automotive distraction guidelines +3. **Leverage 1.9.0-alpha01 components** — Showcase Spotlight Sections, Condensed Items, Chips, Minimized Control Panel, Banners, Section Headers, and Expanded Headers for a modern car UI +4. **Zero disruption to existing app** — The new `feature/car` module integrates via dependency injection without modifying existing module APIs or behavior +5. **Voice-first messaging** — Message composition defaults to voice input, with quick-reply templates as fallback for hands-free operation + +## Non-Goals + +- Firmware updates via the car interface (too complex and risky while driving) +- Full settings UI in-car (a minimal parked-only subset may be considered in future) +- Desktop or iOS car support (this is Android Auto / AAOS specific) +- Video playback or media/audio streaming features +- Compose UI interop (CAL uses its own template-based rendering system) +- Google Assistant App Actions / voice command routing (handled by separate AppFunctions feature) +- NAVIGATION category declaration / live map tracking (deferred to v2; v1 uses POI with PlaceListMapTemplate) +- Phone app UI changes (car UI is additive only) + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Read and Reply to Mesh Messages While Driving (Priority: P1) + +A driver receives mesh messages from their group while on the road. They glance at the head unit to see new messages and use voice to compose a reply, keeping hands on the wheel and eyes on the road. + +**Why this priority**: Messaging is the primary use case for Meshtastic. Enabling safe in-car messaging addresses the #1 reason users would want car integration. + +**Independent Test**: Can be fully tested by sending a message from a second Meshtastic device, verifying it appears on the car display, and dictating a voice reply that arrives on the sender's device. + +**Acceptance Scenarios**: + +1. **Given** the car app is connected to a Meshtastic radio and a new message arrives, **When** the driver views the messaging screen, **Then** the new message appears within 3 seconds with sender name, timestamp, and message content visible at a glance +2. **Given** the driver is viewing a conversation, **When** they tap the reply action, **Then** the system presents voice input as the default composition method +3. **Given** the driver has initiated voice reply, **When** they speak their message and confirm, **Then** the message is sent to the correct channel/DM within 2 seconds +4. **Given** the driver prefers not to use voice, **When** they select quick-reply, **Then** a list of configurable template responses (e.g., "On my way", "Copy that", "10 minutes out") is presented for one-tap selection +5. **Given** the mesh radio is disconnected, **When** the driver opens messaging, **Then** a banner clearly indicates offline status and cached messages remain visible as read-only + +--- + +### User Story 2 - Emergency Alert Reception (Priority: P1) + +A driver receives an emergency alert broadcast from a mesh node (SOS, hazard warning, etc.). The alert demands immediate attention with distinct visual and audio treatment, regardless of which screen is currently active. + +**Why this priority**: Emergency alerts are life-safety critical. Failure to surface them prominently could have real-world safety consequences. + +**Independent Test**: Can be tested by triggering an emergency broadcast from a test device and verifying the car app interrupts current activity with a banner alert. + +**Acceptance Scenarios**: + +1. **Given** any screen is active, **When** an emergency message is received, **Then** a high-priority banner appears immediately (within 1 second) with emergency iconography and distinct color treatment +2. **Given** an emergency banner is displayed, **When** the driver taps it, **Then** full emergency details are shown including sender identity, location (if available), and timestamp +3. **Given** an emergency alert has been received, **When** the driver navigates to the messaging screen, **Then** the emergency message appears in a Spotlight Section at the top, visually distinguished from normal messages +4. **Given** emergency audio alerts are enabled, **When** an emergency message arrives, **Then** an audible notification tone plays through the car's audio system + +--- + +### User Story 3 - Monitor Node Network Status (Priority: P2) + +A driver glances at the head unit to check how many mesh nodes are in range, their signal strength, and battery levels — useful for caravan/convoy scenarios or checking if they're still in range of base camp. + +**Why this priority**: Node awareness is the second-most-common Meshtastic use case and provides critical situational awareness for mobile users. + +**Independent Test**: Can be tested by having 3+ nodes in range and verifying the dashboard displays each with correct signal/battery metrics. + +**Acceptance Scenarios**: + +1. **Given** the car app is connected with multiple nodes in range, **When** the driver opens the node dashboard, **Then** all known nodes are displayed as Condensed Items showing node name, signal quality indicator, and battery level +2. **Given** 6+ nodes are in range, **When** viewing the dashboard, **Then** at least 6 nodes are visible simultaneously without scrolling (leveraging Condensed Items) +3. **Given** a node goes offline, **When** the dashboard refreshes, **Then** the offline node is visually distinguished (dimmed or marked) and sorted to the bottom +4. **Given** the node list is displayed, **When** the driver taps a node, **Then** a detail view shows last heard time, distance (if location known), hardware model, and direct message option + +--- + +### User Story 4 - Switch Between Channels (Priority: P2) + +A driver participating in multiple mesh channels (e.g., "Convoy", "Emergency", "General") quickly switches between them to view messages from different groups. + +**Why this priority**: Channel management is essential for users in organized groups and must be achievable without complex navigation. + +**Independent Test**: Can be tested by configuring 3+ channels and verifying single-tap channel switching via chips. + +**Acceptance Scenarios**: + +1. **Given** the device has multiple channels configured, **When** the messaging screen loads, **Then** channel chips are displayed at the top allowing single-tap switching +2. **Given** channel chips are visible, **When** the driver taps a different channel chip, **Then** the message list updates to show that channel's messages within 1 second +3. **Given** a channel has unread messages, **When** viewing the chip bar, **Then** that channel's chip displays an unread indicator (badge or visual emphasis) + +--- + +### User Story 5 - View Node Locations on Map (Priority: P2) + +A driver in a convoy scenario views the locations of all mesh nodes on a map to understand relative positions and navigate toward or away from group members. + +**Why this priority**: Location awareness is a core differentiator of Meshtastic and maps are natural for automotive interfaces. + +**Independent Test**: Can be tested by having 2+ nodes reporting GPS positions and verifying pins appear at correct locations on the car map. + +**Acceptance Scenarios**: + +1. **Given** nodes are reporting GPS positions, **When** the driver opens the map screen, **Then** node locations appear as labeled items in a PlaceListMapTemplate with pins on the map +2. **Given** the map is displayed with node pins, **When** the driver taps a node item in the list, **Then** a detail panel shows node name, distance, last update time, and option to send a direct message +3. **Given** the driver's own position is available, **When** viewing the map, **Then** their position is shown distinctly from other nodes +4. **Given** a node's position updates, **When** the map is visible, **Then** the pin moves to the new position within 5 seconds + +--- + +### User Story 6 - Persistent Mesh Status at a Glance (Priority: P3) + +While using any car app feature, the driver can glance at a persistent mini-panel showing mesh connectivity health — how many nodes are online, time since last message, and connection status to the radio. + +**Why this priority**: Persistent status awareness reduces the need to navigate between screens, minimizing distraction. + +**Independent Test**: Can be tested by verifying the minimized control panel remains visible across all screens and updates in real-time. + +**Acceptance Scenarios**: + +1. **Given** the car app is active on any screen, **When** the driver glances at the minimized control panel, **Then** they see: radio connection status, node count online, and time since last received message +2. **Given** the radio disconnects, **When** the status panel updates, **Then** it clearly indicates "Disconnected" with warning iconography +3. **Given** the minimized panel is visible, **When** the driver taps it, **Then** it expands to show additional detail (mesh name, own node battery, firmware version) + +--- + +### User Story 7 - In-Context Voice Input for Actions (Priority: P3) + +A driver uses CAL's built-in voice input to compose messages and perform actions without typing — tapping reply then dictating, or using TTS readback of messages. System-level voice commands ("Hey Google, send Meshtastic message to John") are handled separately by the AppFunctions feature and work automatically on car displays without car module code. + +**Why this priority**: Voice is the safest interaction modality while driving and rounds out the hands-free experience. + +**Independent Test**: Can be tested by tapping the reply action, dictating a message via CAL voice input, and verifying delivery. System-level "Hey Google" commands are tested via the AppFunctions spec. + +**Acceptance Scenarios**: + +1. **Given** the car app is on a conversation screen, **When** the driver taps the reply action and speaks a message, **Then** voice composition targets that node/channel using CAL's built-in voice input API +2. **Given** a message is displayed, **When** the driver taps a "read aloud" action, **Then** the message is read via TTS including sender name and content +3. **Given** the driver initiates a direct message from the node dashboard, **When** they tap a node and select "message", **Then** voice input is presented as the default composition method with `FuzzyNameResolver` used for node name matching + +--- + +### Edge Cases + +- What happens when the Bluetooth connection to the Meshtastic radio drops mid-conversation? → Banner notification + graceful degradation to cached data, auto-reconnect in background +- What happens when the message list exceeds CAL template item limits? → Cap at 10 conversations with 5 messages each per Android Auto best practices; most recent first +- How does the system handle very long messages that exceed car display constraints? → Truncation with "..." and full message available on tap or read-aloud +- What happens when outgoing messages exceed 237 bytes (Meshtastic protocol limit)? → Reject with user feedback ("Message too long"); do not attempt to send +- What happens when the car's system restricts interaction (e.g., car moving at speed)? → No parked-mode differentiation; the templated messaging UI is uniform regardless of driving state. Voice reply is built into ConversationItem automatically. The Android Auto host enforces its own driving restrictions — the app provides templates only. +- What happens when multiple emergency alerts arrive simultaneously? → Stack as multiple banners; Spotlight Section shows all active emergencies chronologically +- How does the app handle no configured channels? → Show onboarding prompt directing user to configure channels on their phone first +- What happens with emoji-only or admin messages? → Filtered from car display entirely (not shown in conversation list or read aloud) +- What happens on initial session connect with existing unread messages? → Batch-load up to 50 unread messages across conversations; also post MessagingStyle notifications for read-back support +- How are favorites vs recent contacts distinguished? → Favorites (node.favorite == true) grouped at top of DM list with Section Header; remaining contacts sorted by last-heard, capped at 24 + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: System MUST register as a Car App Service discoverable by Android Auto and AAOS hosts +- **FR-002**: System MUST display incoming mesh messages in a scrollable list grouped by channel using Section Headers +- **FR-003**: System MUST support voice-based message composition as the primary reply method +- **FR-004**: System MUST provide quick-reply templates selectable with a single tap +- **FR-005**: System MUST display emergency messages as high-priority Banners that overlay any active screen within 1 second of receipt +- **FR-006**: System MUST present emergency messages in a Spotlight Section when viewing the messaging screen +- **FR-007**: System MUST display all known mesh nodes as Condensed Items showing name, signal quality, and battery level +- **FR-008**: System MUST support channel switching via Chips displayed at the top of the messaging screen +- **FR-009**: System MUST render node positions on a map using PlaceListMapTemplate under the POI category (static pin list, refreshable; NAVIGATION category with MapWithContentTemplate deferred to v2) +- **FR-010**: System MUST maintain a persistent Minimized Control Panel showing radio status, online node count, and last message time +- **FR-011**: System MUST display a Banner when the Bluetooth connection to the radio is lost +- **FR-012**: System MUST support expanding node details on tap (last heard, distance, hardware model) +- **FR-013**: System MUST use Expanded Header Layout for the node dashboard showing mesh topology summary +- **FR-014**: System MUST declare MESSAGING as the primary category and POI as secondary +- **FR-015**: System MUST gracefully degrade to cached/read-only data when the mesh radio is disconnected +- **FR-016**: System MUST support unread message indicators on channel Chips +- **FR-017**: System MUST filter emoji-only and admin messages from the car display (only text messages shown) +- **FR-018**: System MUST reject outgoing messages exceeding 237 bytes (Meshtastic packet limit) with user-visible feedback +- **FR-019**: System MUST display at most 10 conversations and at most 5 messages per ConversationItem, per Android Auto best practices +- **FR-020**: System MUST group direct message contacts into "Favorites" (nodes marked favorite) and "Recent" sections using Section Headers +- **FR-021**: System MUST load up to 50 unread messages across conversations on session start, most recent first +- **FR-022**: System MUST also implement notification-based messaging (NotificationCompat.MessagingStyle with reply and mark-as-read Actions) as required by templated messaging apps + +### Non-Functional Requirements + +- **NFR-001**: All interactive elements MUST be reachable within 2 taps from any screen +- **NFR-002**: New message display latency MUST be ≤ 3 seconds from radio receipt to screen render +- **NFR-003**: Car app battery overhead MUST be < 10% additional drain compared to the phone app running alone +- **NFR-004**: Car App minimum API level MUST be Car API Level 8 (required for 1.9.0 components) +- **NFR-005**: The car module MUST NOT introduce dependencies that affect the phone app's build time by more than 5% +- **NFR-006**: All text elements MUST meet automotive readability guidelines (minimum font sizes per OEM requirements) +- **NFR-007**: The app MUST support both Android Auto (projection) and AAOS (embedded) deployment modes +- **NFR-008**: Emergency alert audio MUST play through the car's notification channel, not media channel +- **NFR-009**: Car module MUST tag all Crashlytics events with a `car_session` custom key (value: session ID) to enable car-specific crash/ANR filtering and diagnosis +- **NFR-010**: Screen invalidation MUST be debounced (≥300ms) and MUST NOT recreate Screen objects; use `invalidate()` to trigger `onGetTemplate()` re-evaluation, matching CarPlay's proven refresh pattern +- **NFR-011**: Template data refresh latency MUST be ≤500ms from invalidation trigger to rendered update + +## Architecture + +### Key Components + +| Component | Module / File | Purpose | +|-----------|---------------|---------| +| MeshtasticCarAppService | `feature/car/service/` | CAL Session host, entry point for Android Auto/AAOS | +| MessagingScreen | `feature/car/screens/` | Message list with channel chips, voice reply, quick-reply | +| NodeDashboardScreen | `feature/car/screens/` | Condensed Items grid of all mesh nodes | +| MapScreen | `feature/car/screens/` | PlaceListMapTemplate showing node positions as place items | +| EmergencyHandler | `feature/car/alerts/` | Banner management for emergency messages | +| MeshStatusPanel | `feature/car/panels/` | Minimized Control Panel with mesh health | +| CarMessageRepository | `core/data/` | Existing message repository (reused) | +| CarNodeRepository | `core/data/` | Existing node repository (reused) | +| ChannelManager | `core/domain/` | Existing channel logic (reused) | +| BleConnectionManager | `core/ble/` | Existing BLE connection (reused; Application-scoped singleton shared with phone app — CarAppService keeps process alive via host) | + +### Component Interaction + +``` +┌─────────────────────────────────────────────────┐ +│ Android Auto / AAOS Host │ +└────────────────────┬────────────────────────────┘ + │ CAL Session +┌────────────────────▼────────────────────────────┐ +│ MeshtasticCarAppService │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐│ +│ │Messaging │ │ Nodes │ │ Map Screen ││ +│ │ Screen │ │Dashboard │ │ ││ +│ └────┬─────┘ └────┬─────┘ └───────┬──────────┘│ +│ │ │ │ │ +│ ┌────▼─────────────▼───────────────▼──────────┐│ +│ │ MeshStatusPanel (persistent) ││ +│ └─────────────────────────────────────────────┘│ +│ ┌─────────────────────────────────────────────┐│ +│ │ EmergencyHandler (banners) ││ +│ └─────────────────────────────────────────────┘│ +└────────────────────┬────────────────────────────┘ + │ Koin DI +┌────────────────────▼────────────────────────────┐ +│ Shared Business Logic (core/) │ +│ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌───────┐ │ +│ │Messages │ │ Nodes │ │Channels│ │ BLE │ │ +│ │ Repo │ │ Repo │ │Manager │ │Connect│ │ +│ └─────────┘ └─────────┘ └────────┘ └───────┘ │ +└─────────────────────────────────────────────────┘ +``` + +## Source-Set Impact + +| Source Set | Impact | Justification | +|-----------|--------|---------------| +| `commonMain` | No changes | All shared business logic already exists in core modules | +| `androidMain` | New `feature/car` module | CAL is Android-only; entire car UI layer is platform-specific | + +## Design Standards Compliance + +- [ ] New screens reviewed against automotive HMI distraction guidelines (NHTSA Phase 2) +- [ ] CAL template system used exclusively (no custom rendering that bypasses automotive safety checks) +- [ ] Accessibility: Voice readback of all visual information, high-contrast automotive color schemes +- [ ] Typography: Uses CAL's built-in automotive-safe text sizing (enforced by host) +- [ ] Emergency alerts use distinct visual language (color, iconography) distinguishable from informational banners + +## Privacy Assessment + +- [ ] No PII, location data, or cryptographic keys logged or exposed beyond what existing modules already handle +- [ ] Car app reuses existing data layer — no new network calls or data collection +- [ ] Node location data displayed on map uses existing privacy controls (user opt-in for position sharing) +- [ ] No data sent to third-party automotive services +- [ ] Proto submodule (`core/proto`) not modified (read-only upstream) + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Users can read a new message and send a voice reply in under 15 seconds total interaction time +- **SC-002**: Emergency alerts are visible to the driver within 1 second of receipt by the radio +- **SC-003**: Node dashboard displays 6+ nodes simultaneously without scrolling (Condensed Items density) +- **SC-004**: All primary actions (read message, reply, check nodes, view map) reachable within 2 taps from home +- **SC-005**: Car app adds < 10% battery drain overhead compared to phone-only operation over a 1-hour driving session +- **SC-006**: Channel switching completes (chip tap to new message list rendered) within 1 second +- **SC-007**: App passes Android Auto App Quality review criteria for the MESSAGING category +- **SC-008**: 95% of voice-initiated replies complete successfully without fallback to touch input +- **SC-009**: Map displays node positions with < 5-second update latency when positions change +- **SC-010**: Zero crashes or ANRs attributed to the car module during a 2-hour continuous driving session + +## Assumptions + +- Car App Library 1.9.0-alpha01 APIs are sufficiently stable for production use (alpha risk accepted per user directive) +- The existing `core/data` repositories provide all necessary data access; no new data sources required +- Meshtastic radio remains paired and connected via BLE during driving (standard operating mode) +- BLE connection is Application-scoped (not Activity-scoped); CarAppService keeps the host process alive so the connection naturally persists regardless of phone app Activity state +- Users have already configured channels and node settings via the phone app before driving +- Android Auto host enforces its own distraction-optimization rules (template item limits, interaction restrictions); the app respects these constraints +- The `google` build flavor is the distribution target; F-Droid/GitHub flavors do not include car support +- Quick-reply templates are configurable via the phone app's settings; the car app consumes them read-only +- Voice input quality depends on the car's microphone hardware; the app delegates to Android's speech recognition system +- MapWithContentTemplate availability depends on NAVIGATION category declaration (deferred to v2); v1 uses PlaceListMapTemplate under POI which is widely supported +- Minimum Car API Level 8 is required; older Android Auto hosts will not show the app (graceful absence, not crash) +- Koin dependency injection is used consistently with Koin Annotations for the new module +- TTS (text-to-speech) for reading messages aloud uses Android's built-in TTS engine + +## External References & Research + +### Official Documentation + +| Resource | URL | Relevance | +|----------|-----|-----------| +| Car App Library Release Notes | https://developer.android.com/jetpack/androidx/releases/car-app | 1.8.0-beta01 & 1.9.0-alpha01 component APIs | +| Building Car Apps (Training) | https://developer.android.com/training/cars/apps | CarAppService setup, templates, lifecycle | +| Templated Messaging Guide | https://developer.android.com/training/cars/communication/templated-messaging | ConversationItem, voice reply, notification integration | +| Notification-based Messaging | https://developer.android.com/training/cars/messaging | MessagingStyle, reply/mark-as-read Actions | +| Android Auto Add Support | https://developer.android.com/training/cars/apps/auto | Manifest, automotive_app_desc.xml, projection | +| Component Design Guidance | https://developer.android.com/design/ui/cars/guides/components/overview | Automotive HMI patterns | +| Car App Quality Guidelines | https://developer.android.com/docs/quality-guidelines/car-app-quality | Review criteria for MESSAGING category | +| Testing with DHU | https://developer.android.com/training/cars/testing | Desktop Head Unit setup and usage | + +### Google I/O 2026 Announcements + +| Resource | URL | Key Takeaways | +|----------|-----|---------------| +| Android for Cars: Unifying Platforms | https://android-developers.googleblog.com/2026/05/android-for-cars-unifying-platforms-premium-experiences.html | CAL 1.8.0 media templates, CAL 1.9.0 components, Material 3 Expressive, video support | + +### Key API Patterns from Official Docs + +#### Templated Messaging (from official guidance) + +- **ConversationItem** auto-provides voice reply + mark-as-read actions +- Max **5–10 conversations**, each with ≤ **5 messages** +- Refresh cadence: ≤ **500ms** per invalidation +- Must also implement **notification-based messaging** (MessagingStyle) as fallback +- Distribution: Currently **internal + closed testing** tracks only (production opening later) + +#### Manifest Requirements + +```xml + + + + + + + + + + + + + + + + +``` + +#### ConversationItem Pattern (from official sample) + +```kotlin +ConversationItem.Builder() + .setConversationCallback(callback) + .setId(conversation.id) + .setTitle(conversation.title) + .setIcon(conversation.icon) + .setMessages(carMessages) + .setSelf(selfPerson) + .setGroupConversation(conversation.isGroup) + .build() +``` + +### Related In-Flight Features + +| Feature | Branch | Spec | Relationship | +|---------|--------|------|-------------| +| App Functions | `jamesarich/crispy-barnacle` | `specs/20260521-091500-app-functions/` | Provides "Hey Google" system AI integration for sendMessage, getMeshStatus, listNodes, getRecentMessages, getNodePosition — complementary to CAL voice input | + +#### Shared Infrastructure from AppFunctions + +- **`AiFunctionProvider`** interface in `core/data/commonMain` — platform-agnostic contract for AI-driven operations +- **`FuzzyNameResolver`** in `core/data/commonMain` — LCS-based node/channel name matching (50% threshold) +- **`RateLimiter`** in `core/data/commonMain` — sliding window rate limiter (5 calls/60s) for mesh airtime protection +- **Architecture pattern:** Thin Android wrappers (`androidApp/src/google/`) calling shared business logic + +#### Integration Points + +- Car module reuses `FuzzyNameResolver` for voice reply targeting (e.g., "reply to James" → resolve to node) +- `RateLimiter` can protect car-originated sends from exceeding mesh airtime +- AppFunctions "Hey Google" commands work on car displays automatically (system-level, no car module code needed) +- Both features share: `NodeRepository`, `CommandSender`, `RadioConfigRepository`, `PacketRepository` + +### CAL 1.9.0-alpha01 Component Reference + +| Component | API Class | Min Car API | Use in Meshtastic | +|-----------|-----------|-------------|-------------------| +| Spotlight Section | `SpotlightSection.Builder()` | 8 | Emergency messages pinned at top | +| Condensed Items | `CondensedItem.Builder()` | 8 | Dense node list (6+ visible) | +| Chips | `Chip.Builder()` | 8 | Channel switching + unread badges | +| Minimized Control Panel | `SectionedItemTemplate` | 8 | Persistent mesh status strip | +| Banners | `Banner.Builder()` | 8 | Emergency overlay + disconnection alerts | +| Section Headers | `SectionHeader.Builder()` | 8 | Message grouping by channel | +| Expanded Header Layout | `Header.Builder()` | 8 | Mesh topology summary (node dashboard) | + +### Distribution Constraints (as of May 2026) + +- **Templated messaging apps:** Internal + closed testing tracks only on Play Store +- **Production track:** Not yet open for templated messaging category +- **AAOS:** Separate distribution channel (OEM app stores or Play for Automotive) +- **F-Droid:** Excluded (CAL requires Google Play Services) +- **Timeline:** Production track expected to open "later" per Google (no firm date) + +### Cross-Platform Parity: Meshtastic-Apple CarPlay + +**Source:** `Meshtastic-Apple/Meshtastic/CarPlay/` (main branch, May 21, 2026) + +**Apple CarPlay features (shipped):** +- Two-tab UI: Channels + Direct Messages (with Favorites/Recent sections) +- SiriKit voice compose/read-back via `INSendMessageIntent` +- Unread badges per channel and per DM +- "Not Connected" graceful degradation +- Live Activity (Dynamic Island) with node telemetry stats +- Batch donation of 50 unread messages on session start +- 300ms debounced refresh (updateSections, not rebuild) +- Message search via `INSearchForMessagesIntent` +- Message filtering: no emoji-only, no admin messages +- 200-byte message limit enforcement + +**Parity decisions incorporated into this spec:** +- FR-017: Message filtering (emoji/admin exclusion) — matches Apple +- FR-018: Message size limit enforcement — matches Apple (237 bytes for Meshtastic) +- FR-019: Conversation caps (10 convos, 5 msgs each) — per Android guidance +- FR-020: Favorites section grouping — matches Apple's Favorites/Recent pattern +- FR-021: Session start unread batch load — matches Apple's 50-message donation +- FR-022: Notification-based messaging fallback — required per Android templated messaging docs +- NFR-010: Refresh debouncing (≥300ms) — matches Apple's proven 300ms debounce +- NFR-011: Refresh latency (≤500ms) — matches Apple's observed performance + +**Android-exclusive features (exceeding Apple):** +- Node dashboard with Condensed Items (Apple has no node visibility) +- Emergency Banner overlays with audio alerts (Apple shows emergencies as regular messages) +- Map integration via PlaceListMapTemplate (Apple has no map) +- Channel Chips for instant switching (Apple requires tab navigation) +- Quick-reply templates (Apple only offers Siri voice) +- Visual hierarchy via Spotlight/Section Headers/Expanded Headers +- Persistent Minimized Control Panel (Apple uses separate Live Activity) + +**Deferred to v2 (Apple has, we don't yet):** +- Message search (SearchTemplate or via AppFunctions) +- Live Activity equivalent (Android ongoing notification with mesh telemetry) diff --git a/specs/20260521-153452-car-app-library-integration/tasks.md b/specs/20260521-153452-car-app-library-integration/tasks.md new file mode 100644 index 000000000..4fe54c62e --- /dev/null +++ b/specs/20260521-153452-car-app-library-integration/tasks.md @@ -0,0 +1,273 @@ +# Tasks: Car App Library Integration + +**Input**: Design documents from `/specs/20260521-153452-car-app-library-integration/` + +**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/ ✅ + +**Tests**: Not explicitly requested in spec. Test tasks omitted per template rules. + +**Verification**: Constitution-required validation (spotlessCheck, detekt, compile/test) included in final phase. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +--- + +## Phase 1: Setup (Project Initialization) + +**Purpose**: Create the `feature/car` module structure, Gradle configuration, and version catalog entries + +- [ ] T001 Add Car App Library version catalog entries in gradle/libs.versions.toml (car-app version, 4 library entries) +- [ ] T002 Add `include(":feature:car")` to settings.gradle.kts +- [ ] T003 Create module build file at feature/car/build.gradle.kts with android-library, flavors, koin plugins, and all dependencies per contracts/manifest-declarations.md +- [ ] T004 [P] Add `"googleImplementation"(projects.feature.car)` dependency in androidApp/build.gradle.kts +- [ ] T005 [P] Create AndroidManifest.xml at feature/car/src/main/AndroidManifest.xml with CarAppService, MESSAGING+POI categories, and minCarApiLevel 8 meta-data +- [ ] T006 [P] Create AAOS descriptor at feature/car/src/main/res/xml/automotive_app_desc.xml +- [ ] T007 [P] Create car-specific strings file at feature/car/src/main/res/values/strings.xml with initial string resources + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Core infrastructure that ALL user stories depend on — service entry point, session lifecycle, DI, utilities + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete + +- [ ] T008 Create Koin DI module at feature/car/src/main/kotlin/org/meshtastic/feature/car/di/FeatureCarModule.kt declaring MeshtasticCarSession (factory), EmergencyHandler (singleton), CrashlyticsCarTagger (singleton) +- [ ] T009 Register FeatureCarModule in androidApp google flavor Koin configuration (androidApp/src/google/ Koin app module graph) +- [ ] T010 [P] Create CrashlyticsCarTagger utility at feature/car/src/main/kotlin/org/meshtastic/feature/car/util/CrashlyticsCarTagger.kt implementing car_session custom key set/clear +- [ ] T011 [P] Create TemplateBuilders helper extensions at feature/car/src/main/kotlin/org/meshtastic/feature/car/util/TemplateBuilders.kt with reusable CAL template construction helpers +- [ ] T012 Create MeshtasticCarAppService at feature/car/src/main/kotlin/org/meshtastic/feature/car/service/MeshtasticCarAppService.kt extending CarAppService, creating sessions via Koin +- [ ] T013 Create MeshtasticCarSession at feature/car/src/main/kotlin/org/meshtastic/feature/car/service/MeshtasticCarSession.kt with onCreateScreen (returns HomeScreen), onNewIntent, onCarConfigurationChanged, Crashlytics tagging, 300ms invalidation debouncing +- [ ] T014 Create presentation state models (CarSessionState, ConnectionStatus, MessagingUiState, ChannelUi, ConversationUi, NodeDashboardUiState, NodeUi, SignalQuality, TopologyHeader, MapUiState, NodePlace, LatLngWrapper, EmergencyAlert) at feature/car/src/main/kotlin/org/meshtastic/feature/car/model/CarUiModels.kt +- [ ] T015 Create HomeScreen (TabTemplate with Messages/Nodes/Map tabs) at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/HomeScreen.kt + +**Checkpoint**: Foundation ready — CarAppService binds, session creates, HomeScreen renders tabs. User story implementation can now begin in parallel. + +--- + +## Phase 3: User Story 1 — Read and Reply to Mesh Messages While Driving (Priority: P1) 🎯 MVP + +**Goal**: Drivers can view incoming mesh messages grouped by channel and reply via voice or quick-reply templates + +**Independent Test**: Send a message from a second Meshtastic device → appears on car display within 3s → dictate voice reply → arrives on sender's device + +### Implementation for User Story 1 + +- [ ] T016 [P] [US1] Create MessagingScreen at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/MessagingScreen.kt with ListTemplate, channel Chips header, Section Headers grouping conversations, ConversationItem list (max 10), 300ms debounced invalidation, favorites/recent DM grouping +- [ ] T017 [P] [US1] Create ConversationScreen at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/ConversationScreen.kt with MessageTemplate showing messages (max 5 per conversation), voice reply action via CAL built-in ConversationItem voice input, quick-reply action list from QuickChatActionRepository, read-aloud TTS action +- [ ] T018 [US1] Reuse `FuzzyNameResolver` from `core/data/commonMain` (shared with AppFunctions feature) for voice-initiated DM node name matching — inject via Koin from existing `core/data` module. If AppFunctions branch not yet merged, temporarily duplicate LCS algorithm in feature/car/src/main/kotlin/org/meshtastic/feature/car/util/FuzzyNodeNameResolver.kt with TODO to consolidate post-merge +- [ ] T019 [US1] Implement message filtering logic in MessagingScreen — exclude emoji-only and admin messages from display (FR-017), enforce 237-byte outgoing limit with user feedback (FR-018) +- [ ] T020 [US1] Implement session-start batch loading of up to 50 unread messages in MeshtasticCarSession (FR-021) and post MessagingStyle notifications for read-back support +- [ ] T021 [US1] Implement notification-based messaging (NotificationCompat.MessagingStyle with reply and mark-as-read Actions) at feature/car/src/main/kotlin/org/meshtastic/feature/car/service/CarNotificationManager.kt (FR-022) + +**Checkpoint**: Messaging fully functional — driver can see messages, switch channels, voice reply, use quick-reply templates, and receive MessagingStyle notifications + +--- + +## Phase 4: User Story 2 — Emergency Alert Reception (Priority: P1) + +**Goal**: Emergency broadcasts immediately surface as prominent banners with audio alerts regardless of active screen + +**Independent Test**: Trigger emergency broadcast from test device → banner appears within 1s → audio alert plays → tap shows full details in Spotlight Section + +### Implementation for User Story 2 + +- [ ] T022 [P] [US2] Create EmergencyHandler at feature/car/src/main/kotlin/org/meshtastic/feature/car/alerts/EmergencyHandler.kt observing PacketRepository flow for emergency-priority packets, triggering Banner via AppManager.showAlert(), managing active emergency list, stacking multiple banners chronologically +- [ ] T023 [US2] Implement emergency audio alert playback in EmergencyHandler using NotificationManager on USAGE_NOTIFICATION audio channel (NFR-008), not media channel +- [ ] T024 [US2] Integrate Spotlight Section in MessagingScreen for active emergencies — display EmergencyAlert items at top of messaging list when activeEmergencies is non-empty (FR-006). **Depends on T016 (MessagingScreen must exist first)** +- [ ] T025 [US2] Wire EmergencyHandler into MeshtasticCarSession lifecycle — start collecting on onCreateScreen, stop on session destroy + +**Checkpoint**: Emergency alerts fully operational — banners overlay any screen within 1s, audio plays, Spotlight Section shows in messaging view + +--- + +## Phase 5: User Story 3 — Monitor Node Network Status (Priority: P2) + +**Goal**: Driver views all mesh nodes as a dense Condensed Items grid with signal/battery metrics and topology header + +**Independent Test**: Have 3+ nodes in range → open node dashboard → all nodes displayed with correct signal/battery → tap node → detail view shows full info + +### Implementation for User Story 3 + +- [ ] T026 [P] [US3] Create NodeDashboardScreen at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDashboardScreen.kt with ListTemplate, Expanded Header Layout (mesh topology summary: online/total), Condensed Items for each node (name, signal quality, battery), online-first sorting with offline dimmed at bottom +- [ ] T027 [P] [US3] Create NodeDetailScreen at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/NodeDetailScreen.kt with PaneTemplate showing last heard, distance, hardware model, battery, SNR, and "Message" action to push ConversationScreen for DM + +**Checkpoint**: Node dashboard shows 6+ nodes without scrolling via Condensed Items, detail drill-down works, DM action connects to messaging + +--- + +## Phase 6: User Story 4 — Switch Between Channels (Priority: P2) + +**Goal**: Single-tap channel switching via Chips with unread badges at the top of the messaging screen + +**Independent Test**: Configure 3+ channels → messaging screen shows channel chips → tap chip → message list updates within 1s → unread badge visible on channels with new messages + +### Implementation for User Story 4 + +- [ ] T028 [US4] Implement channel Chip actions with unread badge indicators in MessagingScreen header — single-tap switches selectedChannelIndex, triggers message list re-filter within 1s (FR-008, FR-016) + +**Checkpoint**: Channel chips render with unread counts, tapping switches view to that channel's conversations immediately + +--- + +## Phase 7: User Story 5 — View Node Locations on Map (Priority: P2) + +**Goal**: Nodes with GPS positions displayed as place items on a PlaceListMapTemplate with auto-zoom and detail drill-down + +**Independent Test**: Have 2+ nodes reporting GPS → open map → pins at correct locations → tap list item → node detail with distance and DM option + +### Implementation for User Story 5 + +- [ ] T029 [US5] Create MapScreen at feature/car/src/main/kotlin/org/meshtastic/feature/car/screens/MapScreen.kt with PlaceListMapTemplate under POI category, node Place items with LatLng from NodeRepository (filtered to valid positions), distance + last update in row text, own position as anchor, onClickListener pushing NodeDetailScreen, 5-second refresh interval. **Cap at 6 Place items per CAL PlaceListMapTemplate limit — prioritize by distance (nearest first), then recency** + +**Checkpoint**: Map displays node pins, auto-zooms to fit, list items show distance, tap navigates to node detail + +--- + +## Phase 8: User Story 6 — Persistent Mesh Status at a Glance (Priority: P3) + +**Goal**: Minimized Control Panel visible across all screens showing radio status, node count, last message time + +**Independent Test**: Navigate between all screens → mini-panel always visible → shows correct node count → disconnect radio → panel shows "Disconnected" + +### Implementation for User Story 6 + +- [ ] T030 [US6] Create MeshStatusPanel at feature/car/src/main/kotlin/org/meshtastic/feature/car/panels/MeshStatusPanel.kt implementing Minimized Control Panel — connectionStatusIcon, "{N} nodes online" title, "Last msg: {timeAgo}" subtitle, onClickListener expanding to full detail (mesh name, own battery, firmware version) +- [ ] T031 [US6] Register MeshStatusPanel in MeshtasticCarSession lifecycle — attach to session on creation, observe BleConnectionState + NodeRepository for live updates, show "Disconnected" with warning icon on radio disconnect (FR-010, FR-011) + +**Checkpoint**: Persistent mini-panel visible across all screens, updates in real-time, expands on tap + +--- + +## Phase 9: User Story 7 — In-Context Voice Input for Actions (Priority: P3) + +**Goal**: Voice reply is the default composition method, TTS reads messages aloud, FuzzyNodeNameResolver handles voice-initiated DMs + +**Independent Test**: Tap reply → dictate → message sent → tap "read aloud" → TTS reads message with sender name + +### Implementation for User Story 7 + +- [ ] T032 [US7] Implement TTS read-aloud action in ConversationScreen using Android built-in TTS engine — reads sender name + message content on tap of "Read Aloud" action +- [ ] T033 [US7] Wire FuzzyNodeNameResolver into node detail "Message" action flow — when initiating DM from NodeDashboard, voice input is default composition method with resolved node context + +**Checkpoint**: Voice reply works end-to-end, TTS reads messages clearly, node-initiated DMs use voice by default + +--- + +## Phase 10: Polish & Cross-Cutting Concerns + +**Purpose**: Error handling, degraded states, compliance, and verification + +- [ ] T034 [P] Implement BLE disconnection Banner + graceful degradation to cached read-only data across all screens (FR-011, FR-015) +- [ ] T035 [P] Implement empty/error states: no channels configured → onboarding PaneTemplate, no nodes → "No nodes heard", no positions → "No positions reported" (per error contracts) +- [ ] T036 [P] Add ProGuard/R8 keep rule for MeshtasticCarAppService in feature/car/proguard-rules.pro +- [ ] T037 [P] Confirm no logs, telemetry, or config changes expose PII, location data, secrets, or modify `core/proto` +- [ ] T038 [P] Review all screens against automotive HMI distraction guidelines — verify ≤ 2 taps for all primary actions (NFR-001) +- [ ] T039 Run constitution-required verification: `./gradlew spotlessApply :feature:car:spotlessCheck :feature:car:detekt :feature:car:testGoogleDebugUnitTest` +- [ ] T040 Validate quickstart.md developer workflow documentation is accurate for the implemented module + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: No dependencies — start immediately +- **Phase 2 (Foundational)**: Depends on Phase 1 — BLOCKS all user stories +- **Phase 3 (US1 - Messaging)**: Depends on Phase 2 — MVP target +- **Phase 4 (US2 - Emergency)**: Depends on Phase 2; integrates with MessagingScreen (Phase 3 T016) +- **Phase 5 (US3 - Nodes)**: Depends on Phase 2 — independent of messaging +- **Phase 6 (US4 - Channels)**: Depends on Phase 3 (modifies MessagingScreen) +- **Phase 7 (US5 - Map)**: Depends on Phase 5 (reuses NodeDetailScreen from T027) +- **Phase 8 (US6 - Status Panel)**: Depends on Phase 2 — independent +- **Phase 9 (US7 - Voice)**: Depends on Phase 3 (ConversationScreen T017, FuzzyNodeNameResolver T018) +- **Phase 10 (Polish)**: Depends on all user story phases + +### User Story Dependencies + +- **US1 (Messaging, P1)**: Can start after Phase 2 — no other story dependencies +- **US2 (Emergency, P1)**: Can start after Phase 2 — integrates with US1's MessagingScreen (T016) for Spotlight Section (T024) +- **US3 (Nodes, P2)**: Can start after Phase 2 — fully independent +- **US4 (Channels, P2)**: Depends on US1 (extends MessagingScreen) +- **US5 (Map, P2)**: Depends on US3 (reuses NodeDetailScreen) +- **US6 (Status Panel, P3)**: Can start after Phase 2 — fully independent +- **US7 (Voice, P3)**: Depends on US1 (extends ConversationScreen) + +### Within Each User Story + +- State models → Screen implementation → Integration logic +- Screens before cross-screen wiring +- Core implementation before refinement + +### Parallel Opportunities + +- **Phase 1**: T004, T005, T006, T007 can all run in parallel +- **Phase 2**: T010, T011 in parallel; T014 parallel with T010/T011 +- **After Phase 2**: US1, US3, and US6 can start simultaneously (independent) +- **Within US1**: T016 and T017 in parallel (different files) +- **Within US2**: T022 independent of other stories +- **Within US3**: T026 and T027 in parallel (different files) +- **Phase 10**: T034, T035, T036, T037, T038 all in parallel + +--- + +## Parallel Example: After Foundational Phase + +```bash +# Three stories can start simultaneously: +# Developer A: US1 (Messaging) +Task: T016 "Create MessagingScreen" +Task: T017 "Create ConversationScreen" + +# Developer B: US3 (Nodes) +Task: T026 "Create NodeDashboardScreen" +Task: T027 "Create NodeDetailScreen" + +# Developer C: US6 (Status Panel) +Task: T030 "Create MeshStatusPanel" +Task: T031 "Register panel in session" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup (T001–T007) +2. Complete Phase 2: Foundational (T008–T015) +3. Complete Phase 3: User Story 1 — Messaging (T016–T021) +4. **STOP and VALIDATE**: Test messaging end-to-end with DHU +5. Deploy to internal testing track if ready + +### Incremental Delivery + +1. Setup + Foundational → Module compiles and binds to Android Auto +2. Add US1 (Messaging) → Core value delivered (MVP!) +3. Add US2 (Emergency) → Safety-critical alerts operational +4. Add US3 + US5 (Nodes + Map) → Location awareness complete +5. Add US4 (Channels) → Multi-channel workflows enabled +6. Add US6 + US7 (Panel + Voice) → Polish and hands-free refinement +7. Each increment is independently testable with the Desktop Head Unit (DHU) + +### Parallel Team Strategy + +With multiple developers after Phase 2: +- Developer A: US1 (Messaging) → US4 (Channels) → US7 (Voice) +- Developer B: US3 (Nodes) → US5 (Map) +- Developer C: US2 (Emergency) + US6 (Status Panel) + +--- + +## Notes + +- All screens use `invalidate()` for refresh (never recreate Screen objects) per NFR-010 +- 300ms debounce on all invalidation triggers per NFR-010 +- CAL host enforces distraction guidelines — app provides templates only +- Existing `core/` modules consumed read-only via Koin DI — no API changes +- Google flavor only — F-Droid builds unaffected +- Car API Level 8 minimum — older hosts gracefully hide the app