diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java index d9e1a3c8af0..15fb96a4747 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java @@ -857,6 +857,24 @@ public final void setSessionActivity( impl.setSessionActivity(controller, activityPendingIntent); } + /** + * Sets whether to opt out from media button (e.g. Bluetooth) playback resumption. + * + *

The default value is {@code false}. + * + *

This method will throw {@link IllegalStateException} if called when media button playback + * resumption was never opted into in the first place (by adding {@link MediaButtonReceiver} to + * the manifest). + * + * @param optOutOfMediaButtonPlaybackResumption Whether to opt out of media button playback + * resumption. + */ + @UnstableApi + public void setOptOutOfMediaButtonPlaybackResumption( + boolean optOutOfMediaButtonPlaybackResumption) { + impl.setOptOutOfMediaButtonPlaybackResumption(optOutOfMediaButtonPlaybackResumption); + } + /** * Sets the underlying {@link Player} for this session to dispatch incoming events to. * diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java index c9c2226b2fd..c4bc29e0e12 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java @@ -889,6 +889,12 @@ public MediaSession.ConnectionResult onConnectOnHandler(ControllerInfo controlle return connectionResult; } + public void setOptOutOfMediaButtonPlaybackResumption( + boolean optOutOfMediaButtonPlaybackResumption) { + sessionLegacyStub.setOptOutOfMediaButtonPlaybackResumption( + optOutOfMediaButtonPlaybackResumption); + } + public void onPostConnectOnHandler(ControllerInfo controller) { if (isMediaNotificationControllerConnected && isSystemUiController(controller)) { // Hide System UI. Apps can use the media notification controller to maintain the platform 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 1b5bd9b0c09..c616b3ea70b 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -68,6 +68,7 @@ import android.os.SystemClock; import android.text.TextUtils; import android.view.KeyEvent; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.util.ObjectsCompat; @@ -136,6 +137,7 @@ private final MediaSessionCompat sessionCompat; @Nullable private final MediaButtonReceiver runtimeBroadcastReceiver; @Nullable private final ComponentName broadcastReceiverComponentName; + private boolean optOutOfMediaButtonPlaybackResumption; @Nullable private VolumeProviderCompat volumeProviderCompat; private final boolean playIfSuppressed; @@ -191,7 +193,7 @@ public MediaSessionLegacyStub( broadcastReceiverComponentName = queryPackageManagerForMediaButtonReceiver(context); @Nullable ComponentName receiverComponentName = broadcastReceiverComponentName; boolean isReceiverComponentAService = false; - if (receiverComponentName == null || SDK_INT < 31) { + if (receiverComponentName == null && SDK_INT < 26) { // Below API 26, media button events are sent to the receiver at runtime also. We always want // these to arrive at the service at runtime. release() then set the receiver for restart if // available. @@ -201,13 +203,10 @@ public MediaSessionLegacyStub( receiverComponentName = getServiceComponentByAction(context, MediaSessionService.SERVICE_INTERFACE); } - isReceiverComponentAService = - receiverComponentName != null - && !Objects.equals(receiverComponentName, broadcastReceiverComponentName); + isReceiverComponentAService = receiverComponentName != null; } - Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri); - PendingIntent mediaButtonIntent; - if (receiverComponentName == null) { + final PendingIntent mediaButtonIntent; + if (receiverComponentName == null && SDK_INT < 26) { // Neither a media button receiver from the app manifest nor a service available that could // handle media button events. Create a runtime receiver and a pending intent for it. runtimeBroadcastReceiver = new MediaButtonReceiver(); @@ -215,23 +214,24 @@ public MediaSessionLegacyStub( filter.addDataScheme(castNonNull(sessionUri.getScheme())); Util.registerReceiverNotExported(context, runtimeBroadcastReceiver, filter); // Create a pending intent to be broadcast to the receiver. + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri); intent.setPackage(context.getPackageName()); mediaButtonIntent = PendingIntent.getBroadcast( context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE); - // Creates a fake ComponentName for MediaSessionCompat in pre-L or without a service. - receiverComponentName = new ComponentName(context, context.getClass()); } else { - intent.setComponent(receiverComponentName); - mediaButtonIntent = - isReceiverComponentAService - ? (SDK_INT >= 26 - ? PendingIntent.getForegroundService( - context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE) - : PendingIntent.getService( - context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE)) - : PendingIntent.getBroadcast( - context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE); + if (receiverComponentName != null && SDK_INT < 31) { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri); + intent.setComponent(receiverComponentName); + mediaButtonIntent = + isReceiverComponentAService + ? PendingIntent.getService( + context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE) + : PendingIntent.getBroadcast( + context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE); + } else { + mediaButtonIntent = null; + } runtimeBroadcastReceiver = null; } @@ -243,7 +243,6 @@ public MediaSessionLegacyStub( new MediaSessionCompat( context, sessionCompatId, - SDK_INT < 31 ? receiverComponentName : null, SDK_INT < 31 ? mediaButtonIntent : null, /* sessionInfo= */ tokenExtras); if (SDK_INT >= 31 && broadcastReceiverComponentName != null) { @@ -261,6 +260,15 @@ public MediaSessionLegacyStub( sessionCompat.setCallback(thisRef, handler); } + public void setOptOutOfMediaButtonPlaybackResumption( + boolean optOutOfMediaButtonPlaybackResumption) { + if (broadcastReceiverComponentName == null) { + throw new IllegalStateException( + "Media button playback resumption is not enabled, cannot opt out."); + } + this.optOutOfMediaButtonPlaybackResumption = optOutOfMediaButtonPlaybackResumption; + } + /** * Sets the available commands for the platform session. * @@ -460,22 +468,18 @@ public void start() { @SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent. public void release() { - if (SDK_INT < 31) { - if (broadcastReceiverComponentName == null) { - // No broadcast receiver available. Playback resumption not supported. - setMediaButtonReceiver(sessionCompat, /* mediaButtonReceiverIntent= */ null); - } else { - // Override the runtime receiver with the broadcast receiver for playback resumption. - Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionImpl.getUri()); - intent.setComponent(broadcastReceiverComponentName); - PendingIntent mediaButtonReceiverIntent = - PendingIntent.getBroadcast( - sessionImpl.getContext(), - /* requestCode= */ 0, - intent, - PENDING_INTENT_FLAG_MUTABLE); - setMediaButtonReceiver(sessionCompat, mediaButtonReceiverIntent); - } + if ((broadcastReceiverComponentName == null && SDK_INT < 26) + || (broadcastReceiverComponentName != null && optOutOfMediaButtonPlaybackResumption)) { + // No broadcast receiver available / opted out. Playback resumption not supported. + setMediaButtonReceiver(sessionCompat, /* mediaButtonReceiverIntent= */ null); + } else if (SDK_INT < 26) { + // Override the runtime receiver with the broadcast receiver for playback resumption. + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionImpl.getUri()); + intent.setComponent(broadcastReceiverComponentName); + PendingIntent mediaButtonReceiverIntent = + PendingIntent.getBroadcast( + sessionImpl.getContext(), /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE); + setMediaButtonReceiver(sessionCompat, mediaButtonReceiverIntent); } if (runtimeBroadcastReceiver != null) { sessionImpl.getContext().unregisterReceiver(runtimeBroadcastReceiver); @@ -2080,7 +2084,7 @@ public void onReceive(Context context, Intent intent) { @RequiresApi(31) private static final class Api31 { public static void setMediaButtonBroadcastReceiver( - MediaSessionCompat mediaSessionCompat, ComponentName broadcastReceiver) { + MediaSessionCompat mediaSessionCompat, @NonNull ComponentName broadcastReceiver) { try { ((android.media.session.MediaSession) checkNotNull(mediaSessionCompat.getMediaSession())) .setMediaButtonBroadcastReceiver(broadcastReceiver); diff --git a/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java b/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java index ee242179198..186630d2d40 100644 --- a/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java +++ b/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java @@ -325,32 +325,12 @@ public class MediaSessionCompat { public MediaSessionCompat( Context context, String tag, - @Nullable ComponentName mbrComponent, @Nullable PendingIntent mbrIntent, @Nullable Bundle sessionInfo) { if (TextUtils.isEmpty(tag)) { throw new IllegalArgumentException("tag must not be null or empty"); } - if (mbrComponent == null) { - mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context); - if (mbrComponent == null) { - Log.i(TAG, "Couldn't find a unique registered media button receiver in the given context."); - } - } - if (mbrComponent != null && mbrIntent == null) { - // construct a PendingIntent for the media button - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - // the associated intent will be handled by the component being registered - mediaButtonIntent.setComponent(mbrComponent); - mbrIntent = - PendingIntent.getBroadcast( - context, - 0 /* requestCode, ignored */, - mediaButtonIntent, - Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0); - } - if (Build.VERSION.SDK_INT >= 29) { impl = new MediaSessionImplApi29(context, tag, sessionInfo); } else if (Build.VERSION.SDK_INT >= 28) { @@ -364,7 +344,9 @@ public MediaSessionCompat( Looper myLooper = Looper.myLooper(); Handler handler = new Handler(myLooper != null ? myLooper : Looper.getMainLooper()); setCallback(new Callback() {}, handler); - impl.setMediaButtonReceiver(mbrIntent); + if (mbrIntent != null) { + impl.setMediaButtonReceiver(mbrIntent); + } controller = new MediaControllerCompat(context, this); } @@ -404,7 +386,7 @@ public void setSessionActivity(@Nullable PendingIntent pi) { * * @param mbr The {@link PendingIntent} to send the media button event to. */ - public void setMediaButtonReceiver(PendingIntent mbr) { + public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { impl.setMediaButtonReceiver(mbr); }