-
released this
2026-05-15 14:32:13 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.7 — Atmo Service-Player Handoff
Version: 2.0.7 · versionCode: 35 · Build type: Release (signed)
Baseline: v2.0.6 (GPS2Audio_v2_0_6_atmo_background_fix_source.zip)
Signing:GPS2Audio_release_keystore.jks(aliasgps2audio_release)
Signature scheme: APK Signature Scheme v2 (verified)
Package:de.waypointaudio· applicationId unchanged.Artifacts
Path Purpose /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_signed.apkSigned release APK (≈15 MB) /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_source.zipFull source tree (no build dir, no keystore) /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_handoff.mdThis document
Root cause (why v2.0.6 still failed)
Even after the wake-mode fix in v2.0.6, the ExoPlayer instance was still owned by
an application-process singleton (BackgroundMusicPlayer, held by
BackgroundMusicManager). TheAtmoPlaybackServicewas a shell foreground
service: it only posted a notification and held themediaPlaybackforeground
type — it did not own the player.This split ownership is the architecture that Android 12+ specifically
penalises. The OS heuristic that allows background audio to survive
screen-off and process-cached transitions is:A
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACKservice must be the visible owner
of the audio playback. Detached playback (player in app process, FGS
elsewhere) is increasingly treated as a candidate for pause/throttle.In addition, the service in v2.0.6 was
START_NOT_STICKYand self-stopped on
anyintent.action == nullre-delivery. Combined with split ownership, any
brief process-state transition (e.g. Activity → background while another FGS
context is busy) could cause the ExoPlayer to lose its execution priority and
the audio pipeline to pause —setWakeMode()alone is not enough on
heavily-Doze'd OEMs.Architectural change in v2.0.7
The fix is to give the foreground service direct ownership of the
ExoPlayer. The Activity / ViewModel / Manager no longer touch the player
directly.┌───────────────────────────────────────┐ │ AtmoPlaybackService │ │ (foreground · mediaPlayback) │ │ │ │ ExoPlayer ◀── owns & holds │ │ • setWakeMode │ │ • AudioAttributes (USAGE_MEDIA) │ │ • setHandleAudioBecomingNoisy │ │ │ │ StateFlow<MusicPlaybackState> │ │ ── companion-singleton, lives even │ │ when service is down │ └───────────────┬───────────────────────┘ ▲ Intent actions │ StateFlow.collect (START / PAUSE / RESUME / │ STOP / NEXT / PREV / │ SET_VOLUME / UPDATE_SETTINGS)│ │ ┌─────────────────────────────┴────────────────────────┐ │ │ BackgroundMusicPlayer UI / ViewModel (thin facade — same (no API change) public API as v2.0.6) ▲ │ BackgroundMusicManager (app singleton, unchanged)What changed in code
-
AtmoPlaybackService(rewritten)- Owns the
ExoPlayerinstance fromapplySettingsFromIntent()until
ACTION_STOP/onDestroy. - Exposes a
companion objectstate: StateFlow<MusicPlaybackState>that
survives between service lifecycles (always reflects current truth). - Handles
ACTION_START,ACTION_UPDATE_SETTINGS,ACTION_PAUSE,
ACTION_RESUME,ACTION_NEXT,ACTION_PREVIOUS,ACTION_SET_VOLUME,
ACTION_STOP. - Returns
START_STICKY(wasSTART_NOT_STICKY): a system-induced
restart no longer terminates playback. - Null/unknown intent actions on re-delivery leave the player intact
(v2.0.6 self-stopped — that was a bug that compounded background kills). - Adds a
Stopaction button to the notification so the user can stop
Atmo from outside the app. startForeground()is still called as the very first action in
onCreate()(preserves the v2.0.5 fix for
ForegroundServiceDidNotStartInTimeException).- Wraps
startForegroundin try/catch so a deniedPOST_NOTIFICATIONS
permission cannot crash the service.
- Owns the
-
BackgroundMusicPlayer(rewritten as facade)- Same public API as before — drop-in for
BackgroundMusicManagerand
WaypointViewModel. No call sites changed. play()/pause()/stop()/next()/previous()/
setVolume()all route intoAtmoPlaybackServicecompanion intents.isPlaying/hasContentread fromAtmoPlaybackService.state.playbackState: StateFlow<MusicPlaybackState>is now a direct alias of
the service's companion flow, so UI observers see the canonical truth.beforeWaypointAudio()/afterWaypointAudio()preserve pause/resume,
fade-out/in and duck behaviour. Fades are still implemented locally —
they just callAtmoPlaybackService.setVolume()repeatedly instead of
mutating an owned player.
- Same public API as before — drop-in for
-
No changes in
BackgroundMusicManager,AtmoResumeManager,
WaypointViewModel,BegleitmusikDialog,WaypointListScreen,
LivePttManager,WaypointAudioInterruptionCoordinator— the facade
keeps their interfaces stable. -
AndroidManifest.xmlunchanged. The
FOREGROUND_SERVICE_MEDIA_PLAYBACKpermission and
android:foregroundServiceType="mediaPlayback"onAtmoPlaybackService
were already present in v2.0.6. -
build.gradle.kts:versionCode 34 → 35,versionName 2.0.6 → 2.0.7.
Everything else unchanged.
Lifecycle / service ownership behaviour (exact)
Event Effect on service Effect on ExoPlayer User taps "Play" / play()first timestartForegroundService→ACTION_STARTCreated inside service; play()User taps "Pause" ACTION_PAUSEplayer.pause()— service staysUser taps "Stop" (UI or notification) ACTION_STOPstop()+release(), thenstopSelf()User changes Atmo source (Begleitmusik-Dialog Save) ACTION_UPDATE_SETTINGS(orACTION_STARTif not running)Source compared; rebuilt only if different Begleitmusik checkbox disabled Manager calls player.stop()→ACTION_STOPService ends Tour switched (Atmo disabled in new tour) Manager calls player.stop()Service ends App minimised (Home / Recents) No effect Continues Screen off / Doze No effect (FGS + wakeMode) Continues App swiped from Recents onTaskRemoved— service intentionally keeps runningContinues; user can stop from notification PTT begins AtmoResumeManager→pauseMusic()→ACTION_PAUSEpause(); service staysPTT ends AtmoResumeManager→player.play()→ACTION_RESUMEplay()Waypoint audio begins (PAUSE_RESUME) beforeWaypointAudio→pausepause()Waypoint audio begins (DUCK_UNDERLAY) fade ACTION_SET_VOLUMEto duckVolumevolume ramps; play continues Waypoint audio ends afterWaypointAudio→ resume / fade-upas configured Manual player begins same as waypoint (via musicManager.beforeWaypointAudio)as configured Manual player ends resume / fade-up as configured System OOM-kill of process (very rare with FGS) START_STICKY→ service restarted, no stale playerPlayer will be empty until user re-starts Manual test plan
Build and install the new APK (uninstall older v2.0.x first or use a different
applicationId variant to keep installed in parallel). Then test:-
Stream — backgrounded with screen off (≥ 5 min)
- Configure a HTTP stream URL in Begleitmusik.
- Start playback. Press Home. Lock the screen.
- Wait 5 minutes. Audio must continue without interruption.
- Unlock — playback should still be active; "Stopp" button in
Atmo-Notification works.
-
Local playlist — backgrounded with screen off (≥ 5 min)
- Add 3+ local audio files to the playlist (with Shuffle on or off).
- Start playback. Press Home. Lock the screen.
- Wait 5 minutes spanning at least one track transition.
- Track must auto-advance, audio must not stop.
-
App swipe from Recents
- Start Atmo. Open Recents. Swipe the app away.
- Audio must continue.
- Tap the Atmo notification → app reopens cleanly.
- Tap "Stopp" → audio stops; notification disappears.
-
PTT interruption
- Start Atmo. Tap-and-hold PTT — Atmo must pause.
- Release PTT — Atmo must resume from same position.
- Repeat 3× — no leak; no stale pauses.
-
Waypoint audio interruption (each behaviour)
- For each
WaypointMusicBehavior(PAUSE_RESUME, FADE_OUT_IN,
DUCK_UNDERLAY, CONTINUE_UNDERLAY): trigger a waypoint with audio.
Behaviour must match v2.0.6 semantics. - After waypoint completes, Atmo resumes (if it was playing prior).
autoStartAfterWaypoint=true: if Atmo was paused/stopped, it starts
fresh after the first waypoint.
- For each
-
Manual single-file / playlist player vs. Atmo
- Tap any waypoint card's play icon — manual file plays, Atmo ducks/pauses
per behaviour. - Tap stop / pause on manual — Atmo resumes.
- Use playlist mode play/pause/next/prev — Atmo behaviour identical.
- Tap any waypoint card's play icon — manual file plays, Atmo ducks/pauses
-
Begleitmusik-Dialog Cancel/Save
- Open dialog while Atmo plays. Cancel — Atmo stops (intentional, kept
v2.0.6 behaviour; user cancelled the config session). - Open dialog. Change source. Save — service updates / rebuilds player.
- Open dialog while Atmo plays. Cancel — Atmo stops (intentional, kept
-
Tour switch
- Switch to a tour with Atmo disabled — Atmo stops, service ends.
- Switch to a tour with Atmo enabled — Atmo state remains stopped until
user presses Play.
-
Notification permission denied
- Revoke POST_NOTIFICATIONS in system settings.
- Start Atmo — no crash; service runs (silent notification absent).
-
Doze / Battery saver
- On a device with aggressive OEM Doze (Xiaomi, Huawei, OnePlus):
with Atmo running and screen off for 10 minutes, audio must persist.
- On a device with aggressive OEM Doze (Xiaomi, Huawei, OnePlus):
Regression checklist (preserved from v2.0.6)
- PTT pauses waypoint audio (
WaypointAudioInterruptionCoordinator) - PTT pauses & resumes Atmo (
AtmoResumeManager) - Drag-and-drop reorder of waypoints and tour tabs
- Wake Lock settings dialog and controller
- Map providers (OSM / fallback)
- Track-Editor and Drafts archive
- Backup import/export (ZIP)
- Map search (Nominatim)
- Audio library cross-tour
- OSM attribution
- ScrollableTabRow fix
- About dialog with PayPal link and version notice on first open
Known non-issues
MainActivity.launchWhenStarteddeprecation warning — pre-existing in
v2.0.6, not a runtime concern. Replaced only when Lifecycle APIs are
next refactored.Modifier.menuAnchor()deprecation — Compose Material3 1.3+; cosmetic.Polyline.fillColor/strokeColor/strokeWidthdeprecation — osmdroid
9.0.2; cosmetic.
Build details
package: de.waypointaudio versionCode=35 versionName=2.0.7 minSdk=26 targetSdk=35 compileSdk=35 SHA-256 cert: 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 Built with: Gradle 8.9, OpenJDK 21.0.11, Android SDK build-tools 35.0.0 APK size: 15.13 MBDownloads
-