Use media button broadcast receiver above Android 31

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/media#167
PiperOrigin-RevId: 562890206
This commit is contained in:
bachinger 2023-09-05 14:21:39 -07:00 committed by Copybara-Service
parent b56bdd8da0
commit 8c0191d0cb
2 changed files with 50 additions and 13 deletions

View File

@ -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:

View File

@ -66,7 +66,9 @@ import android.support.v4.media.session.PlaybackStateCompat;
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;
@ -128,7 +130,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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;
@ -150,20 +152,24 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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;
@ -179,7 +185,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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);
@ -203,9 +209,12 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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) {
@ -244,9 +253,24 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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);
@ -633,7 +657,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
}
/* package */ boolean canResumePlaybackOnStart() {
return canResumePlaybackOnStart;
return broadcastReceiverComponentName != null;
}
private void dispatchSessionTaskWithPlayerCommand(
@ -1459,7 +1483,6 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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
@ -1478,4 +1501,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
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);
}
}
}