• v2.0.6 b2aa1c167c

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

    GPS2Audio v2.0.6 — Atmo Background Playback Fix (WakeMode)

    Artifacts

    • Signed APK: /home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_signed.apk
    • Source zip: /home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_source.zip
    • This handoff: /home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_handoff.md

    Build & Signing Verification

    Field Value
    applicationId de.waypointaudio
    versionCode 34 (up from 33 in v2.0.5)
    versionName 2.0.6
    minSdk / targetSdk 26 / 35
    compileSdk 35
    Build type release, signed
    Signer DN CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DE
    Signer cert SHA-256 7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46
    Signer cert SHA-1 EB:92:2A:83:39:09:ED:6B:2D:CB:62:52:F0:CF:42:5F:26:DF:25:AD
    apksigner verify PASS (v2 scheme)
    Source baseline GPS2Audio_v2_0_5_atmo_service_hotfix_source.zip

    The signature matches the GPS2Audio release keystore used since v1 → installs
    as an update over any existing release-channel install (no uninstall required).

    uses-permission set is unchanged vs. v2.0.5 (FOREGROUND_SERVICE_MEDIA_PLAYBACK,
    POST_NOTIFICATIONS, WAKE_LOCK, etc. all preserved).

    Bug Report Summary

    User report: After the v2.0.5 hotfix, pressing Atmo stream Play no longer
    crashes (foreground-service start deadline fix worked) – but when the app is
    minimized / sent to background, the Atmo music still stops/pauses
    within a
    short time. Expected behavior: Atmo continues to play in the background until
    the user (or one of the well-defined app rules) intentionally pauses or stops
    it.

    Root Cause

    In v2.0.4 we added AtmoPlaybackService, a foregroundServiceType="mediaPlayback"
    service that prevents Android from killing the app process while Atmo is alive
    (this is what the v2.0.5 hotfix made crash-safe).

    However, a foreground service alone does NOT keep the device's CPU and
    network awake when the screen turns off.
    The FGS guarantees the process
    stays alive, but the CPU enters low-power state and the network gets
    de-prioritised. For ExoPlayer this manifests as the audio pipeline stalling /
    pausing shortly after screen-off — exactly the user's observation.

    The fix is the one ExoPlayer documents for exactly this case: call
    setWakeMode(...) on the player. This makes ExoPlayer acquire a
    PARTIAL_WAKE_LOCK (CPU stays on while playing) and, for WAKE_MODE_NETWORK,
    additionally a WifiLock so the network connection survives screen-off /
    Doze.

    In v2.0.5's BackgroundMusicPlayer.buildPlayer() we did not call
    setWakeMode(...). With no wake-mode set, ExoPlayer relies on the process
    holding the wake state externally — which, in this app, nobody did once
    FLAG_KEEP_SCREEN_ON (managed by WakeLockController) was cleared on the
    Activity going to onPause. That clearing is correct (only the screen flag
    should drop on minimise — see WakeLockController docstring), and was the
    explicit design since v2.0.0. The missing piece was the audio-pipeline wake
    lock owned by ExoPlayer itself.

    The Foreground Service notification IS being posted (v2.0.4 still works) — it's
    just that without a wake lock, the CPU sleeps and the player has nothing to
    decode with.

    Fix (exact changes)

    File: app/src/main/kotlin/de/waypointaudio/service/BackgroundMusicPlayer.kt

    Inside buildPlayer(settings), immediately after setHandleAudioBecomingNoisy(true),
    we now set the wake mode on the ExoPlayer instance:

    // Wake-Mode auf Network: hält PARTIAL_WAKE_LOCK + WifiLock während
    // der Wiedergabe – damit Stream UND lokale Playlist auch bei
    // ausgeschaltetem Display weiterspielen (v2.0.6 fix).
    // Der Foreground-Service alleine verhindert nur, dass der Prozess
    // gekillt wird – er hält aber nicht die CPU/das Netzwerk wach.
    // ExoPlayer benötigt diesen Modus auf vielen Geräten (Android 12+,
    // Doze), damit die Audio-Pipeline beim Bildschirm-Aus nicht
    // einschläft. Der Lock wird automatisch wieder freigegeben,
    // sobald `release()` oder `stop()` aufgerufen wird.
    if (isStream) {
        p.setWakeMode(C.WAKE_MODE_NETWORK)
    } else {
        p.setWakeMode(C.WAKE_MODE_LOCAL)
    }
    
    • Stream URLsWAKE_MODE_NETWORK (PARTIAL_WAKE_LOCK + WifiLock).
    • Local playlistWAKE_MODE_LOCAL (PARTIAL_WAKE_LOCK only, no WifiLock,
      because no network access is needed once tracks are loaded from local files).

    ExoPlayer releases both locks automatically on Player.stop() /
    Player.release(), which is already wired into BackgroundMusicPlayer.releasePlayer().
    Pause/resume keep the player allocated (the wake lock is held only while
    actually playing, ExoPlayer manages this internally).

    The existing WAKE_LOCK permission in AndroidManifest.xml already covers
    this — it was originally declared "for ExoPlayer", as the manifest comment
    states ("Keep CPU alive while playing audio in service"). It was just never
    being claimed because the player never asked for it.

    File: app/build.gradle.kts

    • versionCode 33 → 34
    • versionName "2.0.5" → "2.0.6"

    No other changes elsewhere.

    What was deliberately NOT changed

    • AtmoPlaybackService — kept exactly as the v2.0.5 hotfix. The
      service-start-deadline fix remains intact (still calls startForeground in
      onCreate as the very first action).
    • MainActivity.onPause/onResume — only WakeLockController is touched; it
      manipulates the screen-on flag (FLAG_KEEP_SCREEN_ON), never the audio
      wake lock. The two are deliberately separate concerns since v2.0.0.
    • WakeLockController — untouched. The screen flag still drops on background
      as designed. ExoPlayer now owns the CPU/wifi wake lock for audio, decoupled
      from screen state.
    • BackgroundMusicManager — untouched. Still an app-singleton owning the
      player; not released on Activity lifecycle (only on release(), which
      nothing currently calls).
    • BackgroundMusicPlayer.beforeWaypointAudio / afterWaypointAudio
      untouched. PTT/waypoint interruption flows unchanged.
    • AtmoResumeManager — untouched.
    • WaypointAudioInterruptionCoordinator — untouched.
    • ViewModel.onCleared() — does NOT release the music manager; only the
      manual player. Confirmed (viewmodel/WaypointViewModel.kt:1533-1539).
    • No LifecycleEventObserver / ProcessLifecycleOwner / DefaultLifecycleObserver
      exists anywhere that would pause Atmo on background — grep-verified.
    • No DisposableEffect releases the Atmo player on screen disposal
      (the two existing DisposableEffect blocks dispose only the map view and
      draft-editor map view).
    • AudioManager.requestAudioFocus is not called on the Atmo path; ExoPlayer's
      audio-focus handling is left off (handleAudioFocus = false) so the app's
      own coordinators decide pause/resume — unchanged.

    How the fix satisfies the requirements

    1. Atmo stream and local playlist continue playing when minimized/backgrounded.
      ExoPlayer now holds PARTIAL_WAKE_LOCK (+ WifiLock for streams) for the
      duration of playback, so CPU/network do not sleep when the screen turns
      off.
    2. Atmo still pauses/resumes for intentional rules (PTT, waypoint, manual
      player, user pause/stop, disable, tour change). All those call paths go
      through BackgroundMusicManager/AtmoResumeManager/beforeWaypointAudio
      – none touched. The wake lock is released by ExoPlayer when pause() is
      called and re-acquired on play(), so pause flows are not impeded.
    3. Wake Lock release on background must not affect audio.
      WakeLockController only touches FLAG_KEEP_SCREEN_ON on the Activity
      window. The audio wake lock is now an independent lock owned by
      ExoPlayer and unaffected by onActivityPaused().
    4. Foreground service is alive for the whole Atmo playback duration.
      Service start/stop is tied to buildPlayer() / releasePlayer() (i.e. to
      the existence of an ExoPlayer instance), not to Activity state. Pause /
      duck / fade keep the player allocated, so the FGS keeps running. ExoPlayer
      does not stop on Activity stop — the manager is an app-singleton, not a
      ViewModel-scoped object.
    5. Notification/service stops when Atmo is truly stopped/released by user
      or no content remains.
      releasePlayer() calls
      AtmoPlaybackService.stop(context); pause does not.
    6. Preserve all v2.0.5 features, no regressions. Manifest unchanged,
      permissions unchanged, all PTT / waypoint / drag-reorder / Wake-Lock-
      settings / map-providers / Track-Editor + drafts / backup import-export /
      map-search / audio-library / OSM-attribution / TabRow fix / About-PayPal +
      version-notice code paths untouched.
    7. Version & signing. versionCode 33 → 34, versionName 2.0.5 → 2.0.6.
      Signed with the existing GPS2Audio release keystore. apksigner verify
      passes. SHA-256 fingerprint matches all prior release builds.
    8. Artifacts produced at the requested paths. See "Artifacts" above.

    Manual Test Plan

    Primary regression (the actual user bug)

    1. Configure Atmo for a tour — either a local playlist OR a stream URL.
    2. Open the app, start Atmo via the music dialog test button or via the
      start-tour flow.
    3. Confirm the ongoing notification "Begleitmusik (Atmo)" / "Begleitmusik läuft"
      appears.
    4. Press Home to minimize the app. Audio must keep playing.
    5. Wait at least 2 minutes with the app in background.
      Audio must still be playing the whole time.
    6. (If possible) Lock the screen (Power button) and wait another 2 minutes.
      Audio must still be playing.
    7. Reopen the app — the mini-player / dialog must show the same track /
      stream still playing, no glitch / restart, no double-track, no error.

    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 / screen off — Atmo should still pause when
      PTT is engaged and resume when released.

    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/mini-player → Atmo stops,
      notification disappears within ~1 s, no resume.
    2. Press Pause → Atmo pauses, notification stays (player still allocated),
      pressing play resumes from same position.
    3. Open Begleitmusik dialog → toggle "Aktiviert" off → save. Atmo stops,
      notification disappears.

    Tour change

    1. Switch to a tour with enabled=false Atmo. Atmo stops on tour switch,
      notification disappears.
    2. Switch to a tour with a different stream/playlist with enabled=true
      new content takes over.

    Headphone unplug

    1. With wired/Bluetooth headphones connected and Atmo playing, unplug
      (or disconnect Bluetooth). Atmo pauses (does not stop) —
      setHandleAudioBecomingNoisy(true) unchanged.
    2. Press play in dialog/mini-player to resume.

    Wake Lock settings

    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 (audio wake lock is independent).
    3. Unlock — Atmo is still playing, screen-on flag re-applies if mode dictates.

    Battery / Doze sanity

    • On a stock Android device with Battery Optimisation enabled for the app,
      Atmo should still play continuously in background — PARTIAL_WAKE_LOCK
      acquired by a foreground-service-backed app is an explicitly permitted use
      case on all Android versions.

    Regression Checklist (v2.0.x feature contract)

    • 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.
    • Atmo plays in background — v2.0.4 FGS + v2.0.6 wake mode.
    • No FGS-deadline crash on Atmo stream Play — v2.0.5 hotfix preserved.
    • Drag-and-drop reorder of waypoints.
    • Wake Lock settings (Never / App foreground / Active tour) — unchanged
      semantics, audio decoupled.
    • Map providers — unchanged.
    • Track-Editor + Drafts — unchanged.
    • Backup import/export — unchanged.
    • Map search — unchanged.
    • Audio library — unchanged.
    • OSM attribution badge on map — unchanged.
    • TabRow fix — unchanged.
    • About dialog PayPal link + Matrix QR — unchanged.
    • Version-notice popup (once per new versionCode) — fires on first launch
      of 2.0.6 since versionCode bumped 33 → 34.

    Build Reproduction

    unzip GPS2Audio_v2_0_6_atmo_background_fix_source.zip -d /tmp/v2.0.6
    cd /tmp/v2.0.6/work
    cat > keystore.properties <<EOF
    storeFile=/path/to/GPS2Audio_release_keystore.jks
    storePassword=...
    keyAlias=gps2audio_release
    keyPassword=...
    EOF
    export ANDROID_HOME=/path/to/android-sdk
    gradle :app:assembleRelease
    # Output: app/build/outputs/apk/release/app-release.apk
    

    Tested toolchain: Gradle 8.9, JDK 21 (OpenJDK), Android SDK with platform-35
    and build-tools 35.0.0.

    Downloads