• v2.0.4 b2aa1c167c

    marcel released this 2026-05-15 14:31:10 +02:00 | 7 commits to main since this release

    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

    • PTT pauses waypoint audio (v2.0.3 feature) — unchanged code path.
    • PTT pauses Atmo with resume — AtmoResumeManager.notifyInterruptStart
      still calls pauseMusic(), only releasing service on hard stop.
    • Drag-and-drop reorder of waypoints — unrelated, untouched.
    • Wake Lock settings (NEVER / APP_FOREGROUND / ACTIVE_TOUR) — untouched.
    • Map provider selection — untouched.
    • Track-Editor / drafts — untouched.
    • Backup import / export — untouched.
    • Map search (everywhere / current view) — untouched.
    • Audio library playlist (drag/drop, picker) — untouched.
    • OSM attribution badge — untouched.
    • TabRow fix — untouched.
    • About / PayPal / version notice — untouched.
    • Signed-release path (keystore.properties + env vars) — unchanged.
    • Version notice will trigger again because versionCode advanced (31 → 32).
    • 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).
    • 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)
    
    Downloads