Skip to content

Add legacy service preservation and feature flag toggle infrastructure#5145

Open
sztomek wants to merge 4 commits intomedia3/04-session-callbacksfrom
media3/05-legacy-service
Open

Add legacy service preservation and feature flag toggle infrastructure#5145
sztomek wants to merge 4 commits intomedia3/04-session-callbacksfrom
media3/05-legacy-service

Conversation

@sztomek
Copy link
Copy Markdown
Contributor

@sztomek sztomek commented Mar 18, 2026

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 the MEDIA3_SESSION feature 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

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics.

@sztomek sztomek added this to the 8.10 milestone Mar 18, 2026
@sztomek sztomek requested a review from a team as a code owner March 18, 2026 10:35
@sztomek sztomek removed the request for review from a team March 18, 2026 10:35
@sztomek sztomek requested a review from MiSikora March 18, 2026 10:35
@sztomek sztomek added the [Type] Enhancement Improve an existing feature. label Mar 18, 2026
Copilot AI review requested due to automatic review settings March 18, 2026 10:35
@sztomek sztomek added the [Area] Playback Episode playback issue label Mar 18, 2026
@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Mar 18, 2026

2 Warnings
⚠️ Class LegacyPlaybackService is missing tests, but unit-tests-exemption label was set to ignore this.
⚠️ Class MediaControllerCallback is missing tests, but unit-tests-exemption label was set to ignore this.

Generated by 🚫 Danger

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LegacyPlaybackService and added an automotive-specific LegacyAutoPlaybackService subclass.
  • Added PlaybackServiceToggle to enable/disable the appropriate service components based on the MEDIA3_SESSION feature 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.

}

Timber.d("Playback Notification State Change $state")
when (state) {

Check warning

Code scanning / Android Lint

Missing @IntDef in Switch Warning

Switch statement on an int with known associated constant missing case PlaybackStateCompat.STATE_CONNECTING, PlaybackStateCompat.STATE_FAST_FORWARDING, PlaybackStateCompat.STATE_REWINDING, PlaybackStateCompat.STATE_SKIPPING_TO_NEXT, PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS, PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the relevant PlaybackStateCompat states are handled; the unhandled cases are intentional no-ops. This is the standard pattern used throughout the codebase.

@sztomek sztomek modified the milestones: 8.10, 8.9 Mar 20, 2026
@sztomek sztomek force-pushed the media3/04-session-callbacks branch from d85dfab to 4841ed3 Compare March 27, 2026 16:03
@sztomek sztomek force-pushed the media3/05-legacy-service branch from 7992866 to 08ef9b3 Compare March 27, 2026 16:04
@wpmobilebot wpmobilebot modified the milestones: 8.9, 8.10 Mar 30, 2026
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Version 8.9 has now entered code-freeze, so the milestone of this PR has been updated to 8.10.

@sztomek sztomek force-pushed the media3/04-session-callbacks branch from 4841ed3 to 497b54c Compare March 31, 2026 17:21
Copilot AI review requested due to automatic review settings April 1, 2026 06:51
@sztomek sztomek force-pushed the media3/05-legacy-service branch from 08ef9b3 to da8949c Compare April 1, 2026 06:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-specific LegacyAutoPlaybackService subclass.
  • Introduce PlaybackServiceToggle to enable/disable service components based on Feature.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.

@sztomek sztomek force-pushed the media3/05-legacy-service branch from da8949c to bc1dd94 Compare April 1, 2026 17:55
@sztomek sztomek force-pushed the media3/04-session-callbacks branch from f992988 to 376454a Compare April 1, 2026 20:26
Copilot AI review requested due to automatic review settings April 1, 2026 20:28
@sztomek sztomek force-pushed the media3/05-legacy-service branch from bc1dd94 to 3327e0e Compare April 1, 2026 20:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +18 to +23
override fun onCreate() {
super.onCreate()
settings.setAutomotiveConnectedToMediaSession(false)

RefreshPodcastsTask.runNow(this, applicationScope)

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@sztomek sztomek force-pushed the media3/04-session-callbacks branch from 376454a to 8eceb80 Compare April 2, 2026 10:56
sztomek and others added 3 commits April 2, 2026 12:56
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>
@sztomek sztomek force-pushed the media3/05-legacy-service branch from 3327e0e to 09906b3 Compare April 2, 2026 10:56
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>
Copilot AI review requested due to automatic review settings April 2, 2026 11:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

return
}

if (sleepTimerDisposable == null || sleepTimerDisposable!!.isDisposed) {
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
if (sleepTimerDisposable == null || sleepTimerDisposable!!.isDisposed) {
if (sleepTimerDisposable?.isDisposed != false) {

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +129
override fun onDestroy() {
super.onDestroy()
isForeground = false

disposables.clear()
sleepTimerDisposable?.dispose()
job.cancel()

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +25
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")
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Project manifest changes for automotive

The following changes in the automotive's merged AndroidManifest.xml file were detected (build variant: release):

--- ./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 -->
         <service

Go to https://buildkite.com/automattic/pocket-casts-android/builds/16227/canvas?sid=019d4df3-a54a-4491-b18a-b7b388a9af6f, click on the Artifacts tab and audit the files.

</intent-filter>
</service>

<service

Check warning

Code scanning / Android Lint

Exported service does not require permission Warning

Exported service does not require permission
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Area] Playback Episode playback issue [Type] Enhancement Improve an existing feature. unit-tests-exemption

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants