Skip to content

Commit

Permalink
Use media button broadcast receiver above Android 31
Browse files Browse the repository at this point in the history
This change avoids using a framework API that is deprecated since
API 31 and works around difficulties with this deprecated API on
devices from some manufacturers on API 33/34.

Manually tested with BT headphone connected to:

- Samsung SM-G920F (API 24)
- Pixel (API 28)
- Samsung SM-A515W (API 33)
- Pixel 6 Pro (API 33)

(verified manually with devices and adb output of dumpsys)

Issue: androidx#167
PiperOrigin-RevId: 562890206
  • Loading branch information
marcbaechinger authored and microkatz committed Sep 29, 2023
1 parent afe2d8a commit d524bcd
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 13 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
* Set the notifications foreground service behavior to
`FOREGROUND_SERVICE_IMMEDIATE` in `DefaultMediaNotificationProvider`
([#167](https://github.com/androidx/media/issues/167)).
* Use only
`android.media.session.MediaSession.setMediaButtonBroadcastReceiver()`
above API 31 to avoid problems with deprecated API on Samsung devices
([#167](https://github.com/androidx/media/issues/167)).
* UI:
* Downloads:
* OkHttp Extension:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.ObjectsCompat;
import androidx.media.MediaSessionManager;
import androidx.media.MediaSessionManager.RemoteUserInfo;
Expand Down Expand Up @@ -128,7 +130,7 @@
private final MediaSessionCompat sessionCompat;
private final String appPackageName;
@Nullable private final MediaButtonReceiver runtimeBroadcastReceiver;
private final boolean canResumePlaybackOnStart;
@Nullable private final ComponentName broadcastReceiverComponentName;
@Nullable private VolumeProviderCompat volumeProviderCompat;

private volatile long connectionTimeoutMs;
Expand All @@ -150,20 +152,24 @@ public MediaSessionLegacyStub(MediaSessionImpl session, Uri sessionUri, Handler
new ConnectionTimeoutHandler(
session.getApplicationHandler().getLooper(), connectedControllersManager);

// Select a media button receiver component.
ComponentName receiverComponentName = queryPackageManagerForMediaButtonReceiver(context);
// Assume an app that intentionally puts a `MediaButtonReceiver` into the manifest has
// implemented some kind of resumption of the last recently played media item.
canResumePlaybackOnStart = receiverComponentName != null;
broadcastReceiverComponentName = queryPackageManagerForMediaButtonReceiver(context);
@Nullable ComponentName receiverComponentName = broadcastReceiverComponentName;
boolean isReceiverComponentAService = false;
if (receiverComponentName == null) {
if (receiverComponentName == null || Util.SDK_INT < 31) {
// 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.
receiverComponentName =
getServiceComponentByAction(context, MediaLibraryService.SERVICE_INTERFACE);
if (receiverComponentName == null) {
receiverComponentName =
getServiceComponentByAction(context, MediaSessionService.SERVICE_INTERFACE);
}
isReceiverComponentAService = receiverComponentName != null;
isReceiverComponentAService =
receiverComponentName != null
&& !Objects.equals(receiverComponentName, broadcastReceiverComponentName);
}
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri);
PendingIntent mediaButtonIntent;
Expand All @@ -179,7 +185,7 @@ public MediaSessionLegacyStub(MediaSessionImpl session, Uri sessionUri, Handler
mediaButtonIntent =
PendingIntent.getBroadcast(
context, /* requestCode= */ 0, intent, PENDING_INTENT_FLAG_MUTABLE);
// Creates a fake ComponentName for MediaSessionCompat in pre-L.
// Creates a fake ComponentName for MediaSessionCompat in pre-L or without a service.
receiverComponentName = new ComponentName(context, context.getClass());
} else {
intent.setComponent(receiverComponentName);
Expand All @@ -203,9 +209,12 @@ public MediaSessionLegacyStub(MediaSessionImpl session, Uri sessionUri, Handler
new MediaSessionCompat(
context,
sessionCompatId,
receiverComponentName,
mediaButtonIntent,
Util.SDK_INT < 31 ? receiverComponentName : null,
Util.SDK_INT < 31 ? mediaButtonIntent : null,
session.getToken().getExtras());
if (Util.SDK_INT >= 31 && broadcastReceiverComponentName != null) {
Api31.setMediaButtonBroadcastReceiver(sessionCompat, broadcastReceiverComponentName);
}

@Nullable PendingIntent sessionActivity = session.getSessionActivity();
if (sessionActivity != null) {
Expand Down Expand Up @@ -244,9 +253,24 @@ public void start() {
sessionCompat.setActive(true);
}

@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent.
public void release() {
if (!canResumePlaybackOnStart) {
setMediaButtonReceiver(sessionCompat, /* mediaButtonReceiverIntent= */ null);
if (Util.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 (runtimeBroadcastReceiver != null) {
sessionImpl.getContext().unregisterReceiver(runtimeBroadcastReceiver);
Expand Down Expand Up @@ -633,7 +657,7 @@ public ConnectedControllersManager<RemoteUserInfo> getConnectedControllersManage
}

/* package */ boolean canResumePlaybackOnStart() {
return canResumePlaybackOnStart;
return broadcastReceiverComponentName != null;
}

private void dispatchSessionTaskWithPlayerCommand(
Expand Down Expand Up @@ -1459,7 +1483,6 @@ private static ComponentName getServiceComponentByAction(Context context, String
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
}

// TODO(b/193193462): Replace this with androidx.media.session.MediaButtonReceiver
private final class MediaButtonReceiver extends BroadcastReceiver {

@Override
Expand All @@ -1478,4 +1501,14 @@ public void onReceive(Context context, Intent intent) {
getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
}
}

@RequiresApi(31)
private static final class Api31 {
@DoNotInline
public static void setMediaButtonBroadcastReceiver(
MediaSessionCompat mediaSessionCompat, ComponentName broadcastReceiver) {
((android.media.session.MediaSession) mediaSessionCompat.getMediaSession())
.setMediaButtonBroadcastReceiver(broadcastReceiver);
}
}
}

0 comments on commit d524bcd

Please sign in to comment.