Send media button events from service directly using MediaSessionImpl
Media button event coming from the `MediaSessionService` are delegated to the `MediaSessionImpl` and then sent to the session by using the `MediaSessionStub` directly instead of using the `MediaController` API. Splitting the `MediaController.Listener` and `Player.Listener` in `MediaNotificationManager` got reverted, and both listener are set to the controller as before. This reverts the change that introduced a different timing behaviour. It still holds, that a listener registered on a `MediaController` that calls a method like `play()` is called immediately and before the call has arrived at the player. This change works around this behaviour from the library side by calling `MediaSessionStub` directly with a `ControllerInfo`. #minor-release PiperOrigin-RevId: 573918850
This commit is contained in:
parent
d5f093f43c
commit
64bd3bcad3
@ -56,6 +56,19 @@ import androidx.media3.common.util.Util;
|
|||||||
public static final String EXTRAS_KEY_ACTION_CUSTOM_EXTRAS =
|
public static final String EXTRAS_KEY_ACTION_CUSTOM_EXTRAS =
|
||||||
"androidx.media3.session.EXTRAS_KEY_CUSTOM_NOTIFICATION_ACTION_EXTRAS";
|
"androidx.media3.session.EXTRAS_KEY_CUSTOM_NOTIFICATION_ACTION_EXTRAS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link KeyEvent} that was included in the media action, or {@code null} if no
|
||||||
|
* {@link KeyEvent} is found in the {@code intent}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static KeyEvent getKeyEvent(Intent intent) {
|
||||||
|
@Nullable Bundle extras = intent.getExtras();
|
||||||
|
if (extras != null && extras.containsKey(Intent.EXTRA_KEY_EVENT)) {
|
||||||
|
return extras.getParcelable(Intent.EXTRA_KEY_EVENT);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private final Service service;
|
private final Service service;
|
||||||
|
|
||||||
private int customActionPendingIntentRequestCode = 0;
|
private int customActionPendingIntentRequestCode = 0;
|
||||||
@ -97,6 +110,7 @@ import androidx.media3.common.util.Util;
|
|||||||
mediaSession, customCommand.customAction, customCommand.customExtras));
|
mediaSession, customCommand.customAction, customCommand.customExtras));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||||
@Override
|
@Override
|
||||||
public PendingIntent createMediaActionPendingIntent(
|
public PendingIntent createMediaActionPendingIntent(
|
||||||
MediaSession mediaSession, @Player.Command long command) {
|
MediaSession mediaSession, @Player.Command long command) {
|
||||||
@ -136,6 +150,7 @@ import androidx.media3.common.util.Util;
|
|||||||
return KEYCODE_UNKNOWN;
|
return KEYCODE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||||
private PendingIntent createCustomActionPendingIntent(
|
private PendingIntent createCustomActionPendingIntent(
|
||||||
MediaSession mediaSession, String action, Bundle extras) {
|
MediaSession mediaSession, String action, Bundle extras) {
|
||||||
Intent intent = new Intent(ACTION_CUSTOM);
|
Intent intent = new Intent(ACTION_CUSTOM);
|
||||||
@ -162,19 +177,6 @@ import androidx.media3.common.util.Util;
|
|||||||
return ACTION_CUSTOM.equals(intent.getAction());
|
return ACTION_CUSTOM.equals(intent.getAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link KeyEvent} that was included in the media action, or {@code null} if no
|
|
||||||
* {@link KeyEvent} is found in the {@code intent}.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public KeyEvent getKeyEvent(Intent intent) {
|
|
||||||
@Nullable Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null && extras.containsKey(Intent.EXTRA_KEY_EVENT)) {
|
|
||||||
return extras.getParcelable(Intent.EXTRA_KEY_EVENT);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the custom action that was included in the {@link #createCustomAction custom action},
|
* Returns the custom action that was included in the {@link #createCustomAction custom action},
|
||||||
* or {@code null} if no custom action is found in the {@code intent}.
|
* or {@code null} if no custom action is found in the {@code intent}.
|
||||||
@ -201,6 +203,7 @@ import androidx.media3.common.util.Util;
|
|||||||
private static final class Api26 {
|
private static final class Api26 {
|
||||||
private Api26() {}
|
private Api26() {}
|
||||||
|
|
||||||
|
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||||
public static PendingIntent createForegroundServicePendingIntent(
|
public static PendingIntent createForegroundServicePendingIntent(
|
||||||
Service service, int keyCode, Intent intent) {
|
Service service, int keyCode, Intent intent) {
|
||||||
return PendingIntent.getForegroundService(
|
return PendingIntent.getForegroundService(
|
||||||
|
@ -17,14 +17,6 @@ package androidx.media3.session;
|
|||||||
|
|
||||||
import static android.app.Service.STOP_FOREGROUND_DETACH;
|
import static android.app.Service.STOP_FOREGROUND_DETACH;
|
||||||
import static android.app.Service.STOP_FOREGROUND_REMOVE;
|
import static android.app.Service.STOP_FOREGROUND_REMOVE;
|
||||||
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;
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@ -34,7 +26,6 @@ import android.content.pm.ServiceInfo;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import androidx.annotation.DoNotInline;
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
@ -74,7 +65,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
private final NotificationManagerCompat notificationManagerCompat;
|
private final NotificationManagerCompat notificationManagerCompat;
|
||||||
private final Executor mainExecutor;
|
private final Executor mainExecutor;
|
||||||
private final Intent startSelfIntent;
|
private final Intent startSelfIntent;
|
||||||
private final Map<MediaSession, ControllerAndListener> controllerAndListenerMap;
|
private final Map<MediaSession, ListenableFuture<MediaController>> controllerMap;
|
||||||
|
|
||||||
private int totalNotificationCount;
|
private int totalNotificationCount;
|
||||||
@Nullable private MediaNotification mediaNotification;
|
@Nullable private MediaNotification mediaNotification;
|
||||||
@ -91,34 +82,30 @@ import java.util.concurrent.TimeoutException;
|
|||||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
mainExecutor = (runnable) -> Util.postOrRun(mainHandler, runnable);
|
mainExecutor = (runnable) -> Util.postOrRun(mainHandler, runnable);
|
||||||
startSelfIntent = new Intent(mediaSessionService, mediaSessionService.getClass());
|
startSelfIntent = new Intent(mediaSessionService, mediaSessionService.getClass());
|
||||||
controllerAndListenerMap = new HashMap<>();
|
controllerMap = new HashMap<>();
|
||||||
startedInForeground = false;
|
startedInForeground = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSession(MediaSession session) {
|
public void addSession(MediaSession session) {
|
||||||
if (controllerAndListenerMap.containsKey(session)) {
|
if (controllerMap.containsKey(session)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaControllerListener controllerListener =
|
MediaControllerListener listener = new MediaControllerListener(mediaSessionService, session);
|
||||||
new MediaControllerListener(mediaSessionService, session);
|
|
||||||
PlayerListener playerListener = new PlayerListener(mediaSessionService, session);
|
|
||||||
Bundle connectionHints = new Bundle();
|
Bundle connectionHints = new Bundle();
|
||||||
connectionHints.putBoolean(KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
connectionHints.putBoolean(KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||||
ListenableFuture<MediaController> controllerFuture =
|
ListenableFuture<MediaController> controllerFuture =
|
||||||
new MediaController.Builder(mediaSessionService, session.getToken())
|
new MediaController.Builder(mediaSessionService, session.getToken())
|
||||||
.setConnectionHints(connectionHints)
|
.setConnectionHints(connectionHints)
|
||||||
.setListener(controllerListener)
|
.setListener(listener)
|
||||||
.setApplicationLooper(Looper.getMainLooper())
|
.setApplicationLooper(Looper.getMainLooper())
|
||||||
.buildAsync();
|
.buildAsync();
|
||||||
controllerAndListenerMap.put(
|
controllerMap.put(session, controllerFuture);
|
||||||
session, new ControllerAndListener(controllerFuture, playerListener));
|
|
||||||
controllerFuture.addListener(
|
controllerFuture.addListener(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
// Assert connection success.
|
MediaController controller = controllerFuture.get(/* time= */ 0, MILLISECONDS);
|
||||||
controllerFuture.get(/* time= */ 0, MILLISECONDS);
|
listener.onConnected(shouldShowNotification(session));
|
||||||
controllerListener.onConnected(shouldShowNotification(session));
|
controller.addListener(listener);
|
||||||
session.getImpl().addPlayerListener(playerListener);
|
|
||||||
} catch (CancellationException
|
} catch (CancellationException
|
||||||
| ExecutionException
|
| ExecutionException
|
||||||
| InterruptedException
|
| InterruptedException
|
||||||
@ -131,52 +118,9 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeSession(MediaSession session) {
|
public void removeSession(MediaSession session) {
|
||||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.remove(session);
|
@Nullable ListenableFuture<MediaController> future = controllerMap.remove(session);
|
||||||
if (controllerAndListener != null) {
|
if (future != null) {
|
||||||
session.getImpl().removePlayerListener(controllerAndListener.listener);
|
MediaController.releaseFuture(future);
|
||||||
MediaController.releaseFuture(controllerAndListener.controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMediaButtonEvent(MediaSession session, KeyEvent keyEvent) {
|
|
||||||
int keyCode = keyEvent.getKeyCode();
|
|
||||||
@Nullable MediaController mediaController = getConnectedControllerForSession(session);
|
|
||||||
if (mediaController == null) {
|
|
||||||
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (keyCode) {
|
|
||||||
case KEYCODE_MEDIA_PLAY_PAUSE:
|
|
||||||
if (mediaController.getPlayWhenReady()) {
|
|
||||||
mediaController.pause();
|
|
||||||
} else {
|
|
||||||
mediaController.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_PLAY:
|
|
||||||
mediaController.play();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_PAUSE:
|
|
||||||
mediaController.pause();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_NEXT:
|
|
||||||
mediaController.seekToNext();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_PREVIOUS:
|
|
||||||
mediaController.seekToPrevious();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_FAST_FORWARD:
|
|
||||||
mediaController.seekForward();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_REWIND:
|
|
||||||
mediaController.seekBack();
|
|
||||||
break;
|
|
||||||
case KEYCODE_MEDIA_STOP:
|
|
||||||
mediaController.stop();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.w(TAG, "Received media button event with unsupported key code: " + keyCode);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,11 +154,11 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
int notificationSequence = ++totalNotificationCount;
|
int notificationSequence = ++totalNotificationCount;
|
||||||
MediaController mediaNotificationController = null;
|
MediaController mediaNotificationController = null;
|
||||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.get(session);
|
ListenableFuture<MediaController> controller = controllerMap.get(session);
|
||||||
if (controllerAndListener != null && controllerAndListener.controller.isDone()) {
|
if (controller != null && controller.isDone()) {
|
||||||
try {
|
try {
|
||||||
mediaNotificationController = Futures.getDone(controllerAndListener.controller);
|
mediaNotificationController = Futures.getDone(controller);
|
||||||
} catch (CancellationException | ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,13 +261,13 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private MediaController getConnectedControllerForSession(MediaSession session) {
|
private MediaController getConnectedControllerForSession(MediaSession session) {
|
||||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.get(session);
|
ListenableFuture<MediaController> controller = controllerMap.get(session);
|
||||||
if (controllerAndListener == null) {
|
if (controller == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Futures.getDone(controllerAndListener.controller);
|
return Futures.getDone(controller);
|
||||||
} catch (CancellationException | ExecutionException exception) {
|
} catch (ExecutionException exception) {
|
||||||
// We should never reach this.
|
// We should never reach this.
|
||||||
throw new IllegalStateException(exception);
|
throw new IllegalStateException(exception);
|
||||||
}
|
}
|
||||||
@ -361,7 +305,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MediaControllerListener implements MediaController.Listener {
|
private static final class MediaControllerListener
|
||||||
|
implements MediaController.Listener, Player.Listener {
|
||||||
private final MediaSessionService mediaSessionService;
|
private final MediaSessionService mediaSessionService;
|
||||||
private final MediaSession session;
|
private final MediaSession session;
|
||||||
|
|
||||||
@ -399,18 +344,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
mediaSessionService.onUpdateNotificationInternal(
|
mediaSessionService.onUpdateNotificationInternal(
|
||||||
session, /* startInForegroundWhenPaused= */ false);
|
session, /* startInForegroundWhenPaused= */ false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static class PlayerListener implements Player.Listener {
|
|
||||||
private final MediaSessionService mediaSessionService;
|
|
||||||
private final MediaSession session;
|
|
||||||
private final Handler mainHandler;
|
|
||||||
|
|
||||||
public PlayerListener(MediaSessionService mediaSessionService, MediaSession session) {
|
|
||||||
this.mediaSessionService = mediaSessionService;
|
|
||||||
this.session = session;
|
|
||||||
mainHandler = new Handler(Looper.getMainLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvents(Player player, Player.Events events) {
|
public void onEvents(Player player, Player.Events events) {
|
||||||
@ -421,13 +354,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||||
Player.EVENT_MEDIA_METADATA_CHANGED,
|
Player.EVENT_MEDIA_METADATA_CHANGED,
|
||||||
Player.EVENT_TIMELINE_CHANGED)) {
|
Player.EVENT_TIMELINE_CHANGED)) {
|
||||||
// onUpdateNotificationInternal is required to be called on the main thread and the
|
|
||||||
// application thread of the player may be a different thread.
|
|
||||||
Util.postOrRun(
|
|
||||||
mainHandler,
|
|
||||||
() ->
|
|
||||||
mediaSessionService.onUpdateNotificationInternal(
|
mediaSessionService.onUpdateNotificationInternal(
|
||||||
session, /* startInForegroundWhenPaused= */ false));
|
session, /* startInForegroundWhenPaused= */ false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,17 +385,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
startedInForeground = false;
|
startedInForeground = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ControllerAndListener {
|
|
||||||
public final ListenableFuture<MediaController> controller;
|
|
||||||
public final Player.Listener listener;
|
|
||||||
|
|
||||||
private ControllerAndListener(
|
|
||||||
ListenableFuture<MediaController> controller, Player.Listener listener) {
|
|
||||||
this.controller = controller;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
private static class Api24 {
|
private static class Api24 {
|
||||||
|
|
||||||
|
@ -15,15 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
|
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_SKIP_BACKWARD;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
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.common.util.Util.postOrRun;
|
||||||
|
import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
|
||||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
|
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
|
||||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN;
|
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN;
|
||||||
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
|
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -37,6 +50,7 @@ import android.os.Process;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
@ -134,7 +148,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
|
||||||
// Should be only accessed on the application looper
|
// Should be only accessed on the application looper
|
||||||
private final List<Player.Listener> wrapperListeners;
|
|
||||||
private long sessionPositionUpdateDelayMs;
|
private long sessionPositionUpdateDelayMs;
|
||||||
private boolean isMediaNotificationControllerConnected;
|
private boolean isMediaNotificationControllerConnected;
|
||||||
private ImmutableList<CommandButton> customLayout;
|
private ImmutableList<CommandButton> customLayout;
|
||||||
@ -161,7 +174,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
sessionStub = new MediaSessionStub(thisRef);
|
sessionStub = new MediaSessionStub(thisRef);
|
||||||
this.sessionActivity = sessionActivity;
|
this.sessionActivity = sessionActivity;
|
||||||
this.customLayout = customLayout;
|
this.customLayout = customLayout;
|
||||||
wrapperListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
mainHandler = new Handler(Looper.getMainLooper());
|
mainHandler = new Handler(Looper.getMainLooper());
|
||||||
applicationHandler = new Handler(player.getApplicationLooper());
|
applicationHandler = new Handler(player.getApplicationLooper());
|
||||||
@ -240,38 +252,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
playerWrapper.getAvailablePlayerCommands()));
|
playerWrapper.getAvailablePlayerCommands()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlayerListener(Player.Listener listener) {
|
|
||||||
postOrRun(
|
|
||||||
applicationHandler,
|
|
||||||
() -> {
|
|
||||||
wrapperListeners.add(listener);
|
|
||||||
playerWrapper.addListener(listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removePlayerListener(Player.Listener listener) {
|
|
||||||
postOrRun(
|
|
||||||
applicationHandler,
|
|
||||||
() -> {
|
|
||||||
playerWrapper.removeListener(listener);
|
|
||||||
wrapperListeners.remove(listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPlayerInternal(
|
private void setPlayerInternal(
|
||||||
@Nullable PlayerWrapper oldPlayerWrapper, PlayerWrapper newPlayerWrapper) {
|
@Nullable PlayerWrapper oldPlayerWrapper, PlayerWrapper newPlayerWrapper) {
|
||||||
playerWrapper = newPlayerWrapper;
|
playerWrapper = newPlayerWrapper;
|
||||||
if (oldPlayerWrapper != null) {
|
if (oldPlayerWrapper != null) {
|
||||||
oldPlayerWrapper.removeListener(checkStateNotNull(this.playerListener));
|
oldPlayerWrapper.removeListener(checkStateNotNull(this.playerListener));
|
||||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
|
||||||
oldPlayerWrapper.removeListener(wrapperListeners.get(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PlayerListener playerListener = new PlayerListener(this, newPlayerWrapper);
|
PlayerListener playerListener = new PlayerListener(this, newPlayerWrapper);
|
||||||
newPlayerWrapper.addListener(playerListener);
|
newPlayerWrapper.addListener(playerListener);
|
||||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
|
||||||
newPlayerWrapper.addListener(wrapperListeners.get(i));
|
|
||||||
}
|
|
||||||
this.playerListener = playerListener;
|
this.playerListener = playerListener;
|
||||||
|
|
||||||
dispatchRemoteControllerTaskToLegacyStub(
|
dispatchRemoteControllerTaskToLegacyStub(
|
||||||
@ -303,10 +291,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
if (playerListener != null) {
|
if (playerListener != null) {
|
||||||
playerWrapper.removeListener(playerListener);
|
playerWrapper.removeListener(playerListener);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
|
||||||
playerWrapper.removeListener(wrapperListeners.get(i));
|
|
||||||
}
|
|
||||||
wrapperListeners.clear();
|
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Catch all exceptions to ensure the rest of this method to be executed as exceptions may be
|
// Catch all exceptions to ensure the rest of this method to be executed as exceptions may be
|
||||||
@ -1089,6 +1073,75 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
(callback, seq) -> callback.onDeviceInfoChanged(seq, playerInfo.deviceInfo));
|
(callback, seq) -> callback.onDeviceInfoChanged(seq, playerInfo.deviceInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ boolean onMediaButtonEvent(Intent intent) {
|
||||||
|
KeyEvent keyEvent = DefaultActionFactory.getKeyEvent(intent);
|
||||||
|
ComponentName intentComponent = intent.getComponent();
|
||||||
|
if (!Objects.equals(intent.getAction(), Intent.ACTION_MEDIA_BUTTON)
|
||||||
|
|| (intentComponent != null
|
||||||
|
&& !Objects.equals(intentComponent.getPackageName(), context.getPackageName()))
|
||||||
|
|| keyEvent == null
|
||||||
|
|| 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable command;
|
||||||
|
switch (keyEvent.getKeyCode()) {
|
||||||
|
case KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
|
command =
|
||||||
|
getPlayerWrapper().getPlayWhenReady()
|
||||||
|
? () -> sessionStub.pauseForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER)
|
||||||
|
: () -> sessionStub.playForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_PLAY:
|
||||||
|
command = () -> sessionStub.playForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_PAUSE:
|
||||||
|
command = () -> sessionStub.pauseForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_NEXT: // Fall through.
|
||||||
|
case KEYCODE_MEDIA_SKIP_FORWARD:
|
||||||
|
command =
|
||||||
|
() -> sessionStub.seekToNextForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_PREVIOUS: // Fall through.
|
||||||
|
case KEYCODE_MEDIA_SKIP_BACKWARD:
|
||||||
|
command =
|
||||||
|
() ->
|
||||||
|
sessionStub.seekToPreviousForControllerInfo(
|
||||||
|
controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_FAST_FORWARD:
|
||||||
|
command =
|
||||||
|
() -> sessionStub.seekForwardForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_REWIND:
|
||||||
|
command =
|
||||||
|
() -> sessionStub.seekBackForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
case KEYCODE_MEDIA_STOP:
|
||||||
|
command = () -> sessionStub.stopForControllerInfo(controllerInfo, UNKNOWN_SEQUENCE_NUMBER);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
postOrRun(
|
||||||
|
getApplicationHandler(),
|
||||||
|
() -> {
|
||||||
|
command.run();
|
||||||
|
sessionStub.getConnectedControllersManager().flushCommandQueue(controllerInfo);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* @FunctionalInterface */
|
/* @FunctionalInterface */
|
||||||
interface RemoteControllerTask {
|
interface RemoteControllerTask {
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import android.os.Handler;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.DoNotInline;
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
@ -157,7 +156,7 @@ public abstract class MediaSessionService extends Service {
|
|||||||
/** The action for {@link Intent} filter that must be declared by the service. */
|
/** The action for {@link Intent} filter that must be declared by the service. */
|
||||||
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
||||||
|
|
||||||
private static final String TAG = "MSSImpl";
|
private static final String TAG = "MSessionService";
|
||||||
|
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
@ -426,9 +425,8 @@ public abstract class MediaSessionService extends Service {
|
|||||||
}
|
}
|
||||||
addSession(session);
|
addSession(session);
|
||||||
}
|
}
|
||||||
@Nullable KeyEvent keyEvent = actionFactory.getKeyEvent(intent);
|
if (!session.getImpl().onMediaButtonEvent(intent)) {
|
||||||
if (keyEvent != null) {
|
Log.w(TAG, "Ignoring unrecognized media button intent.");
|
||||||
getMediaNotificationManager().onMediaButtonEvent(session, keyEvent);
|
|
||||||
}
|
}
|
||||||
} else if (session != null && actionFactory.isCustomAction(intent)) {
|
} else if (session != null && actionFactory.isCustomAction(intent)) {
|
||||||
@Nullable String customAction = actionFactory.getCustomAction(intent);
|
@Nullable String customAction = actionFactory.getCustomAction(intent);
|
||||||
|
@ -117,6 +117,12 @@ import java.util.concurrent.ExecutionException;
|
|||||||
/** The version of the IMediaSession interface. */
|
/** The version of the IMediaSession interface. */
|
||||||
public static final int VERSION_INT = 2;
|
public static final int VERSION_INT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number used when a controller method is triggered on the sesison side that wasn't
|
||||||
|
* initiated by the controller itself.
|
||||||
|
*/
|
||||||
|
public static final int UNKNOWN_SEQUENCE_NUMBER = Integer.MIN_VALUE;
|
||||||
|
|
||||||
private final WeakReference<MediaSessionImpl> sessionImpl;
|
private final WeakReference<MediaSessionImpl> sessionImpl;
|
||||||
private final MediaSessionManager sessionManager;
|
private final MediaSessionManager sessionManager;
|
||||||
private final ConnectedControllersManager<IBinder> connectedControllersManager;
|
private final ConnectedControllersManager<IBinder> connectedControllersManager;
|
||||||
@ -285,6 +291,18 @@ import java.util.concurrent.ExecutionException;
|
|||||||
int sequenceNumber,
|
int sequenceNumber,
|
||||||
@Player.Command int command,
|
@Player.Command int command,
|
||||||
SessionTask<ListenableFuture<Void>, K> task) {
|
SessionTask<ListenableFuture<Void>, K> task) {
|
||||||
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo, sequenceNumber, command, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <K extends MediaSessionImpl> void queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
ControllerInfo controller,
|
||||||
|
int sequenceNumber,
|
||||||
|
@Player.Command int command,
|
||||||
|
SessionTask<ListenableFuture<Void>, K> task) {
|
||||||
long token = Binder.clearCallingIdentity();
|
long token = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings({"unchecked", "cast.unsafe"})
|
@SuppressWarnings({"unchecked", "cast.unsafe"})
|
||||||
@ -293,11 +311,6 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (sessionImpl == null || sessionImpl.isReleased()) {
|
if (sessionImpl == null || sessionImpl.isReleased()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@Nullable
|
|
||||||
ControllerInfo controller = connectedControllersManager.getController(caller.asBinder());
|
|
||||||
if (controller == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
postOrRun(
|
postOrRun(
|
||||||
sessionImpl.getApplicationHandler(),
|
sessionImpl.getApplicationHandler(),
|
||||||
() -> {
|
() -> {
|
||||||
@ -621,8 +634,19 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller, sequenceNumber, COMMAND_STOP, sendSessionResultSuccess(player -> player.stop()));
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
stopForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo,
|
||||||
|
sequenceNumber,
|
||||||
|
COMMAND_STOP,
|
||||||
|
sendSessionResultSuccess(PlayerWrapper::stop));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -677,27 +701,30 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@Nullable
|
||||||
ControllerInfo controller = connectedControllersManager.getController(caller.asBinder());
|
ControllerInfo controller = connectedControllersManager.getController(caller.asBinder());
|
||||||
if (controller == null) {
|
if (controller != null) {
|
||||||
return;
|
playForControllerInfo(controller, sequenceNumber);
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
}
|
||||||
caller,
|
|
||||||
|
public void playForControllerInfo(ControllerInfo controller, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controller,
|
||||||
sequenceNumber,
|
sequenceNumber,
|
||||||
COMMAND_PLAY_PAUSE,
|
COMMAND_PLAY_PAUSE,
|
||||||
sendSessionResultSuccess(
|
sendSessionResultSuccess(
|
||||||
player -> {
|
player -> {
|
||||||
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
|
@Nullable MediaSessionImpl impl = sessionImpl.get();
|
||||||
if (sessionImpl == null || sessionImpl.isReleased()) {
|
if (impl == null || impl.isReleased()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sessionImpl.onPlayRequested()) {
|
if (impl.onPlayRequested()) {
|
||||||
if (player.getMediaItemCount() == 0) {
|
if (player.getMediaItemCount() == 0) {
|
||||||
// The player is in IDLE or ENDED state and has no media items in the playlist
|
// The player is in IDLE or ENDED state and has no media items in the playlist
|
||||||
// yet.
|
// yet. Handle the play command as a playback resumption command to try resume
|
||||||
// Handle the play command as a playback resumption command to try resume
|
|
||||||
// playback.
|
// playback.
|
||||||
sessionImpl.prepareAndPlayForPlaybackResumption(controller, player);
|
impl.prepareAndPlayForPlaybackResumption(controller, player);
|
||||||
} else {
|
} else {
|
||||||
Util.handlePlayButtonAction(player);
|
Util.handlePlayButtonAction(player);
|
||||||
}
|
}
|
||||||
@ -710,8 +737,16 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller, sequenceNumber, COMMAND_PLAY_PAUSE, sendSessionResultSuccess(Player::pause));
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
pauseForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pauseForControllerInfo(ControllerInfo controller, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controller, sequenceNumber, COMMAND_PLAY_PAUSE, sendSessionResultSuccess(Player::pause));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -787,8 +822,19 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller, sequenceNumber, COMMAND_SEEK_BACK, sendSessionResultSuccess(Player::seekBack));
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
seekBackForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekBackForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo,
|
||||||
|
sequenceNumber,
|
||||||
|
COMMAND_SEEK_BACK,
|
||||||
|
sendSessionResultSuccess(Player::seekBack));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -796,8 +842,16 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller,
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
seekForwardForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekForwardForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo,
|
||||||
sequenceNumber,
|
sequenceNumber,
|
||||||
COMMAND_SEEK_FORWARD,
|
COMMAND_SEEK_FORWARD,
|
||||||
sendSessionResultSuccess(Player::seekForward));
|
sendSessionResultSuccess(Player::seekForward));
|
||||||
@ -1365,8 +1419,16 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller,
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
seekToPreviousForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekToPreviousForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo,
|
||||||
sequenceNumber,
|
sequenceNumber,
|
||||||
COMMAND_SEEK_TO_PREVIOUS,
|
COMMAND_SEEK_TO_PREVIOUS,
|
||||||
sendSessionResultSuccess(Player::seekToPrevious));
|
sendSessionResultSuccess(Player::seekToPrevious));
|
||||||
@ -1377,8 +1439,19 @@ import java.util.concurrent.ExecutionException;
|
|||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queueSessionTaskWithPlayerCommand(
|
@Nullable
|
||||||
caller, sequenceNumber, COMMAND_SEEK_TO_NEXT, sendSessionResultSuccess(Player::seekToNext));
|
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
|
||||||
|
if (controllerInfo != null) {
|
||||||
|
seekToNextForControllerInfo(controllerInfo, sequenceNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekToNextForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
|
||||||
|
queueSessionTaskWithPlayerCommandForControllerInfo(
|
||||||
|
controllerInfo,
|
||||||
|
sequenceNumber,
|
||||||
|
COMMAND_SEEK_TO_NEXT,
|
||||||
|
sendSessionResultSuccess(Player::seekToNext));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,6 +39,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -411,6 +412,9 @@ public class MediaSessionServiceTest {
|
|||||||
serviceController.startCommand(/* flags= */ 0, /* startId= */ 0);
|
serviceController.startCommand(/* flags= */ 0, /* startId= */ 0);
|
||||||
|
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(service.callers).hasSize(1);
|
||||||
|
assertThat(service.session.isMediaNotificationController(service.callers.get(0))).isTrue();
|
||||||
|
controller.release();
|
||||||
serviceController.destroy();
|
serviceController.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,21 +505,35 @@ public class MediaSessionServiceTest {
|
|||||||
|
|
||||||
private static final class TestServiceWithPlaybackResumption extends MediaSessionService {
|
private static final class TestServiceWithPlaybackResumption extends MediaSessionService {
|
||||||
|
|
||||||
private List<MediaItem> mediaItems = ImmutableList.of();
|
private final List<MediaSession.ControllerInfo> callers;
|
||||||
|
|
||||||
public void setMediaItems(List<MediaItem> mediaItems) {
|
private ImmutableList<MediaItem> mediaItems;
|
||||||
this.mediaItems = mediaItems;
|
@Nullable private MediaSession session;
|
||||||
|
|
||||||
|
public TestServiceWithPlaybackResumption() {
|
||||||
|
callers = new ArrayList<>();
|
||||||
|
mediaItems = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable private MediaSession session;
|
public void setMediaItems(List<MediaItem> mediaItems) {
|
||||||
|
this.mediaItems = ImmutableList.copyOf(mediaItems);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
ForwardingPlayer forwardingPlayer =
|
||||||
|
new ForwardingPlayer(player) {
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
callers.add(session.getControllerForCurrentRequest());
|
||||||
|
super.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
session =
|
session =
|
||||||
new MediaSession.Builder(context, player)
|
new MediaSession.Builder(context, forwardingPlayer)
|
||||||
.setCallback(
|
.setCallback(
|
||||||
new MediaSession.Callback() {
|
new MediaSession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
@ -546,11 +564,14 @@ public class MediaSessionServiceTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
if (session != null) {
|
||||||
session.getPlayer().stop();
|
session.getPlayer().stop();
|
||||||
session.getPlayer().clearMediaItems();
|
session.getPlayer().clearMediaItems();
|
||||||
session.getPlayer().release();
|
session.getPlayer().release();
|
||||||
session.release();
|
session.release();
|
||||||
|
callers.clear();
|
||||||
session = null;
|
session = null;
|
||||||
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,10 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(modulePrefix + 'lib-session')
|
implementation project(modulePrefix + 'lib-session')
|
||||||
implementation project(modulePrefix + 'test-session-common')
|
implementation project(modulePrefix + 'test-session-common')
|
||||||
|
implementation project(modulePrefix + 'test-data')
|
||||||
implementation 'androidx.media:media:' + androidxMediaVersion
|
implementation 'androidx.media:media:' + androidxMediaVersion
|
||||||
implementation 'androidx.test:core:' + androidxTestCoreVersion
|
|
||||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
|
implementation 'androidx.test:core:' + androidxTestCoreVersion
|
||||||
implementation project(modulePrefix + 'test-data')
|
implementation project(modulePrefix + 'test-data')
|
||||||
androidTestImplementation project(modulePrefix + 'lib-exoplayer')
|
androidTestImplementation project(modulePrefix + 'lib-exoplayer')
|
||||||
androidTestImplementation project(modulePrefix + 'test-utils')
|
androidTestImplementation project(modulePrefix + 'test-utils')
|
||||||
|
@ -26,6 +26,7 @@ import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_
|
|||||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -43,6 +44,7 @@ import androidx.media3.session.MediaSession.ControllerInfo;
|
|||||||
import androidx.media3.test.session.R;
|
import androidx.media3.test.session.R;
|
||||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||||
import androidx.media3.test.session.common.MainLooperTestRule;
|
import androidx.media3.test.session.common.MainLooperTestRule;
|
||||||
|
import androidx.media3.test.session.common.TestHandler;
|
||||||
import androidx.media3.test.session.common.TestUtils;
|
import androidx.media3.test.session.common.TestUtils;
|
||||||
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@ -61,7 +63,6 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@ -73,16 +74,28 @@ import org.junit.runner.RunWith;
|
|||||||
@LargeTest
|
@LargeTest
|
||||||
public class MediaSessionCallbackTest {
|
public class MediaSessionCallbackTest {
|
||||||
|
|
||||||
private static final String TAG = "MSessionCallbackTest";
|
// Prepares the main looper.
|
||||||
|
|
||||||
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
|
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
|
||||||
|
|
||||||
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
|
@Rule
|
||||||
|
public final HandlerThreadTestRule playerThreadTestRule =
|
||||||
|
new HandlerThreadTestRule("MSessionCallbackTest:player");
|
||||||
|
|
||||||
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
|
@Rule
|
||||||
|
public final HandlerThreadTestRule controllerThreadTestRule =
|
||||||
|
new HandlerThreadTestRule("MSessionCallbackTest:controller");
|
||||||
|
|
||||||
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
|
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
|
||||||
|
|
||||||
|
// Used to create controllers in the service running in a different process.
|
||||||
|
@Rule
|
||||||
|
public final RemoteControllerTestRule remoteControllerTestRule = new RemoteControllerTestRule();
|
||||||
|
|
||||||
|
// Used to create controllers on a different thread in the local process.
|
||||||
|
@Rule
|
||||||
|
public final MediaControllerTestRule controllerTestRule =
|
||||||
|
new MediaControllerTestRule(controllerThreadTestRule);
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
private MockPlayer player;
|
private MockPlayer player;
|
||||||
private ListeningExecutorService executorService;
|
private ListeningExecutorService executorService;
|
||||||
@ -92,7 +105,7 @@ public class MediaSessionCallbackTest {
|
|||||||
context = ApplicationProvider.getApplicationContext();
|
context = ApplicationProvider.getApplicationContext();
|
||||||
player =
|
player =
|
||||||
new MockPlayer.Builder()
|
new MockPlayer.Builder()
|
||||||
.setApplicationLooper(threadTestRule.getHandler().getLooper())
|
.setApplicationLooper(playerThreadTestRule.getHandler().getLooper())
|
||||||
.build();
|
.build();
|
||||||
// Intentionally use an Executor with another thread to test asynchronous workflows involving
|
// Intentionally use an Executor with another thread to test asynchronous workflows involving
|
||||||
// background tasks.
|
// background tasks.
|
||||||
@ -129,7 +142,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("testOnConnect_correctControllerVersions")
|
.setId("testOnConnect_correctControllerVersions")
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(controllerVersion.get()).isEqualTo(MediaLibraryInfo.VERSION_INT);
|
assertThat(controllerVersion.get()).isEqualTo(MediaLibraryInfo.VERSION_INT);
|
||||||
@ -185,7 +198,7 @@ public class MediaSessionCallbackTest {
|
|||||||
"onConnect_acceptWithMissingSessionCommand_buttonDisabledAndPermissionDenied")
|
"onConnect_acceptWithMissingSessionCommand_buttonDisabledAndPermissionDenied")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController remoteController =
|
RemoteMediaController remoteController =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
ImmutableList<CommandButton> layout = remoteController.getCustomLayout();
|
ImmutableList<CommandButton> layout = remoteController.getCustomLayout();
|
||||||
|
|
||||||
@ -215,7 +228,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("onConnect_emptyPlayerCommands_commandReleaseAlwaysIncluded")
|
.setId("onConnect_emptyPlayerCommands_commandReleaseAlwaysIncluded")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController remoteController =
|
RemoteMediaController remoteController =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
assertThat(remoteController.getAvailableCommands().size()).isEqualTo(1);
|
assertThat(remoteController.getAvailableCommands().size()).isEqualTo(1);
|
||||||
assertThat(remoteController.getAvailableCommands().contains(Player.COMMAND_RELEASE)).isTrue();
|
assertThat(remoteController.getAvailableCommands().contains(Player.COMMAND_RELEASE)).isTrue();
|
||||||
@ -237,7 +250,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setCallback(callback)
|
.setCallback(callback)
|
||||||
.setId("testOnPostConnect_afterConnected")
|
.setId("testOnPostConnect_afterConnected")
|
||||||
.build());
|
.build());
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +276,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setCallback(callback)
|
.setCallback(callback)
|
||||||
.setId("testOnPostConnect_afterConnectionRejected")
|
.setId("testOnPostConnect_afterConnectionRejected")
|
||||||
.build());
|
.build());
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +309,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("testOnCommandRequest")
|
.setId("testOnCommandRequest")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.prepare();
|
controller.prepare();
|
||||||
Thread.sleep(NO_RESPONSE_TIMEOUT_MS);
|
Thread.sleep(NO_RESPONSE_TIMEOUT_MS);
|
||||||
@ -358,7 +371,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("testOnCustomCommand")
|
.setId("testOnCustomCommand")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
|
SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
|
||||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
@ -398,7 +411,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("testOnSetRating")
|
.setId("testOnSetRating")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
SessionResult result = controller.setRating(testMediaId, testRating);
|
SessionResult result = controller.setRating(testMediaId, testRating);
|
||||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
@ -434,7 +447,7 @@ public class MediaSessionCallbackTest {
|
|||||||
.setId("testOnSetRating")
|
.setId("testOnSetRating")
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
SessionResult result = controller.setRating(testRating);
|
SessionResult result = controller.setRating(testRating);
|
||||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
@ -459,7 +472,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItem(mediaItem);
|
controller.setMediaItem(mediaItem);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
@ -476,7 +489,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
||||||
@ -498,7 +511,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.setMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
controller.setMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
||||||
@ -518,7 +531,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
||||||
@ -538,7 +551,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.setMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
controller.setMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
||||||
@ -565,7 +578,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItem(mediaItem, /* startPositionMs= */ 1234);
|
controller.setMediaItem(mediaItem, /* startPositionMs= */ 1234);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
@ -594,7 +607,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItem(mediaItem, /* resetPosition= */ true);
|
controller.setMediaItem(mediaItem, /* resetPosition= */ true);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
@ -622,7 +635,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems);
|
controller.setMediaItems(mediaItems);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
@ -651,7 +664,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 1234);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 1234);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
@ -682,7 +695,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* resetPosition= */ true);
|
controller.setMediaItems(mediaItems, /* resetPosition= */ true);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
@ -712,7 +725,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.addMediaItem(mediaItem);
|
controller.addMediaItem(mediaItem);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
||||||
@ -729,7 +742,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
||||||
@ -750,7 +763,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.addMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
controller.addMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
||||||
@ -769,7 +782,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
||||||
@ -789,7 +802,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||||
controller.addMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
controller.addMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
||||||
@ -817,7 +830,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
controller.setMediaItem(existingItem);
|
controller.setMediaItem(existingItem);
|
||||||
|
|
||||||
controller.addMediaItem(/* index= */ 1, mediaItem);
|
controller.addMediaItem(/* index= */ 1, mediaItem);
|
||||||
@ -849,7 +862,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.addMediaItems(mediaItems);
|
controller.addMediaItems(mediaItems);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
||||||
@ -879,7 +892,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
controller.setMediaItem(existingItem);
|
controller.setMediaItem(existingItem);
|
||||||
|
|
||||||
controller.addMediaItems(/* index= */ 1, mediaItems);
|
controller.addMediaItems(/* index= */ 1, mediaItems);
|
||||||
@ -922,7 +935,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItem(mediaItem, /* startPositionMs= */ 100);
|
controller.setMediaItem(mediaItem, /* startPositionMs= */ 100);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
@ -960,7 +973,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
@ -1000,7 +1013,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
@ -1039,7 +1052,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
controller.setMediaItems(mediaItems, true);
|
controller.setMediaItems(mediaItems, true);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
|
|
||||||
@ -1077,7 +1090,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.play();
|
controller.play();
|
||||||
|
|
||||||
@ -1098,7 +1111,7 @@ public class MediaSessionCallbackTest {
|
|||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.play();
|
controller.play();
|
||||||
|
|
||||||
@ -1122,7 +1135,7 @@ public class MediaSessionCallbackTest {
|
|||||||
@Override
|
@Override
|
||||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||||
MediaSession mediaSession, ControllerInfo controller) {
|
MediaSession mediaSession, ControllerInfo controller) {
|
||||||
Assert.fail();
|
fail();
|
||||||
return Futures.immediateFuture(
|
return Futures.immediateFuture(
|
||||||
new MediaSession.MediaItemsWithStartPosition(
|
new MediaSession.MediaItemsWithStartPosition(
|
||||||
MediaTestUtils.createMediaItems(/* size= */ 10),
|
MediaTestUtils.createMediaItems(/* size= */ 10),
|
||||||
@ -1134,7 +1147,7 @@ public class MediaSessionCallbackTest {
|
|||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.play();
|
controller.play();
|
||||||
|
|
||||||
@ -1173,7 +1186,7 @@ public class MediaSessionCallbackTest {
|
|||||||
Bundle testConnectionHints = new Bundle();
|
Bundle testConnectionHints = new Bundle();
|
||||||
testConnectionHints.putString("test_key", "test_value");
|
testConnectionHints.putString("test_key", "test_value");
|
||||||
|
|
||||||
controllerTestRule.createRemoteController(
|
remoteControllerTestRule.createRemoteController(
|
||||||
session.getToken(), /* waitForConnection= */ false, testConnectionHints);
|
session.getToken(), /* waitForConnection= */ false, testConnectionHints);
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
|
assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
|
||||||
@ -1199,20 +1212,21 @@ public class MediaSessionCallbackTest {
|
|||||||
})
|
})
|
||||||
.build());
|
.build());
|
||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||||
controller.release();
|
controller.release();
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToNextMediaItem_inProcessController_correctMediaItemTransitionsEvents()
|
public void
|
||||||
|
seekToNextMediaItem_controllerListenerTriggeredByMasking_commandNotYetArrivedAtSession()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MediaItem mediaItem1 =
|
MediaItem mediaItem1 =
|
||||||
new MediaItem.Builder().setMediaId("id1").setUri("http://www.example.com/1").build();
|
new MediaItem.Builder().setMediaId("id1").setUri("http://www.example.com/1").build();
|
||||||
MediaItem mediaItem2 =
|
MediaItem mediaItem2 =
|
||||||
new MediaItem.Builder().setMediaId("id2").setUri("http://www.example.com/2").build();
|
new MediaItem.Builder().setMediaId("id2").setUri("http://www.example.com/2").build();
|
||||||
ExoPlayer testPlayer =
|
ExoPlayer testPlayer =
|
||||||
threadTestRule
|
playerThreadTestRule
|
||||||
.getHandler()
|
.getHandler()
|
||||||
.postAndSync(
|
.postAndSync(
|
||||||
() -> {
|
() -> {
|
||||||
@ -1220,48 +1234,174 @@ public class MediaSessionCallbackTest {
|
|||||||
exoPlayer.setMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
|
exoPlayer.setMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
|
||||||
return exoPlayer;
|
return exoPlayer;
|
||||||
});
|
});
|
||||||
List<String> capturedMediaItemIds = new ArrayList<>();
|
List<MediaItem> currentMediaItemsOfPlayer = new ArrayList<>();
|
||||||
List<Player.Events> capturedEvents = new ArrayList<>();
|
AtomicReference<MediaController> controller = new AtomicReference<>();
|
||||||
List<String> eventOrder = new ArrayList<>();
|
List<String> eventOrder = new ArrayList<>();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
MediaSession session =
|
// Listener added to player before the the session is built and the session adds a listener.
|
||||||
sessionTestRule.ensureReleaseAfterTest(
|
testPlayer.addListener(
|
||||||
new MediaSession.Builder(context, testPlayer)
|
|
||||||
.setId("seekToNextMediaItem_inProcessController_correctMediaItemTransitionsEvents")
|
|
||||||
.build());
|
|
||||||
MediaController controller =
|
|
||||||
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
|
||||||
.setApplicationLooper(threadTestRule.getHandler().getLooper())
|
|
||||||
.buildAsync()
|
|
||||||
.get();
|
|
||||||
controller.addListener(
|
|
||||||
new Player.Listener() {
|
new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||||
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem());
|
||||||
eventOrder.add("onMediaItemTransition");
|
eventOrder.add("player.onMediaItemTransition");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvents(Player player, Player.Events events) {
|
public void onEvents(Player player, Player.Events events) {
|
||||||
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||||
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
// Player still has the first item. Command has not yet arrived at the session.
|
||||||
capturedEvents.add(events);
|
currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem());
|
||||||
eventOrder.add("onEvents");
|
eventOrder.add("player.onEvents");
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
MediaSession session =
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, testPlayer)
|
||||||
|
.setId(
|
||||||
|
"listener_controllerListenerTriggeredByMasking_commandNotYetArrivedAtSession")
|
||||||
|
.build());
|
||||||
|
controller.set(controllerTestRule.createController(session.getToken()));
|
||||||
|
controller
|
||||||
|
.get()
|
||||||
|
.addListener(
|
||||||
|
/* listener= */ new Player.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||||
|
eventOrder.add("controller.onMediaItemTransition");
|
||||||
|
postToPlayerAndSync(
|
||||||
|
() -> currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvents(Player player, Player.Events events) {
|
||||||
|
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||||
|
// Triggered by masking in the same looper iteration as where
|
||||||
|
// controller.seekToNextMediaItem() is called.
|
||||||
|
eventOrder.add("controller.onEvents");
|
||||||
|
postToPlayerAndSync(
|
||||||
|
() -> currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem()));
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
threadTestRule.getHandler().postAndSync(testPlayer::seekToNextMediaItem);
|
postToControllerAndSync(controller.get()::seekToNextMediaItem);
|
||||||
|
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(capturedMediaItemIds).containsExactly("id2", "id2").inOrder();
|
assertThat(currentMediaItemsOfPlayer)
|
||||||
assertThat(eventOrder).containsExactly("onMediaItemTransition", "onEvents").inOrder();
|
.containsExactly(mediaItem1, mediaItem1, mediaItem2, mediaItem2)
|
||||||
assertThat(capturedEvents).hasSize(1);
|
.inOrder();
|
||||||
assertThat(capturedEvents.get(0).size()).isEqualTo(2);
|
assertThat(eventOrder)
|
||||||
assertThat(capturedEvents.get(0).contains(Player.EVENT_MEDIA_ITEM_TRANSITION)).isTrue();
|
.containsExactly(
|
||||||
assertThat(capturedEvents.get(0).contains(Player.EVENT_POSITION_DISCONTINUITY)).isTrue();
|
"controller.onMediaItemTransition",
|
||||||
|
"controller.onEvents",
|
||||||
|
"player.onMediaItemTransition",
|
||||||
|
"player.onEvents")
|
||||||
|
.inOrder();
|
||||||
|
postToControllerAndSync(() -> controller.get().release());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNextMediaItem_playerListenerTriggeredByMasking_immediateCallHasStaleController()
|
||||||
|
throws Exception {
|
||||||
|
MediaItem mediaItem1 =
|
||||||
|
new MediaItem.Builder().setMediaId("id1").setUri("http://www.example.com/1").build();
|
||||||
|
MediaItem mediaItem2 =
|
||||||
|
new MediaItem.Builder().setMediaId("id2").setUri("http://www.example.com/2").build();
|
||||||
|
ExoPlayer testPlayer =
|
||||||
|
playerThreadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(
|
||||||
|
() -> {
|
||||||
|
ExoPlayer exoPlayer = new TestExoPlayerBuilder(context).build();
|
||||||
|
exoPlayer.setMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
|
||||||
|
return exoPlayer;
|
||||||
|
});
|
||||||
|
List<String> currentMediaIdsOfController = new ArrayList<>();
|
||||||
|
List<String> eventOrder = new ArrayList<>();
|
||||||
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
|
AtomicReference<MediaController> controller = new AtomicReference<>();
|
||||||
|
// Listener added to player before the the session is built and the session adds a listener.
|
||||||
|
testPlayer.addListener(
|
||||||
|
new Player.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||||
|
postToControllerAndSync(
|
||||||
|
() ->
|
||||||
|
currentMediaIdsOfController.add(
|
||||||
|
controller.get().getCurrentMediaItem().mediaId));
|
||||||
|
eventOrder.add("player.onMediaItemTransition");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvents(Player player, Player.Events events) {
|
||||||
|
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||||
|
postToControllerAndSync(
|
||||||
|
() ->
|
||||||
|
currentMediaIdsOfController.add(
|
||||||
|
controller.get().getCurrentMediaItem().mediaId));
|
||||||
|
eventOrder.add("player.onEvents");
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
MediaSession session =
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, testPlayer)
|
||||||
|
.setId(
|
||||||
|
"listener_playerListenerTriggeredByMasking_statusUpdateArrivedAtSameProcessController")
|
||||||
|
.build());
|
||||||
|
controller.set(controllerTestRule.createController(session.getToken()));
|
||||||
|
controller
|
||||||
|
.get()
|
||||||
|
.addListener(
|
||||||
|
new Player.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||||
|
currentMediaIdsOfController.add(controller.get().getCurrentMediaItem().mediaId);
|
||||||
|
eventOrder.add("controller.onMediaItemTransition");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvents(Player player, Player.Events events) {
|
||||||
|
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||||
|
currentMediaIdsOfController.add(controller.get().getCurrentMediaItem().mediaId);
|
||||||
|
eventOrder.add("controller.onEvents");
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
postToPlayerAndSync(testPlayer::seekToNextMediaItem);
|
||||||
|
|
||||||
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(currentMediaIdsOfController).containsExactly("id1", "id2", "id2", "id2").inOrder();
|
||||||
|
assertThat(eventOrder)
|
||||||
|
.containsExactly(
|
||||||
|
"player.onMediaItemTransition",
|
||||||
|
"controller.onMediaItemTransition",
|
||||||
|
"controller.onEvents",
|
||||||
|
"player.onEvents")
|
||||||
|
.inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postToPlayerAndSync(TestHandler.TestRunnable r) {
|
||||||
|
try {
|
||||||
|
playerThreadTestRule.getHandler().postAndSync(r);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postToControllerAndSync(TestHandler.TestRunnable r) {
|
||||||
|
try {
|
||||||
|
controllerThreadTestRule.getHandler().postAndSync(r);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaItem updateMediaItemWithLocalConfiguration(MediaItem mediaItem) {
|
private static MediaItem updateMediaItemWithLocalConfiguration(MediaItem mediaItem) {
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
|
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_PREVIOUS;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_MEDIA_REWIND;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
|
||||||
import static androidx.media3.common.Player.STATE_IDLE;
|
import static androidx.media3.common.Player.STATE_IDLE;
|
||||||
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||||
@ -23,7 +30,9 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -31,11 +40,14 @@ import android.os.SystemClock;
|
|||||||
import android.support.v4.media.session.MediaControllerCompat;
|
import android.support.v4.media.session.MediaControllerCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import androidx.media.MediaSessionManager;
|
import androidx.media.MediaSessionManager;
|
||||||
|
import androidx.media3.common.ForwardingPlayer;
|
||||||
import androidx.media3.common.MediaLibraryInfo;
|
import androidx.media3.common.MediaLibraryInfo;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||||
import androidx.media3.test.session.common.MainLooperTestRule;
|
import androidx.media3.test.session.common.MainLooperTestRule;
|
||||||
import androidx.media3.test.session.common.TestHandler;
|
import androidx.media3.test.session.common.TestHandler;
|
||||||
@ -49,6 +61,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
@ -82,7 +95,6 @@ public class MediaSessionTest {
|
|||||||
context = ApplicationProvider.getApplicationContext();
|
context = ApplicationProvider.getApplicationContext();
|
||||||
handler = threadTestRule.getHandler();
|
handler = threadTestRule.getHandler();
|
||||||
player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build();
|
player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build();
|
||||||
|
|
||||||
session =
|
session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player)
|
new MediaSession.Builder(context, player)
|
||||||
@ -91,7 +103,7 @@ public class MediaSessionTest {
|
|||||||
new MediaSession.Callback() {
|
new MediaSession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
if (TextUtils.equals(
|
if (TextUtils.equals(
|
||||||
context.getPackageName(), controller.getPackageName())) {
|
context.getPackageName(), controller.getPackageName())) {
|
||||||
return MediaSession.Callback.super.onConnect(session, controller);
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
@ -149,7 +161,8 @@ public class MediaSessionTest {
|
|||||||
// expected. pass-through
|
// expected. pass-through
|
||||||
}
|
}
|
||||||
// Empty string as ID is allowed.
|
// Empty string as ID is allowed.
|
||||||
new MediaSession.Builder(context, player).setId("").build().release();
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, player).setId("").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -328,7 +341,7 @@ public class MediaSessionTest {
|
|||||||
new MediaSession.Callback() {
|
new MediaSession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
Future<SessionResult> result =
|
Future<SessionResult> result =
|
||||||
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
||||||
try {
|
try {
|
||||||
@ -342,7 +355,7 @@ public class MediaSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPostConnect(MediaSession session, MediaSession.ControllerInfo controller) {
|
public void onPostConnect(MediaSession session, ControllerInfo controller) {
|
||||||
Future<SessionResult> result =
|
Future<SessionResult> result =
|
||||||
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
||||||
try {
|
try {
|
||||||
@ -365,10 +378,6 @@ public class MediaSessionTest {
|
|||||||
/** Test {@link MediaSession#getSessionCompatToken()}. */
|
/** Test {@link MediaSession#getSessionCompatToken()}. */
|
||||||
@Test
|
@Test
|
||||||
public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat() throws Exception {
|
public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat() throws Exception {
|
||||||
String expectedControllerCompatPackageName =
|
|
||||||
(21 <= Util.SDK_INT && Util.SDK_INT < 24)
|
|
||||||
? MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER
|
|
||||||
: context.getPackageName();
|
|
||||||
MediaSession session =
|
MediaSession session =
|
||||||
sessionTestRule.ensureReleaseAfterTest(
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
new MediaSession.Builder(context, player)
|
new MediaSession.Builder(context, player)
|
||||||
@ -377,9 +386,10 @@ public class MediaSessionTest {
|
|||||||
new MediaSession.Callback() {
|
new MediaSession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
if (TextUtils.equals(
|
if (TextUtils.equals(
|
||||||
expectedControllerCompatPackageName, controller.getPackageName())) {
|
getControllerCallerPackageName(controller),
|
||||||
|
controller.getPackageName())) {
|
||||||
return MediaSession.Callback.super.onConnect(session, controller);
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
}
|
}
|
||||||
return MediaSession.ConnectionResult.reject();
|
return MediaSession.ConnectionResult.reject();
|
||||||
@ -416,7 +426,7 @@ public class MediaSessionTest {
|
|||||||
new MediaSession.Callback() {
|
new MediaSession.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
controllerVersionRef.set(controller.getControllerVersion());
|
controllerVersionRef.set(controller.getControllerVersion());
|
||||||
connectedLatch.countDown();
|
connectedLatch.countDown();
|
||||||
return MediaSession.Callback.super.onConnect(session, controller);
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
@ -494,4 +504,302 @@ public class MediaSessionTest {
|
|||||||
|
|
||||||
assertThat(bufferedPositionsMs).containsExactly(0L, 0L, 0L, 0L, 0L).inOrder();
|
assertThat(bufferedPositionsMs).containsExactly(0L, 0L, 0L, 0L, 0L).inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_allSupportedKeys_notificationControllerConnected_dispatchesEvent()
|
||||||
|
throws Exception {
|
||||||
|
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||||
|
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(player, session);
|
||||||
|
session.set(
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||||
|
.setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
|
||||||
|
.setCallback(
|
||||||
|
new MediaSession.Callback() {
|
||||||
|
@Override
|
||||||
|
public MediaSession.ConnectionResult onConnect(
|
||||||
|
MediaSession session, ControllerInfo controller) {
|
||||||
|
if (TextUtils.equals(
|
||||||
|
context.getPackageName(), controller.getPackageName())) {
|
||||||
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
|
}
|
||||||
|
return MediaSession.ConnectionResult.reject();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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_TO_NEXT, TIMEOUT_MS);
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
|
||||||
|
assertThat(callerCollectorPlayer.callingControllers).hasSize(7);
|
||||||
|
for (ControllerInfo controllerInfo : callerCollectorPlayer.callingControllers) {
|
||||||
|
assertThat(session.get().isMediaNotificationController(controllerInfo)).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
onMediaButtonEvent_allSupportedKeys_notificationControllerNotConnected_dispatchesEventThroughFrameworkFallback()
|
||||||
|
throws Exception {
|
||||||
|
AtomicReference<MediaSession> session = new AtomicReference<>();
|
||||||
|
CallerCollectorPlayer callerCollectorPlayer = new CallerCollectorPlayer(player, session);
|
||||||
|
session.set(
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, callerCollectorPlayer)
|
||||||
|
.setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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();
|
||||||
|
|
||||||
|
// Fallback code path through platform session when MediaSessionImpl doesn't handle the event.
|
||||||
|
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_TO_NEXT, TIMEOUT_MS);
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
|
||||||
|
assertThat(callerCollectorPlayer.callingControllers).hasSize(7);
|
||||||
|
for (ControllerInfo controllerInfo : callerCollectorPlayer.callingControllers) {
|
||||||
|
assertThat(session.get().isMediaNotificationController(controllerInfo)).isFalse();
|
||||||
|
assertThat(controllerInfo.getControllerVersion())
|
||||||
|
.isEqualTo(ControllerInfo.LEGACY_CONTROLLER_VERSION);
|
||||||
|
assertThat(controllerInfo.getPackageName())
|
||||||
|
.isEqualTo(getControllerCallerPackageName(controllerInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_noKeyEvent_returnsFalse() {
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_noKeyEvent_mediaNotificationControllerConnected_returnsFalse()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||||
|
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||||
|
.setConnectionHints(connectionHints)
|
||||||
|
.buildAsync()
|
||||||
|
.get();
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.removeExtra(Intent.EXTRA_KEY_EVENT);
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_invalidKeyEvent_returnsFalse() {
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
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);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_invalidKeyEvent_mediaNotificationControllerConnected_returnsFalse()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||||
|
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||||
|
.setConnectionHints(connectionHints)
|
||||||
|
.buildAsync()
|
||||||
|
.get();
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
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);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_invalidAction_returnsFalse() {
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.setAction("notAMediaButtonAction");
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_invalidAction_mediaNotificationControllerConnected_returnsFalse()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||||
|
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||||
|
.setConnectionHints(connectionHints)
|
||||||
|
.buildAsync()
|
||||||
|
.get();
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.setAction("notAMediaButtonAction");
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onMediaButtonEvent_invalidComponent_returnsFalse() {
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.setComponent(new ComponentName("a.package", "a.class"));
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
onMediaButtonEvent_invalidComponent_mediaNotificationControllerConnected_returnsFalse()
|
||||||
|
throws Exception {
|
||||||
|
Bundle connectionHints = new Bundle();
|
||||||
|
connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||||
|
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||||
|
.setConnectionHints(connectionHints)
|
||||||
|
.buildAsync()
|
||||||
|
.get();
|
||||||
|
Intent intent = getMediaButtonIntent(KEYCODE_MEDIA_PLAY);
|
||||||
|
intent.setComponent(new ComponentName("a.package", "a.class"));
|
||||||
|
|
||||||
|
boolean isEventHandled = session.getImpl().onMediaButtonEvent(intent);
|
||||||
|
|
||||||
|
assertThat(isEventHandled).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent getMediaButtonIntent(int keyCode) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||||
|
intent.setComponent(
|
||||||
|
new ComponentName(ApplicationProvider.getApplicationContext(), Object.class));
|
||||||
|
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the expected {@link MediaSessionManager.RemoteUserInfo#getPackageName()} of a
|
||||||
|
* controller hosted in the test companion app.
|
||||||
|
*
|
||||||
|
* <p>Before API 21 and after API 23 the package name is {@link Context#getPackageName()} of the
|
||||||
|
* {@link ApplicationProvider#getApplicationContext() application under test}.
|
||||||
|
*
|
||||||
|
* <p>The early implementations (API 21 - 23), the platform MediaSession doesn't report the caller
|
||||||
|
* package name. Instead the package of the RemoteUserInfo is set for all external controllers to
|
||||||
|
* the same {@code MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER} (see
|
||||||
|
* MediaSessionCompat.MediaSessionCallbackApi21.setCurrentControllerInfo()).
|
||||||
|
*
|
||||||
|
* <p>Calling this method should only be required to test legacy behaviour.
|
||||||
|
*/
|
||||||
|
private static String getControllerCallerPackageName(ControllerInfo controllerInfo) {
|
||||||
|
return (Util.SDK_INT < 21
|
||||||
|
|| Util.SDK_INT > 23
|
||||||
|
|| controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION)
|
||||||
|
? ApplicationProvider.getApplicationContext().getPackageName()
|
||||||
|
: MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CallerCollectorPlayer extends ForwardingPlayer {
|
||||||
|
private final List<ControllerInfo> callingControllers;
|
||||||
|
private final AtomicReference<MediaSession> session;
|
||||||
|
|
||||||
|
public CallerCollectorPlayer(Player player, AtomicReference<MediaSession> mediaSession) {
|
||||||
|
super(player);
|
||||||
|
this.session = mediaSession;
|
||||||
|
callingControllers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekBack() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.seekBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekForward() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.seekForward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToNext() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.seekToNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToPrevious() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.seekToPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
callingControllers.add(session.get().getControllerForCurrentRequest());
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user