mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Use MediaSessionImpl.onMediaButtonEvent() to dispatch key events
This change moves the handling of any media button event into `MediaSessionImpl.onMediaButtonEvent(intent)`. This includes the double click handling from `MediaSessionLegacyStub`. The advantage is that everything is in one place which allows to offer `MediaSession.Callback.onMediaButtonEvent` with which an app can override the default implementation and handle media buttons in a custom way. Media button events can originate from various places: - Delivered to `MediaSessionService.onStartCommand(Intent)` - A `PendingIntent` from the notification below API 33 - An `Intent` sent to the `MediaButtonReceiver` by the system dispatched to the service - Delivered to `MediaSessionCompat.Callback.onMediaButtonEvent(Intent)` implemented by `MediaSessionLegacyStub` during the session is active - Bluetooth (headset/remote control) - Apps/system using `AudioManager.dispatchKeyEvent(KeyEvent)` - Apps/system using `MediaControllerCompat.dispatchKeyEvent(keyEvent)` Issue: androidx/media#12 Issue: androidx/media#159 Issue: androidx/media#216 Issue: androidx/media#249 #minor-release PiperOrigin-RevId: 575231251 (cherry picked from commit a79d44edc5c7fdc81120dbc9b2c89b9799b14031)
This commit is contained in:
parent
47a451abf7
commit
f2cf43ccd5
@ -37,6 +37,8 @@
|
||||
(([#339](https://github.com/androidx/media/issues/339)).
|
||||
* Use `DataSourceBitmapLoader` by default instead of `SimpleBitmapLoader`
|
||||
([#271](https://github.com/androidx/media/issues/271),[#327](https://github.com/androidx/media/issues/327)).
|
||||
* Add `MediaSession.Callback.onMediaButtonEvent(Intent)` that allows apps
|
||||
to override the default media button event handling.
|
||||
* UI:
|
||||
* Downloads:
|
||||
* OkHttp Extension:
|
||||
|
@ -24,6 +24,7 @@ import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -1444,6 +1445,32 @@ public class MediaSession {
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFailedFuture(new UnsupportedOperationException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a media button event has been received by the session.
|
||||
*
|
||||
* <p>Media3 handles media button events internally. An app can override the default behaviour
|
||||
* by overriding this method.
|
||||
*
|
||||
* <p>Return true to stop propagating the event any further. When false is returned, Media3
|
||||
* handles the event and calls {@linkplain MediaSession#getPlayer() the session player}
|
||||
* accordingly.
|
||||
*
|
||||
* <p>Apps normally don't need to override this method. When overriding this method, an app
|
||||
* can/needs to handle all API-level specifics on its own. The intent passed to this method can
|
||||
* come directly from the system that routed a media key event (for instance sent by Bluetooth)
|
||||
* to your session.
|
||||
*
|
||||
* @param session The session that received the media button event.
|
||||
* @param controllerInfo The controller to which the media button event is attributed to.
|
||||
* @param intent The media button intent.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
@UnstableApi
|
||||
default boolean onMediaButtonEvent(
|
||||
MediaSession session, ControllerInfo controllerInfo, Intent intent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Representation of a list of {@linkplain MediaItem media items} and where to start playing. */
|
||||
|
@ -29,7 +29,6 @@ import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS;
|
||||
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.SDK_INT;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
|
||||
@ -52,6 +51,7 @@ import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.GuardedBy;
|
||||
@ -116,6 +116,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
private final Uri sessionUri;
|
||||
private final PlayerInfoChangedHandler onPlayerInfoChangedHandler;
|
||||
private final MediaPlayPauseKeyHandler mediaPlayPauseKeyHandler;
|
||||
private final MediaSession.Callback callback;
|
||||
private final Context context;
|
||||
private final MediaSessionStub sessionStub;
|
||||
@ -161,28 +162,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
BitmapLoader bitmapLoader,
|
||||
boolean playIfSuppressed,
|
||||
boolean isPeriodicPositionUpdateEnabled) {
|
||||
this.context = context;
|
||||
this.instance = instance;
|
||||
this.context = context;
|
||||
sessionId = id;
|
||||
this.sessionActivity = sessionActivity;
|
||||
this.customLayout = customLayout;
|
||||
this.callback = callback;
|
||||
this.bitmapLoader = bitmapLoader;
|
||||
this.playIfSuppressed = playIfSuppressed;
|
||||
this.isPeriodicPositionUpdateEnabled = isPeriodicPositionUpdateEnabled;
|
||||
|
||||
@SuppressWarnings("nullness:assignment")
|
||||
@Initialized
|
||||
MediaSessionImpl thisRef = this;
|
||||
|
||||
sessionStub = new MediaSessionStub(thisRef);
|
||||
this.sessionActivity = sessionActivity;
|
||||
this.customLayout = customLayout;
|
||||
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
applicationHandler = new Handler(player.getApplicationLooper());
|
||||
this.callback = callback;
|
||||
this.bitmapLoader = bitmapLoader;
|
||||
this.playIfSuppressed = playIfSuppressed;
|
||||
this.isPeriodicPositionUpdateEnabled = isPeriodicPositionUpdateEnabled;
|
||||
Looper applicationLooper = player.getApplicationLooper();
|
||||
applicationHandler = new Handler(applicationLooper);
|
||||
|
||||
playerInfo = PlayerInfo.DEFAULT;
|
||||
onPlayerInfoChangedHandler = new PlayerInfoChangedHandler(player.getApplicationLooper());
|
||||
onPlayerInfoChangedHandler = new PlayerInfoChangedHandler(applicationLooper);
|
||||
mediaPlayPauseKeyHandler = new MediaPlayPauseKeyHandler(applicationLooper);
|
||||
|
||||
sessionId = id;
|
||||
// Build Uri that differentiate sessions across the creation/destruction in PendingIntent.
|
||||
// Here's the reason why Session ID / SessionToken aren't suitable here.
|
||||
// - Session ID
|
||||
@ -280,6 +283,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
mediaPlayPauseKeyHandler.clearPendingPlayPauseTask();
|
||||
applicationHandler.removeCallbacksAndMessages(null);
|
||||
try {
|
||||
postOrRun(
|
||||
@ -1080,7 +1084,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
(callback, seq) -> callback.onDeviceInfoChanged(seq, playerInfo.deviceInfo));
|
||||
}
|
||||
|
||||
/* package */ boolean onMediaButtonEvent(Intent intent) {
|
||||
/**
|
||||
* Returns true if the media button event was handled, false otherwise.
|
||||
*
|
||||
* <p>Must be called on the application thread of the session.
|
||||
*
|
||||
* @param callerInfo The calling {@link ControllerInfo}.
|
||||
* @param intent The media button intent.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
/* package */ boolean onMediaButtonEvent(ControllerInfo callerInfo, Intent intent) {
|
||||
KeyEvent keyEvent = DefaultActionFactory.getKeyEvent(intent);
|
||||
ComponentName intentComponent = intent.getComponent();
|
||||
if (!Objects.equals(intent.getAction(), Intent.ACTION_MEDIA_BUTTON)
|
||||
@ -1090,18 +1103,66 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|| keyEvent.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
ControllerInfo controllerInfo = getMediaNotificationControllerInfo();
|
||||
if (controllerInfo == null) {
|
||||
if (intentComponent != null) {
|
||||
// Fallback to legacy if this is a media button event sent to one of our components.
|
||||
return getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent)
|
||||
|| SDK_INT < 21;
|
||||
}
|
||||
return false;
|
||||
|
||||
verifyApplicationThread();
|
||||
if (callback.onMediaButtonEvent(instance, callerInfo, intent)) {
|
||||
// Event handled by app callback.
|
||||
return true;
|
||||
}
|
||||
// Double tap detection.
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
boolean doubleTapCompleted = false;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
if (callerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION
|
||||
|| keyEvent.getRepeatCount() != 0) {
|
||||
// Double tap detection is only for media button events from external sources
|
||||
// (for instance Bluetooth) and excluding long press (repeatCount > 0).
|
||||
mediaPlayPauseKeyHandler.flush();
|
||||
} else if (mediaPlayPauseKeyHandler.hasPendingPlayPauseTask()) {
|
||||
// A double tap arrived. Clear the pending playPause task.
|
||||
mediaPlayPauseKeyHandler.clearPendingPlayPauseTask();
|
||||
doubleTapCompleted = true;
|
||||
} else {
|
||||
// Handle event with a delayed callback that's run if no double tap arrives in time.
|
||||
mediaPlayPauseKeyHandler.setPendingPlayPauseTask(callerInfo, keyEvent);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// If another key is pressed within double tap timeout, make play/pause as a single tap to
|
||||
// handle media keys in order.
|
||||
mediaPlayPauseKeyHandler.flush();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isMediaNotificationControllerConnected()) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE && doubleTapCompleted) {
|
||||
// Double tap completion for legacy when media notification controller is disabled.
|
||||
sessionLegacyStub.onSkipToNext();
|
||||
return true;
|
||||
} else if (callerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||
sessionLegacyStub.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
return true;
|
||||
}
|
||||
// This is an unhandled framework event. Return false to let the framework resolve by calling
|
||||
// `MediaSessionCompat.Callback.onXyz()`.
|
||||
return false;
|
||||
}
|
||||
// Send from media notification controller.
|
||||
return applyMediaButtonKeyEvent(keyEvent, doubleTapCompleted);
|
||||
}
|
||||
|
||||
private boolean applyMediaButtonKeyEvent(KeyEvent keyEvent, boolean doubleTapCompleted) {
|
||||
ControllerInfo controllerInfo = checkNotNull(instance.getMediaNotificationControllerInfo());
|
||||
Runnable command;
|
||||
switch (keyEvent.getKeyCode()) {
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
if ((keyCode == KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KEYCODE_MEDIA_PLAY)
|
||||
&& doubleTapCompleted) {
|
||||
keyCode = KEYCODE_MEDIA_NEXT;
|
||||
}
|
||||
switch (keyCode) {
|
||||
case KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
command =
|
||||
getPlayerWrapper().getPlayWhenReady()
|
||||
@ -1653,6 +1714,56 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for double click detection.
|
||||
*
|
||||
* <p>All methods must be called on the application thread.
|
||||
*/
|
||||
private class MediaPlayPauseKeyHandler extends Handler {
|
||||
|
||||
@Nullable private Runnable playPauseTask;
|
||||
|
||||
public MediaPlayPauseKeyHandler(Looper applicationLooper) {
|
||||
super(applicationLooper);
|
||||
}
|
||||
|
||||
public void setPendingPlayPauseTask(ControllerInfo controllerInfo, KeyEvent keyEvent) {
|
||||
playPauseTask =
|
||||
() -> {
|
||||
if (isMediaNotificationController(controllerInfo)) {
|
||||
applyMediaButtonKeyEvent(keyEvent, /* doubleTapCompleted= */ false);
|
||||
} else {
|
||||
sessionLegacyStub.handleMediaPlayPauseOnHandler(
|
||||
checkNotNull(controllerInfo.getRemoteUserInfo()));
|
||||
}
|
||||
playPauseTask = null;
|
||||
};
|
||||
postDelayed(playPauseTask, ViewConfiguration.getDoubleTapTimeout());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Runnable clearPendingPlayPauseTask() {
|
||||
if (playPauseTask != null) {
|
||||
removeCallbacks(playPauseTask);
|
||||
Runnable task = playPauseTask;
|
||||
playPauseTask = null;
|
||||
return task;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasPendingPlayPauseTask() {
|
||||
return playPauseTask != null;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
@Nullable Runnable task = clearPendingPlayPauseTask();
|
||||
if (task != null) {
|
||||
postOrRun(this, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerInfoChangedHandler extends Handler {
|
||||
|
||||
private static final int MSG_PLAYER_INFO_CHANGED = 1;
|
||||
|
@ -65,7 +65,6 @@ import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
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;
|
||||
@ -126,9 +125,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
private final MediaSessionManager sessionManager;
|
||||
private final ControllerLegacyCbForBroadcast controllerLegacyCbForBroadcast;
|
||||
private final ConnectionTimeoutHandler connectionTimeoutHandler;
|
||||
private final MediaPlayPauseKeyHandler mediaPlayPauseKeyHandler;
|
||||
private final MediaSessionCompat sessionCompat;
|
||||
private final String appPackageName;
|
||||
@Nullable private final MediaButtonReceiver runtimeBroadcastReceiver;
|
||||
@Nullable private final ComponentName broadcastReceiverComponentName;
|
||||
@Nullable private VolumeProviderCompat volumeProviderCompat;
|
||||
@ -141,11 +138,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
public MediaSessionLegacyStub(MediaSessionImpl session, Uri sessionUri, Handler handler) {
|
||||
sessionImpl = session;
|
||||
Context context = sessionImpl.getContext();
|
||||
appPackageName = context.getPackageName();
|
||||
sessionManager = MediaSessionManager.getSessionManager(context);
|
||||
controllerLegacyCbForBroadcast = new ControllerLegacyCbForBroadcast();
|
||||
mediaPlayPauseKeyHandler =
|
||||
new MediaPlayPauseKeyHandler(session.getApplicationHandler().getLooper());
|
||||
connectedControllersManager = new ConnectedControllersManager<>(session);
|
||||
connectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS;
|
||||
connectionTimeoutHandler =
|
||||
@ -318,41 +312,16 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
|
||||
@Nullable KeyEvent keyEvent = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||
if (keyEvent == null || keyEvent.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
RemoteUserInfo remoteUserInfo = sessionCompat.getCurrentControllerInfo();
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
// Double tap detection only for media button events from external sources (for instance
|
||||
// Bluetooth). Media button events from the app package are coming from the notification
|
||||
// below targetApiLevel 33.
|
||||
if (!appPackageName.equals(remoteUserInfo.getPackageName())
|
||||
&& keyEvent.getRepeatCount() == 0) {
|
||||
if (mediaPlayPauseKeyHandler.hasPendingMediaPlayPauseKey()) {
|
||||
mediaPlayPauseKeyHandler.clearPendingMediaPlayPauseKey();
|
||||
onSkipToNext();
|
||||
} else {
|
||||
mediaPlayPauseKeyHandler.addPendingMediaPlayPauseKey(remoteUserInfo);
|
||||
}
|
||||
} else {
|
||||
// Consider long-press as a single tap. Handle immediately.
|
||||
handleMediaPlayPauseOnHandler(remoteUserInfo);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
// If another key is pressed within double tap timeout, consider the pending
|
||||
// pending play/pause as a single tap to handle media keys in order.
|
||||
if (mediaPlayPauseKeyHandler.hasPendingMediaPlayPauseKey()) {
|
||||
handleMediaPlayPauseOnHandler(remoteUserInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
public boolean onMediaButtonEvent(Intent intent) {
|
||||
return sessionImpl.onMediaButtonEvent(
|
||||
new ControllerInfo(
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
ControllerInfo.LEGACY_CONTROLLER_VERSION,
|
||||
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
|
||||
/* trusted= */ false,
|
||||
/* cb= */ null,
|
||||
/* connectionHints= */ Bundle.EMPTY),
|
||||
intent);
|
||||
}
|
||||
|
||||
private void maybeUpdateFlags(PlayerWrapper playerWrapper) {
|
||||
@ -366,8 +335,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMediaPlayPauseOnHandler(RemoteUserInfo remoteUserInfo) {
|
||||
mediaPlayPauseKeyHandler.clearPendingMediaPlayPauseKey();
|
||||
/* package */ void handleMediaPlayPauseOnHandler(RemoteUserInfo remoteUserInfo) {
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_PLAY_PAUSE,
|
||||
controller ->
|
||||
@ -1435,34 +1403,6 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaPlayPauseKeyHandler extends Handler {
|
||||
|
||||
private static final int MSG_DOUBLE_TAP_TIMED_OUT = 1002;
|
||||
|
||||
public MediaPlayPauseKeyHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
RemoteUserInfo remoteUserInfo = (RemoteUserInfo) msg.obj;
|
||||
handleMediaPlayPauseOnHandler(remoteUserInfo);
|
||||
}
|
||||
|
||||
public void addPendingMediaPlayPauseKey(RemoteUserInfo remoteUserInfo) {
|
||||
Message msg = obtainMessage(MSG_DOUBLE_TAP_TIMED_OUT, remoteUserInfo);
|
||||
sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout());
|
||||
}
|
||||
|
||||
public void clearPendingMediaPlayPauseKey() {
|
||||
removeMessages(MSG_DOUBLE_TAP_TIMED_OUT);
|
||||
}
|
||||
|
||||
public boolean hasPendingMediaPlayPauseKey() {
|
||||
return hasMessages(MSG_DOUBLE_TAP_TIMED_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBitmapLoadErrorMessage(Throwable throwable) {
|
||||
return "Failed to load bitmap: " + throwable.getMessage();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import static androidx.media3.common.util.Util.postOrRun;
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@ -39,6 +40,7 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.collection.ArrayMap;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
@ -425,9 +427,19 @@ public abstract class MediaSessionService extends Service {
|
||||
}
|
||||
addSession(session);
|
||||
}
|
||||
if (!session.getImpl().onMediaButtonEvent(intent)) {
|
||||
Log.w(TAG, "Ignoring unrecognized media button intent.");
|
||||
}
|
||||
MediaSessionImpl sessionImpl = session.getImpl();
|
||||
sessionImpl
|
||||
.getApplicationHandler()
|
||||
.post(
|
||||
() -> {
|
||||
ControllerInfo callerInfo = sessionImpl.getMediaNotificationControllerInfo();
|
||||
if (callerInfo == null) {
|
||||
callerInfo = createFallbackMediaButtonCaller(intent);
|
||||
}
|
||||
if (!sessionImpl.onMediaButtonEvent(callerInfo, intent)) {
|
||||
Log.d(TAG, "Ignored unrecognized media button intent.");
|
||||
}
|
||||
});
|
||||
} else if (session != null && actionFactory.isCustomAction(intent)) {
|
||||
@Nullable String customAction = actionFactory.getCustomAction(intent);
|
||||
if (customAction == null) {
|
||||
@ -439,6 +451,24 @@ public abstract class MediaSessionService extends Service {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private static ControllerInfo createFallbackMediaButtonCaller(Intent mediaButtonIntent) {
|
||||
@Nullable ComponentName componentName = mediaButtonIntent.getComponent();
|
||||
String packageName =
|
||||
componentName != null
|
||||
? componentName.getPackageName()
|
||||
: "androidx.media3.session.MediaSessionService";
|
||||
return new ControllerInfo(
|
||||
new MediaSessionManager.RemoteUserInfo(
|
||||
packageName,
|
||||
MediaSessionManager.RemoteUserInfo.UNKNOWN_PID,
|
||||
MediaSessionManager.RemoteUserInfo.UNKNOWN_UID),
|
||||
MediaLibraryInfo.VERSION_INT,
|
||||
MediaControllerStub.VERSION_INT,
|
||||
/* trusted= */ false,
|
||||
/* cb= */ null,
|
||||
/* connectionHints= */ Bundle.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the service is no longer used and is being removed.
|
||||
*
|
||||
|
@ -21,6 +21,7 @@ import static androidx.media3.common.Player.COMMAND_PREPARE;
|
||||
import static androidx.media3.common.Player.STATE_ENDED;
|
||||
import static androidx.media3.common.Player.STATE_IDLE;
|
||||
import static androidx.media3.common.Player.STATE_READY;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE;
|
||||
import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
|
||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||
@ -46,6 +47,7 @@ import androidx.media.AudioManagerCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
import androidx.media3.common.ForwardingPlayer;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Rating;
|
||||
@ -71,7 +73,6 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
@ -93,6 +94,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
|
||||
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
|
||||
|
||||
@Rule public final MediaSessionTestRule mediaSessionTestRule = new MediaSessionTestRule();
|
||||
|
||||
private Context context;
|
||||
private TestHandler handler;
|
||||
private MediaSession session;
|
||||
@ -615,37 +618,47 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchMediaButtonEvent_playWithEmptyTimeline_callsPreparesPlayerCorrectly()
|
||||
public void dispatchMediaButtonEvent_playWithEmptyTimeline_callsPlaybackResumptionPrepareAndPlay()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("sendMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition>
|
||||
onPlaybackResumption(MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 123L));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(session, player);
|
||||
session.set(
|
||||
mediaSessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("dispatchMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition>
|
||||
onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 123L));
|
||||
}
|
||||
})
|
||||
.build()));
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
|
||||
context,
|
||||
session.get().getSessionCompat().getSessionToken(),
|
||||
/* waitForConnection= */ true);
|
||||
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
|
||||
|
||||
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
session.get().getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.startMediaItemIndex).isEqualTo(1);
|
||||
assertThat(player.startPositionMs).isEqualTo(123L);
|
||||
assertThat(player.mediaItems).isEqualTo(mediaItems);
|
||||
assertThat(callerCollectorPlayer.callers).hasSize(3);
|
||||
for (ControllerInfo controllerInfo : callerCollectorPlayer.callers) {
|
||||
assertThat(session.get().isMediaNotificationController(controllerInfo)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -739,7 +752,59 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
|
||||
@Test
|
||||
public void
|
||||
dispatchMediaButtonEvent_playWithEmptyTimelineCallbackFailure_callsHandlePlayButtonAction()
|
||||
dispatchMediaButtonEvent_playWithEmptyTimelineWithMediaNotificationController_callsPlaybackResumptionPrepareAndPlay()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(session, player);
|
||||
session.set(
|
||||
mediaSessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("dispatchMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition>
|
||||
onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 123L));
|
||||
}
|
||||
})
|
||||
.build()));
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
session.get().getSessionCompat().getSessionToken(),
|
||||
/* waitForConnection= */ true);
|
||||
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||
new MediaController.Builder(
|
||||
ApplicationProvider.getApplicationContext(), session.get().getToken())
|
||||
.setConnectionHints(connectionHints)
|
||||
.buildAsync()
|
||||
.get();
|
||||
|
||||
session.get().getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.startMediaItemIndex).isEqualTo(1);
|
||||
assertThat(player.startPositionMs).isEqualTo(123L);
|
||||
assertThat(player.mediaItems).isEqualTo(mediaItems);
|
||||
assertThat(callerCollectorPlayer.callers).hasSize(3);
|
||||
for (ControllerInfo controllerInfo : callerCollectorPlayer.callers) {
|
||||
assertThat(session.get().isMediaNotificationController(controllerInfo)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
dispatchMediaButtonEvent_playWithEmptyTimelinePlaybackResumptionFailure_callsHandlePlayButtonAction()
|
||||
throws Exception {
|
||||
player.mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
player.startMediaItemIndex = 1;
|
||||
@ -781,28 +846,20 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
player.timeline = new PlaylistTimeline(player.mediaItems);
|
||||
player.startMediaItemIndex = 1;
|
||||
player.startPositionMs = 321L;
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("sendMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition>
|
||||
onPlaybackResumption(MediaSession mediaSession, ControllerInfo controller) {
|
||||
Assert.fail();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
MediaTestUtils.createMediaItems(/* size= */ 10),
|
||||
/* startIndex= */ 9,
|
||||
/* startPositionMs= */ 123L));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(session, player);
|
||||
session.set(
|
||||
mediaSessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("dispatchMediaButtonEvent")
|
||||
.build()));
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
|
||||
context,
|
||||
session.get().getSessionCompat().getSessionToken(),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
session.get().getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
@ -811,6 +868,50 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
assertThat(player.startMediaItemIndex).isEqualTo(1);
|
||||
assertThat(player.startPositionMs).isEqualTo(321L);
|
||||
assertThat(player.mediaItems).hasSize(3);
|
||||
assertThat(callerCollectorPlayer.callers).hasSize(2);
|
||||
for (ControllerInfo controllerInfo : callerCollectorPlayer.callers) {
|
||||
assertThat(session.get().isMediaNotificationController(controllerInfo)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
dispatchMediaButtonEvent_playWithNonEmptyTimelineWithMediaNotificationController_callsHandlePlayButtonAction()
|
||||
throws Exception {
|
||||
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
|
||||
player.mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
player.timeline = new PlaylistTimeline(player.mediaItems);
|
||||
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(session, player);
|
||||
session.set(
|
||||
mediaSessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("dispatchMediaButtonEvent")
|
||||
.build()));
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||
new MediaController.Builder(
|
||||
ApplicationProvider.getApplicationContext(), session.get().getToken())
|
||||
.setConnectionHints(connectionHints)
|
||||
.buildAsync()
|
||||
.get();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
session.get().getSessionCompat().getSessionToken(),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
session.get().getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
assertThat(player.mediaItems).hasSize(3);
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
|
||||
.isFalse();
|
||||
assertThat(callerCollectorPlayer.callers).hasSize(2);
|
||||
for (ControllerInfo controllerInfo : callerCollectorPlayer.callers) {
|
||||
assertThat(session.get().isMediaNotificationController(controllerInfo)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1807,4 +1908,33 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
return MediaSession.ConnectionResult.reject();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CallerCollectorPlayer extends ForwardingPlayer {
|
||||
private final List<ControllerInfo> callers;
|
||||
private final AtomicReference<MediaSession> mediaSession;
|
||||
|
||||
public CallerCollectorPlayer(AtomicReference<MediaSession> mediaSession, MockPlayer player) {
|
||||
super(player);
|
||||
this.mediaSession = mediaSession;
|
||||
callers = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
|
||||
callers.add(checkNotNull(mediaSession.get().getControllerForCurrentRequest()));
|
||||
super.setMediaItems(mediaItems, startIndex, startPositionMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
callers.add(checkNotNull(mediaSession.get().getControllerForCurrentRequest()));
|
||||
super.prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play() {
|
||||
callers.add(checkNotNull(mediaSession.get().getControllerForCurrentRequest()));
|
||||
super.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
|
||||
import static androidx.media3.common.Player.STATE_ENDED;
|
||||
import static androidx.media3.session.MediaSession.ControllerInfo.LEGACY_CONTROLLER_VERSION;
|
||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
@ -26,7 +28,9 @@ import static org.junit.Assume.assumeTrue;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.media3.common.ForwardingPlayer;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
@ -37,6 +41,8 @@ import androidx.media3.test.session.common.TestHandler;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import org.junit.After;
|
||||
import org.junit.Assume;
|
||||
@ -69,6 +75,7 @@ public class MediaSessionKeyEventTest {
|
||||
private MediaSession session;
|
||||
private MockPlayer player;
|
||||
private TestSessionCallback sessionCallback;
|
||||
private CallerCollectorPlayer callerCollectorPlayer;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@ -78,10 +85,14 @@ public class MediaSessionKeyEventTest {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
handler = threadTestRule.getHandler();
|
||||
player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build();
|
||||
|
||||
player =
|
||||
new MockPlayer.Builder().setMediaItems(1).setApplicationLooper(handler.getLooper()).build();
|
||||
sessionCallback = new TestSessionCallback();
|
||||
session = new MediaSession.Builder(context, player).setCallback(sessionCallback).build();
|
||||
callerCollectorPlayer = new CallerCollectorPlayer(player);
|
||||
session =
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setCallback(sessionCallback)
|
||||
.build();
|
||||
|
||||
// Here's the requirement for an app to receive media key events via MediaSession.
|
||||
// - SDK < 26: Player should be playing for receiving key events
|
||||
@ -160,6 +171,92 @@ public class MediaSessionKeyEventTest {
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
fastForwardKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
MediaController controller = connectMediaNotificationController();
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, /* doubleTap= */ false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
assertThat(callerCollectorPlayer.callers).hasSize(1);
|
||||
assertThat(callerCollectorPlayer.callers.get(0).getControllerVersion())
|
||||
.isNotEqualTo(LEGACY_CONTROLLER_VERSION);
|
||||
assertThat(callerCollectorPlayer.callers.get(0).getPackageName())
|
||||
.isEqualTo("androidx.media3.test.session");
|
||||
assertThat(callerCollectorPlayer.callers.get(0).getConnectionHints().size()).isEqualTo(1);
|
||||
assertThat(
|
||||
callerCollectorPlayer
|
||||
.callers
|
||||
.get(0)
|
||||
.getConnectionHints()
|
||||
.getBoolean(
|
||||
MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER,
|
||||
/* defaultValue= */ false))
|
||||
.isTrue();
|
||||
threadTestRule.getHandler().postAndSync(controller::release);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
fastForwardKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
List<ControllerInfo> controllers = callerCollectorPlayer.callers;
|
||||
assertThat(controllers).hasSize(1);
|
||||
assertThat(controllers.get(0).getControllerVersion()).isEqualTo(LEGACY_CONTROLLER_VERSION);
|
||||
assertThat(controllers.get(0).getConnectionHints().size()).isEqualTo(0);
|
||||
assertThat(controllers.get(0).getPackageName())
|
||||
.isEqualTo(getExpectedControllerPackageName(controllers.get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rewindKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
MediaController controller = connectMediaNotificationController();
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS);
|
||||
List<ControllerInfo> controllers = callerCollectorPlayer.callers;
|
||||
assertThat(controllers).hasSize(1);
|
||||
assertThat(controllers.get(0).getPackageName()).isEqualTo("androidx.media3.test.session");
|
||||
assertThat(controllers.get(0).getControllerVersion()).isNotEqualTo(LEGACY_CONTROLLER_VERSION);
|
||||
assertThat(controllers.get(0).getConnectionHints().size()).isEqualTo(1);
|
||||
assertThat(
|
||||
controllers
|
||||
.get(0)
|
||||
.getConnectionHints()
|
||||
.getBoolean(
|
||||
MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER,
|
||||
/* defaultValue= */ false))
|
||||
.isTrue();
|
||||
threadTestRule.getHandler().postAndSync(controller::release);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
rewindKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS);
|
||||
List<ControllerInfo> controllers = callerCollectorPlayer.callers;
|
||||
assertThat(controllers).hasSize(1);
|
||||
assertThat(controllers.get(0).getControllerVersion()).isEqualTo(LEGACY_CONTROLLER_VERSION);
|
||||
assertThat(controllers.get(0).getConnectionHints().size()).isEqualTo(0);
|
||||
assertThat(controllers.get(0).getPackageName())
|
||||
.isEqualTo(getExpectedControllerPackageName(controllers.get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
@ -210,7 +307,7 @@ public class MediaSessionKeyEventTest {
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
player.playWhenReady = true;
|
||||
player.playbackState = Player.STATE_ENDED;
|
||||
player.playbackState = STATE_ENDED;
|
||||
});
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
|
||||
@ -233,6 +330,36 @@ public class MediaSessionKeyEventTest {
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playPauseKeyEvent_doubleTapOnPlayPause_seekNext() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
player.playWhenReady = true;
|
||||
player.playbackState = Player.STATE_READY;
|
||||
});
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, /* doubleTap= */ true);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private MediaController connectMediaNotificationController() throws Exception {
|
||||
return threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean(
|
||||
MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, /* value= */ true);
|
||||
return new MediaController.Builder(
|
||||
ApplicationProvider.getApplicationContext(), session.getToken())
|
||||
.setConnectionHints(connectionHints)
|
||||
.buildAsync()
|
||||
.get();
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) {
|
||||
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||
@ -242,30 +369,56 @@ public class MediaSessionKeyEventTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestSessionCallback implements MediaSession.Callback {
|
||||
private static String getExpectedControllerPackageName(ControllerInfo controllerInfo) {
|
||||
if (controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
|
||||
return SUPPORT_APP_PACKAGE_NAME;
|
||||
}
|
||||
// Legacy controllers
|
||||
if (Util.SDK_INT < 21 || Util.SDK_INT >= 28) {
|
||||
// Above API 28: package of the app using AudioManager.
|
||||
// Below 21: package of the owner of the session. Note: This is specific to this test setup
|
||||
// where `ApplicationProvider.getContext().packageName == SUPPORT_APP_PACKAGE_NAME`.
|
||||
return SUPPORT_APP_PACKAGE_NAME;
|
||||
} else if (Util.SDK_INT >= 24) {
|
||||
// API 24 - 27: KeyEvent from system service has the package name "android".
|
||||
return "android";
|
||||
} else {
|
||||
// API 21 - 23: Fallback set by MediaSessionCompat#getCurrentControllerInfo
|
||||
return LEGACY_CONTROLLER;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
|
||||
getExpectedControllerPackageName();
|
||||
private static class TestSessionCallback implements MediaSession.Callback {
|
||||
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
|
||||
if (session.isMediaNotificationController(controller)
|
||||
|| getExpectedControllerPackageName(controller).equals(controller.getPackageName())) {
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
}
|
||||
return MediaSession.ConnectionResult.reject();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getExpectedControllerPackageName() {
|
||||
if (Util.SDK_INT >= 28 || Util.SDK_INT < 21) {
|
||||
return SUPPORT_APP_PACKAGE_NAME;
|
||||
} else if (Util.SDK_INT >= 24) {
|
||||
// KeyEvent from system service has the package name "android".
|
||||
return "android";
|
||||
} else {
|
||||
// In API 21+, MediaSessionCompat#getCurrentControllerInfo always returns fake info.
|
||||
return LEGACY_CONTROLLER;
|
||||
}
|
||||
private class CallerCollectorPlayer extends ForwardingPlayer {
|
||||
private final List<ControllerInfo> callers;
|
||||
|
||||
public CallerCollectorPlayer(Player player) {
|
||||
super(player);
|
||||
callers = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekForward() {
|
||||
callers.add(session.getControllerForCurrentRequest());
|
||||
super.seekForward();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekBack() {
|
||||
callers.add(session.getControllerForCurrentRequest());
|
||||
super.seekBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import static android.view.KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_NEXT;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_PAUSE;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_PLAY;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_PREVIOUS;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_REWIND;
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
|
||||
@ -513,7 +514,7 @@ public class MediaSessionTest {
|
||||
session.set(
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
|
||||
.setId("onMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
@ -535,14 +536,41 @@ public class MediaSessionTest {
|
||||
.buildAsync()
|
||||
.get();
|
||||
|
||||
MediaSessionImpl impl = session.get().getImpl();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PLAY))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PAUSE))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_FAST_FORWARD))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_REWIND))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_NEXT))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PREVIOUS))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_STOP))).isTrue();
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
MediaSessionImpl impl = session.get().getImpl();
|
||||
ControllerInfo controllerInfo = createMediaButtonCaller();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PLAY)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PAUSE)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_FAST_FORWARD)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_REWIND)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_NEXT)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PREVIOUS)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_STOP)))
|
||||
.isTrue();
|
||||
});
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
|
||||
@ -566,7 +594,7 @@ public class MediaSessionTest {
|
||||
session.set(
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||
.setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
|
||||
.setId("onMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
@ -583,19 +611,46 @@ public class MediaSessionTest {
|
||||
.build()));
|
||||
MediaSessionImpl impl = session.get().getImpl();
|
||||
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PLAY))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PAUSE))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_FAST_FORWARD))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_REWIND))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_NEXT))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_PREVIOUS))).isTrue();
|
||||
assertThat(impl.onMediaButtonEvent(getMediaButtonIntent(KEYCODE_MEDIA_STOP))).isTrue();
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
ControllerInfo controllerInfo = createMediaButtonCaller();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PLAY)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PAUSE)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_FAST_FORWARD)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_REWIND)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_NEXT)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_PREVIOUS)))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
impl.onMediaButtonEvent(
|
||||
controllerInfo, getMediaButtonIntent(KEYCODE_MEDIA_STOP)))
|
||||
.isTrue();
|
||||
});
|
||||
|
||||
// Fallback code path through platform session when MediaSessionImpl doesn't handle the event.
|
||||
// Fallback through the framework session when media notification controller in disabled.
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
|
||||
@ -609,12 +664,113 @@ public class MediaSessionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
onMediaButtonEvent_appOverridesCallback_notificationControllerNotConnected_callsWhatAppCalls()
|
||||
throws Exception {
|
||||
List<ControllerInfo> controllers = new ArrayList<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
if (TextUtils.equals(
|
||||
getControllerCallerPackageName(controller),
|
||||
controller.getPackageName())) {
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
}
|
||||
return MediaSession.ConnectionResult.reject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent(
|
||||
MediaSession session, ControllerInfo controllerInfo, Intent intent) {
|
||||
session.getPlayer().seekToNext();
|
||||
controllers.add(controllerInfo);
|
||||
latch.countDown();
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.build());
|
||||
MediaSessionImpl impl = session.getImpl();
|
||||
|
||||
ControllerInfo controllerInfo = createMediaButtonCaller();
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY_PAUSE);
|
||||
assertThat(impl.onMediaButtonEvent(controllerInfo, intent)).isTrue();
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
|
||||
assertThat(controllers).hasSize(1);
|
||||
assertThat(session.isMediaNotificationController(controllers.get(0))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
onMediaButtonEvent_appOverridesCallback_notificationControllerConnected_callsWhatAppCalls()
|
||||
throws Exception {
|
||||
List<ControllerInfo> controllers = new ArrayList<>();
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onMediaButtonEvent")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public boolean onMediaButtonEvent(
|
||||
MediaSession session, ControllerInfo controllerInfo, Intent intent) {
|
||||
if (DefaultActionFactory.getKeyEvent(intent).getKeyCode()
|
||||
== KEYCODE_MEDIA_PLAY) {
|
||||
player.seekForward();
|
||||
controllers.add(controllerInfo);
|
||||
return true;
|
||||
}
|
||||
return MediaSession.Callback.super.onMediaButtonEvent(
|
||||
session, controllerInfo, intent);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||
.setConnectionHints(connectionHints)
|
||||
.buildAsync()
|
||||
.get();
|
||||
|
||||
boolean isEventHandled =
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() ->
|
||||
session
|
||||
.getImpl()
|
||||
.onMediaButtonEvent(
|
||||
session.getMediaNotificationControllerInfo(),
|
||||
getMediaButtonIntent(KEYCODE_MEDIA_PLAY)));
|
||||
|
||||
assertThat(isEventHandled).isTrue();
|
||||
// App changed default behaviour
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
assertThat(controllers).hasSize(1);
|
||||
assertThat(session.isMediaNotificationController(controllers.get(0))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMediaButtonEvent_noKeyEvent_returnsFalse() {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -631,7 +787,8 @@ public class MediaSessionTest {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -642,7 +799,8 @@ public class MediaSessionTest {
|
||||
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KEYCODE_MEDIA_PAUSE));
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -660,7 +818,8 @@ public class MediaSessionTest {
|
||||
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KEYCODE_MEDIA_PAUSE));
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -670,7 +829,8 @@ public class MediaSessionTest {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.setAction("notAMediaButtonAction");
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -687,7 +847,8 @@ public class MediaSessionTest {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.setAction("notAMediaButtonAction");
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -697,7 +858,8 @@ public class MediaSessionTest {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.setComponent(new ComponentName("a.package", "a.class"));
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -715,7 +877,8 @@ public class MediaSessionTest {
|
||||
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||
intent.setComponent(new ComponentName("a.package", "a.class"));
|
||||
|
||||
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||
boolean isEventHandled =
|
||||
session.getImpl().onMediaButtonEvent(createMediaButtonCaller(), intent);
|
||||
|
||||
assertThat(isEventHandled).isFalse();
|
||||
}
|
||||
@ -750,6 +913,19 @@ public class MediaSessionTest {
|
||||
: MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
|
||||
}
|
||||
|
||||
private static ControllerInfo createMediaButtonCaller() {
|
||||
return new ControllerInfo(
|
||||
new MediaSessionManager.RemoteUserInfo(
|
||||
"RANDOM_MEDIA_BUTTON_CALLER_PACKAGE",
|
||||
MediaSessionManager.RemoteUserInfo.UNKNOWN_PID,
|
||||
MediaSessionManager.RemoteUserInfo.UNKNOWN_UID),
|
||||
MediaLibraryInfo.VERSION_INT,
|
||||
MediaControllerStub.VERSION_INT,
|
||||
/* trusted= */ false,
|
||||
/* cb= */ null,
|
||||
/* connectionHints= */ Bundle.EMPTY);
|
||||
}
|
||||
|
||||
private static class CallerCollectorPlayer extends ForwardingPlayer {
|
||||
private final List<ControllerInfo> callingControllers;
|
||||
private final AtomicReference<MediaSession> session;
|
||||
|
Loading…
x
Reference in New Issue
Block a user