# GPS2Audio v2.0.4 — Atmo Background Playback Fix ## Overview - **Version:** `2.0.4` (versionCode `32`, ↑ from 31) - **Baseline:** `GPS2Audio_v2_0_3_ptt_waypoint_pause_source.zip` - **Build type:** signed release, RSA-4096 (`gps2audio_release` alias), SHA-256 fingerprint matches the v2.0.x release keystore on file. ## Artifacts - APK: `/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_signed.apk` - Source: `/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_source.zip` - Handoff: `/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_handoff.md` ## Root cause v2.0.3 starts Atmo (Begleitmusik) through `BackgroundMusicManager`/`BackgroundMusicPlayer`, an **app-singleton** that owns an ExoPlayer instance. No code in MainActivity, DisposableEffect, Lifecycle observer, or `onCleared()` calls `pause()`/`stop()` on the player when the app moves to background — i.e. the app was **not explicitly** pausing Atmo on minimize. However, when the activity goes to background and the screen turns off, the Android process is moved to a "cached / restricted background" state. Because no foreground service of type `mediaPlayback` was holding the audio session, Android terminates or freezes the process within ~10–30 s, which the user observes as "Atmo stopped when minimized". When the activity is brought back to the front, the user manually re-enters Atmo or the GPS service rebinds and restarts the player — looking like "starts again on reopen". This is the canonical Android behaviour for non-foreground audio playback on API 26+ (and tightens on Android 12 / 14). The robust, documented fix is to run a `foregroundServiceType="mediaPlayback"` service whenever Atmo is alive. ## Exact change ### 1. New foreground service `AtmoPlaybackService` - File: `app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt` - `foregroundServiceType="mediaPlayback"` (uses `FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK` on API 34+). - Owns no playback logic — only signals the OS that the app is legitimately playing media in the background and posts a low-priority, silent ongoing notification (channel `atmo_playback_channel`, importance LOW). - `start(ctx,title,source)` / `stop(ctx)` companion helpers; `stopForeground` with `STOP_FOREGROUND_REMOVE` removes the notification on stop. ### 2. `BackgroundMusicPlayer` hooks the service lifecycle to the player lifetime - `buildPlayer()` → `AtmoPlaybackService.start(...)` immediately after building the ExoPlayer (i.e. when Atmo actually starts playing for a tour / test). - `releasePlayer()` → `AtmoPlaybackService.stop(ctx)`. - **Pause/duck/fade do NOT stop the service.** The service is tied to the *existence* of an active ExoPlayer instance, not to play/pause state. This is what preserves PTT, waypoint, and manual-player interruption resumes — during those, the player is paused but still allocated, so the foreground service keeps the process alive and resume works exactly as in v2.0.3. ### 3. ExoPlayer audio plumbing - `setAudioAttributes(USAGE_MEDIA / AUDIO_CONTENT_TYPE_MUSIC, handleAudioFocus=false)` — declares the stream as media. Audio-focus arbitration stays with the app-internal coordinator (`AtmoResumeManager`, `WaypointAudioInterruptionCoordinator`) exactly as before; ExoPlayer does **not** auto-pause on transient focus loss. - `setHandleAudioBecomingNoisy(true)` — when headphones are unplugged, ExoPlayer pauses (not stops). The foreground service stays up, so the user can resume from PTT/waypoint logic or the manual play button. ### 4. Manifest - Added permission `android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK` (Android 14+ requirement). - Declared the new service with `foregroundServiceType="mediaPlayback"`. ### 5. Strings (German UI, consistent with rest of app) - `atmo_notification_channel_name` — "Begleitmusik (Atmo)" - `atmo_notification_channel_description` - `atmo_notification_default_title` — "Begleitmusik läuft" - `atmo_notification_default_source` — fallback when stream/playlist label is blank. ### 6. Version bump - `versionCode 31 → 32`, `versionName 2.0.3 → 2.0.4` in `app/build.gradle.kts`. ## What was deliberately NOT changed - `MainActivity.onPause/onResume` — only WakeLockController is touched there; it manipulates the screen-on flag, never the audio. Confirmed via grep: no `pauseMusic`/`stopMusic`/`player.pause`/`player.stop` is called from any Activity / Compose lifecycle path. - `BackgroundMusicManager.loadTour()` — still stops music on intentional tour change (`previousTour != tourName` with `enabled=false`). - `BackgroundMusicManager.saveAndApplySettings()` — still stops music when the user disables Begleitmusik. - `AtmoResumeManager` (PTT pause/resume) — untouched. - `WaypointAudioInterruptionCoordinator` — untouched. - `BackgroundMusicPlayer.beforeWaypointAudio / afterWaypointAudio` — untouched. - `WakeLockController` — untouched. The window flag still drops on background (as designed in v2.0.0); audio is now decoupled from that flag. ## Manual test plan ### Primary regression (the actual user bug) 1. Set up Atmo for a tour (either local playlist or HTTP stream). 2. Open the app, start Atmo via the music dialog test button or via the start-tour flow. 3. Confirm a small ongoing notification "Begleitmusik (Atmo)" appears. 4. Press the **Home button** to minimize. Audio must keep playing. 5. Lock the screen. Wait **at least 60 seconds** with the screen off. 6. Audio must still be playing. Unlock the screen — still playing. 7. Reopen the app — the music dialog / mini-player must show "Playing" with the same track / stream still running, no glitch / restart. ### PTT still pauses Atmo (v2.0.3 contract) 1. With Atmo playing in foreground, hold PTT. 2. Atmo pauses (or fades / ducks per settings). 3. Release PTT — Atmo resumes from where it left off. 4. Repeat with the app minimized: hold PTT (e.g. via headset key if mapped), Atmo should still pause/resume. ### Waypoint audio still pauses Atmo (v2.0.3 contract) 1. Manually trigger a waypoint audio from the list, or walk into a radius. 2. Atmo follows the configured `WaypointMusicBehavior` (PAUSE_RESUME / FADE_OUT_IN / DUCK_UNDERLAY / CONTINUE_UNDERLAY). 3. After the waypoint audio finishes naturally, Atmo continues / resumes / auto-starts per `autoStartAfterWaypoint`. ### Manual player still pauses Atmo 1. Tap a waypoint to play its audio manually. 2. Atmo pauses for the duration, resumes after. ### User stop / pause / disable still works 1. Press stop in the music dialog → Atmo stops, **notification disappears**, no resume. 2. Press pause → Atmo pauses, notification stays (player still allocated), pressing play resumes. 3. Open Begleitmusik dialog → toggle "Aktiviert" off → save. Atmo stops, notification disappears. ### Tour change 1. Switch to a tour that has `enabled=false`. Atmo stops on tour switch. ### Headphone unplug 1. With wired/Bluetooth headphones connected and Atmo playing, unplug (or disconnect Bluetooth). Atmo pauses (does **not** stop) — resume by pressing play in the dialog/mini-player. ### Wake Lock interaction 1. Set Wake Lock mode to "Active tour only". Start the tour, screen stays on. 2. Press Power to lock the screen. The screen-on flag releases (expected), but Atmo keeps playing. 3. Unlock — Atmo is still playing, screen-on flag re-applied if mode dictates. ## Regression checklist - [x] PTT pauses waypoint audio (v2.0.3 feature) — unchanged code path. - [x] PTT pauses Atmo with resume — `AtmoResumeManager.notifyInterruptStart` still calls `pauseMusic()`, only releasing service on hard stop. - [x] Drag-and-drop reorder of waypoints — unrelated, untouched. - [x] Wake Lock settings (NEVER / APP_FOREGROUND / ACTIVE_TOUR) — untouched. - [x] Map provider selection — untouched. - [x] Track-Editor / drafts — untouched. - [x] Backup import / export — untouched. - [x] Map search (everywhere / current view) — untouched. - [x] Audio library playlist (drag/drop, picker) — untouched. - [x] OSM attribution badge — untouched. - [x] TabRow fix — untouched. - [x] About / PayPal / version notice — untouched. - [x] Signed-release path (keystore.properties + env vars) — unchanged. - [x] Version notice will trigger again because versionCode advanced (31 → 32). - [x] APK targets SDK 35, min SDK 26, includes the new `FOREGROUND_SERVICE_MEDIA_PLAYBACK` permission and the new service in manifest (verified via `aapt dump badging`). - [x] Signature: SHA-256 `7B:0E:A4:D5:…:BB:46` matches keystore record (verified via `apksigner verify --print-certs`). ## Limitations - The user will now see a persistent low-priority "Begleitmusik (Atmo)" notification while Atmo is loaded. This is required by Android 12+ to keep background audio reliably alive and is **the minimal acceptable solution**. It is silent (`setSilent(true)`, importance LOW, `setOnlyAlertOnce`) and disappears the moment the user presses Stop or Begleitmusik is disabled. - The notification has no media-style transport controls. A future iteration could add play/pause/next/previous through a `MediaSessionService`; left out intentionally to keep the diff minimal and avoid touching the v2.0.3 PTT / waypoint pause logic. ## Build verification ``` package: name='de.waypointaudio' versionCode='32' versionName='2.0.4' sdkVersion:'26' targetSdkVersion:'35' uses-permission: …FOREGROUND_SERVICE_MEDIA_PLAYBACK service: de.waypointaudio.service.AtmoPlaybackService (mediaPlayback) Signer #1 SHA-256: 7b0ea4d5528605173d299d7b268e7ab8768dd504ec15b66a52d47b4257b3bb46 BUILD SUCCESSFUL (gradle 8.9, AGP via wrapper config, SDK 35 build-tools 35.0.0) ```