From f4eef88089ec927a27c23e58cbba5a88e117f93e Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 26 Sep 2024 05:54:37 -0700 Subject: [PATCH] Ensure Media3 play calls get FGS exemption When a Media3 controller calls play on a Media3 session, the call is currently not routed through the platform session at all. This means the usual exemption to start a FGS for media controller interactions is not triggered. We can manually ensure this exemption is given by sending a custom platform command to the session. The Media3 session will never receive this command as it's not a known Media3 custom command. Sessions will see a single onConnect call with a platform controller from the sender app though. We can prevent this on newer versions of the code by dropping the onCommand call early. PiperOrigin-RevId: 679115247 --- RELEASENOTES.md | 2 ++ .../java/androidx/media3/session/MediaConstants.java | 2 ++ .../media3/session/MediaControllerImplBase.java | 11 +++++++++++ .../media3/session/MediaSessionLegacyStub.java | 7 +++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6324295640..bb663b186b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -94,6 +94,8 @@ items are available for both, `MediaBrowser` and `MediaController`. See Custom Browse actions of AAOS. + * Fix bug where a Media3 controller was sometimes unable to let a session + app start a foreground service after requesting `play()`. * UI: * Make the stretched/cropped video in `PlayerView`-in-Compose-`AndroidView` workaround opt-in, due to issues diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java index caeb9c19ed..ba171466e9 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java @@ -497,6 +497,8 @@ public final class MediaConstants { "androidx.media3.session.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED"; /* package */ static final String SESSION_COMMAND_REQUEST_SESSION3_TOKEN = "androidx.media3.session.SESSION_COMMAND_REQUEST_SESSION3_TOKEN"; + /* package */ static final String SESSION_COMMAND_MEDIA3_PLAY_REQUEST = + "androidx.media3.session.SESSION_COMMAND_MEDIA3_PLAY_REQUEST"; /* package */ static final String ARGUMENT_CAPTIONING_ENABLED = "androidx.media3.session.ARGUMENT_CAPTIONING_ENABLED"; diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java index c1e1c95699..e59c0bb81d 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -137,6 +137,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @Nullable private TextureView videoTextureView; private Size surfaceSize; @Nullable private IMediaSession iSession; + @Nullable private android.media.session.MediaController platformController; private long currentPositionMs; private long lastSetPlayWhenReadyCalledTimeMs; @Nullable private PlayerInfo pendingPlayerInfo; @@ -405,6 +406,13 @@ import org.checkerframework.checker.nullness.qual.NonNull; return; } + if (Util.SDK_INT >= 31 && platformController != null) { + // Ensure the platform session gets allow-listed to start a foreground service after receiving + // the play command. + platformController.sendCommand( + MediaConstants.SESSION_COMMAND_MEDIA3_PLAY_REQUEST, /* args= */ null, /* cb= */ null); + } + dispatchRemoteSessionTaskWithPlayerCommand( (iSession, seq) -> iSession.play(controllerStub, seq)); @@ -2644,6 +2652,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; playerInfo = result.playerInfo; MediaSession.Token platformToken = result.platformToken == null ? token.getPlatformToken() : result.platformToken; + if (platformToken != null) { + platformController = new android.media.session.MediaController(context, platformToken); + } try { // Implementation for the local binder is no-op, // so can be used without worrying about deadlock. diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java index fccc3a49b4..1019e04351 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -280,8 +280,11 @@ import org.checkerframework.checker.initialization.qual.Initialized; @Override public void onCommand(String commandName, @Nullable Bundle args, @Nullable ResultReceiver cb) { checkStateNotNull(commandName); - if (TextUtils.equals(MediaConstants.SESSION_COMMAND_REQUEST_SESSION3_TOKEN, commandName) - && cb != null) { + if (commandName.equals(MediaConstants.SESSION_COMMAND_MEDIA3_PLAY_REQUEST)) { + // Ignore, no need to handle this command here. + return; + } + if (commandName.equals(MediaConstants.SESSION_COMMAND_REQUEST_SESSION3_TOKEN) && cb != null) { cb.send(RESULT_SUCCESS, sessionImpl.getToken().toBundle()); return; }