Add legacy service preservation and feature flag toggle infrastructure#5145
Add legacy service preservation and feature flag toggle infrastructure#5145sztomek wants to merge 4 commits intomedia3/04-session-callbacksfrom
Conversation
Generated by 🚫 Danger |
There was a problem hiding this comment.
Pull request overview
This PR is part of the Media3 migration, preserving the existing MediaBrowserServiceCompat playback service implementation under new “legacy” service classes and adding a feature-flag-based toggle to switch between legacy and Media3 service components at runtime.
Changes:
- Extracted legacy playback service logic into
LegacyPlaybackServiceand added an automotive-specificLegacyAutoPlaybackServicesubclass. - Added
PlaybackServiceToggleto enable/disable the appropriate service components based on theMEDIA3_SESSIONfeature flag. - Added a ProGuard keep rule intended to retain the legacy service during minification.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| proguard-rules.pro | Adds a keep rule for the new legacy playback service class. |
| modules/services/repositories/.../PlaybackServiceToggle.kt | Introduces runtime component enable/disable logic based on a feature flag. |
| modules/services/repositories/.../LegacyPlaybackService.kt | New legacy MediaBrowserServiceCompat implementation extracted from existing playback service logic. |
| automotive/.../LegacyAutoPlaybackService.kt | Automotive-specific legacy service subclass with refresh/pause behavior. |
You can also share your feedback on Copilot code review. Take the survey.
.../src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackServiceToggle.kt
Show resolved
Hide resolved
.../src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/LegacyPlaybackService.kt
Show resolved
Hide resolved
.../src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/LegacyPlaybackService.kt
Show resolved
Hide resolved
.../src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackServiceToggle.kt
Show resolved
Hide resolved
automotive/src/main/java/au/com/shiftyjelly/pocketcasts/LegacyAutoPlaybackService.kt
Fixed
Show fixed
Hide fixed
| } | ||
|
|
||
| Timber.d("Playback Notification State Change $state") | ||
| when (state) { |
Check warning
Code scanning / Android Lint
Missing @IntDef in Switch Warning
There was a problem hiding this comment.
Only the relevant PlaybackStateCompat states are handled; the unhandled cases are intentional no-ops. This is the standard pattern used throughout the codebase.
d85dfab to
4841ed3
Compare
7992866 to
08ef9b3
Compare
|
Version |
4841ed3 to
497b54c
Compare
08ef9b3 to
da8949c
Compare
There was a problem hiding this comment.
Pull request overview
This PR is part of the Media3 migration stack, extracting the existing PlaybackService (MediaBrowserServiceCompat) behavior into a preserved LegacyPlaybackService and adding a small utility to toggle which service component is enabled at runtime via the MEDIA3_SESSION feature flag.
Changes:
- Add
LegacyPlaybackService(legacy MediaBrowserServiceCompat implementation) and an automotive-specificLegacyAutoPlaybackServicesubclass. - Introduce
PlaybackServiceToggleto enable/disable service components based onFeature.MEDIA3_SESSION. - Add an R8/ProGuard keep rule for
LegacyPlaybackService.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| proguard-rules.pro | Adds a keep rule for the legacy playback service class. |
| modules/services/repositories/.../PlaybackServiceToggle.kt | Adds feature-flag-driven component enable/disable utility for swapping service implementations. |
| modules/services/repositories/.../LegacyPlaybackService.kt | Introduces preserved legacy playback service logic (notifications, browsing, sleep timer). |
| automotive/.../LegacyAutoPlaybackService.kt | Adds an automotive-specific legacy service subclass mirroring the existing automotive service behavior. |
.../src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/LegacyPlaybackService.kt
Show resolved
Hide resolved
da8949c to
bc1dd94
Compare
f992988 to
376454a
Compare
bc1dd94 to
3327e0e
Compare
| override fun onCreate() { | ||
| super.onCreate() | ||
| settings.setAutomotiveConnectedToMediaSession(false) | ||
|
|
||
| RefreshPodcastsTask.runNow(this, applicationScope) | ||
|
|
There was a problem hiding this comment.
LegacyPlaybackService.onCreate() can early-return after calling stopSelf() when mediaSession is null (flag mismatch). This subclass continues executing after super.onCreate() regardless, which can trigger settings writes and RefreshPodcastsTask.runNow() even though the service is shutting down. Add a guard (e.g., return early if mediaController/notificationManager wasn’t initialized or if playbackManager.mediaSessionManager.mediaSession is null) to avoid running automotive side effects in the mismatched/stopSelf path.
376454a to
8eceb80
Compare
Add SupervisorJob to LegacyPlaybackService coroutine context and cancel it in onDestroy() so coroutines are properly cleaned up. Fix misleading proguard comment to match the actual rule behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3327e0e to
09906b3
Compare
The class was introduced in this branch but the manifest entry was deferred to 06d, causing lint to fail on branches 05 through 06c. Register it now with android:enabled="false" — PlaybackServiceToggle enables the correct service at runtime. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| return | ||
| } | ||
|
|
||
| if (sleepTimerDisposable == null || sleepTimerDisposable!!.isDisposed) { |
There was a problem hiding this comment.
sleepTimerDisposable!!.isDisposed introduces a forced unwrap on a nullable Disposable. This can be rewritten with safe calls to avoid potential NPEs and align with the codebase’s preference to avoid !! (e.g., check sleepTimerDisposable?.isDisposed != false).
| if (sleepTimerDisposable == null || sleepTimerDisposable!!.isDisposed) { | |
| if (sleepTimerDisposable?.isDisposed != false) { |
| override fun onDestroy() { | ||
| super.onDestroy() | ||
| isForeground = false | ||
|
|
||
| disposables.clear() | ||
| sleepTimerDisposable?.dispose() | ||
| job.cancel() | ||
|
|
There was a problem hiding this comment.
mediaController registers a MediaControllerCompat.Callback, but the service never unregisters it in onDestroy(). Because the callback is an inner class holding a reference to the service, this can keep the service instance reachable and/or deliver callbacks after teardown. Unregister the callback (and clear the controller reference) during onDestroy() (and consider unregistering the previous callback if mediaController is ever replaced).
| class LegacyAutoPlaybackService : LegacyPlaybackService() { | ||
|
|
||
| @Inject @ApplicationScope | ||
| lateinit var applicationScope: CoroutineScope | ||
|
|
||
| override fun onCreate() { | ||
| super.onCreate() | ||
| settings.setAutomotiveConnectedToMediaSession(false) | ||
|
|
||
| RefreshPodcastsTask.runNow(this, applicationScope) | ||
|
|
||
| Timber.d("Legacy auto playback service created") | ||
| } |
There was a problem hiding this comment.
LegacyAutoPlaybackService doesn’t override onLoadChildren(). The existing AutoPlaybackService adds automotive-specific behavior there (IO dispatcher + podcastManager.refreshPodcastsIfRequired("Automotive")). If this legacy service is intended to be a drop-in replacement behind a feature flag, it likely needs the same override (and dependency injection) to avoid behavior differences/regressions when the legacy path is enabled.
Project manifest changes for automotiveThe following changes in the --- ./build/reports/diff_manifest/automotive/release/base_manifest.txt 2026-04-02 11:32:52.675287290 +0000
+++ ./build/reports/diff_manifest/automotive/release/head_manifest.txt 2026-04-02 11:32:55.455295561 +0000
@@ -105,6 +105,16 @@
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
+ <service
+ android:name="au.com.shiftyjelly.pocketcasts.LegacyAutoPlaybackService"
+ android:enabled="false"
+ android:exported="true"
+ android:foregroundServiceType="mediaPlayback"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent-filter>
+ </service>
<!-- services which are periodic or trigerred by the app automatically triggered, eg: can run in the background -->
<serviceGo to https://buildkite.com/automattic/pocket-casts-android/builds/16227/canvas?sid=019d4df3-a54a-4491-b18a-b7b388a9af6f, click on the |
Description
This is the 5th step of the media3 migration. This PR preserves the existing PlaybackService logic by extracting it into
LegacyPlaybackService(a MediaBrowserServiceCompat) and its thin LegacyAutoPlaybackService subclass, so the old service can continue to run alongside the new Media3 implementation. A PlaybackServiceToggle utility was added to resolve the correct service class based on theMEDIA3_SESSIONfeature flag, enabling a safe runtime switch between legacy and Media3 paths.A ProGuard keep rule was also added to ensure the legacy service class isn't stripped during minification.
Testing Instructions
Just review the code since the new components are not wired up yet
Checklist
./gradlew spotlessApplyto automatically apply formatting/linting)modules/services/localization/src/main/res/values/strings.xml