parent
61770f8a61
commit
1a43aa3602
@ -56,19 +56,6 @@ import androidx.media3.common.util.Util;
|
||||
public static final String EXTRAS_KEY_ACTION_CUSTOM_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 int customActionPendingIntentRequestCode = 0;
|
||||
@ -110,7 +97,6 @@ import androidx.media3.common.util.Util;
|
||||
mediaSession, customCommand.customAction, customCommand.customExtras));
|
||||
}
|
||||
|
||||
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||
@Override
|
||||
public PendingIntent createMediaActionPendingIntent(
|
||||
MediaSession mediaSession, @Player.Command long command) {
|
||||
@ -150,7 +136,6 @@ import androidx.media3.common.util.Util;
|
||||
return KEYCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||
private PendingIntent createCustomActionPendingIntent(
|
||||
MediaSession mediaSession, String action, Bundle extras) {
|
||||
Intent intent = new Intent(ACTION_CUSTOM);
|
||||
@ -177,6 +162,19 @@ import androidx.media3.common.util.Util;
|
||||
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},
|
||||
* or {@code null} if no custom action is found in the {@code intent}.
|
||||
@ -203,7 +201,6 @@ import androidx.media3.common.util.Util;
|
||||
private static final class Api26 {
|
||||
private Api26() {}
|
||||
|
||||
@SuppressWarnings("PendingIntentMutability") // We can't use SaferPendingIntent
|
||||
public static PendingIntent createForegroundServicePendingIntent(
|
||||
Service service, int keyCode, Intent intent) {
|
||||
return PendingIntent.getForegroundService(
|
||||
|
@ -17,6 +17,14 @@ package androidx.media3.session;
|
||||
|
||||
import static android.app.Service.STOP_FOREGROUND_DETACH;
|
||||
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 android.annotation.SuppressLint;
|
||||
@ -26,6 +34,7 @@ import android.content.pm.ServiceInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
@ -65,7 +74,7 @@ import java.util.concurrent.TimeoutException;
|
||||
private final NotificationManagerCompat notificationManagerCompat;
|
||||
private final Executor mainExecutor;
|
||||
private final Intent startSelfIntent;
|
||||
private final Map<MediaSession, ListenableFuture<MediaController>> controllerMap;
|
||||
private final Map<MediaSession, ControllerAndListener> controllerAndListenerMap;
|
||||
|
||||
private int totalNotificationCount;
|
||||
@Nullable private MediaNotification mediaNotification;
|
||||
@ -82,30 +91,34 @@ import java.util.concurrent.TimeoutException;
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainExecutor = (runnable) -> Util.postOrRun(mainHandler, runnable);
|
||||
startSelfIntent = new Intent(mediaSessionService, mediaSessionService.getClass());
|
||||
controllerMap = new HashMap<>();
|
||||
controllerAndListenerMap = new HashMap<>();
|
||||
startedInForeground = false;
|
||||
}
|
||||
|
||||
public void addSession(MediaSession session) {
|
||||
if (controllerMap.containsKey(session)) {
|
||||
if (controllerAndListenerMap.containsKey(session)) {
|
||||
return;
|
||||
}
|
||||
MediaControllerListener listener = new MediaControllerListener(mediaSessionService, session);
|
||||
MediaControllerListener controllerListener =
|
||||
new MediaControllerListener(mediaSessionService, session);
|
||||
PlayerListener playerListener = new PlayerListener(mediaSessionService, session);
|
||||
Bundle connectionHints = new Bundle();
|
||||
connectionHints.putBoolean(KEY_MEDIA_NOTIFICATION_MANAGER, true);
|
||||
ListenableFuture<MediaController> controllerFuture =
|
||||
new MediaController.Builder(mediaSessionService, session.getToken())
|
||||
.setConnectionHints(connectionHints)
|
||||
.setListener(listener)
|
||||
.setListener(controllerListener)
|
||||
.setApplicationLooper(Looper.getMainLooper())
|
||||
.buildAsync();
|
||||
controllerMap.put(session, controllerFuture);
|
||||
controllerAndListenerMap.put(
|
||||
session, new ControllerAndListener(controllerFuture, playerListener));
|
||||
controllerFuture.addListener(
|
||||
() -> {
|
||||
try {
|
||||
MediaController controller = controllerFuture.get(/* time= */ 0, MILLISECONDS);
|
||||
listener.onConnected(shouldShowNotification(session));
|
||||
controller.addListener(listener);
|
||||
// Assert connection success.
|
||||
controllerFuture.get(/* time= */ 0, MILLISECONDS);
|
||||
controllerListener.onConnected(shouldShowNotification(session));
|
||||
session.getImpl().addPlayerListener(playerListener);
|
||||
} catch (CancellationException
|
||||
| ExecutionException
|
||||
| InterruptedException
|
||||
@ -118,9 +131,52 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
|
||||
public void removeSession(MediaSession session) {
|
||||
@Nullable ListenableFuture<MediaController> future = controllerMap.remove(session);
|
||||
if (future != null) {
|
||||
MediaController.releaseFuture(future);
|
||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.remove(session);
|
||||
if (controllerAndListener != null) {
|
||||
session.getImpl().removePlayerListener(controllerAndListener.listener);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,11 +210,11 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
int notificationSequence = ++totalNotificationCount;
|
||||
MediaController mediaNotificationController = null;
|
||||
ListenableFuture<MediaController> controller = controllerMap.get(session);
|
||||
if (controller != null && controller.isDone()) {
|
||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.get(session);
|
||||
if (controllerAndListener != null && controllerAndListener.controller.isDone()) {
|
||||
try {
|
||||
mediaNotificationController = Futures.getDone(controller);
|
||||
} catch (ExecutionException e) {
|
||||
mediaNotificationController = Futures.getDone(controllerAndListener.controller);
|
||||
} catch (CancellationException | ExecutionException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
@ -261,13 +317,13 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Nullable
|
||||
private MediaController getConnectedControllerForSession(MediaSession session) {
|
||||
ListenableFuture<MediaController> controller = controllerMap.get(session);
|
||||
if (controller == null) {
|
||||
ControllerAndListener controllerAndListener = controllerAndListenerMap.get(session);
|
||||
if (controllerAndListener == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Futures.getDone(controller);
|
||||
} catch (ExecutionException exception) {
|
||||
return Futures.getDone(controllerAndListener.controller);
|
||||
} catch (CancellationException | ExecutionException exception) {
|
||||
// We should never reach this.
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
@ -305,8 +361,7 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MediaControllerListener
|
||||
implements MediaController.Listener, Player.Listener {
|
||||
private static final class MediaControllerListener implements MediaController.Listener {
|
||||
private final MediaSessionService mediaSessionService;
|
||||
private final MediaSession session;
|
||||
|
||||
@ -344,6 +399,18 @@ import java.util.concurrent.TimeoutException;
|
||||
mediaSessionService.onUpdateNotificationInternal(
|
||||
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
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
@ -354,8 +421,13 @@ import java.util.concurrent.TimeoutException;
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
Player.EVENT_MEDIA_METADATA_CHANGED,
|
||||
Player.EVENT_TIMELINE_CHANGED)) {
|
||||
mediaSessionService.onUpdateNotificationInternal(
|
||||
session, /* startInForegroundWhenPaused= */ false);
|
||||
// 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(
|
||||
session, /* startInForegroundWhenPaused= */ false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -385,6 +457,17 @@ import java.util.concurrent.TimeoutException;
|
||||
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)
|
||||
private static class Api24 {
|
||||
|
||||
|
@ -15,28 +15,15 @@
|
||||
*/
|
||||
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.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.SDK_INT;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN;
|
||||
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@ -50,7 +37,6 @@ import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.GuardedBy;
|
||||
@ -148,6 +134,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private boolean closed;
|
||||
|
||||
// Should be only accessed on the application looper
|
||||
private final List<Player.Listener> wrapperListeners;
|
||||
private long sessionPositionUpdateDelayMs;
|
||||
private boolean isMediaNotificationControllerConnected;
|
||||
private ImmutableList<CommandButton> customLayout;
|
||||
@ -174,6 +161,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
sessionStub = new MediaSessionStub(thisRef);
|
||||
this.sessionActivity = sessionActivity;
|
||||
this.customLayout = customLayout;
|
||||
wrapperListeners = new ArrayList<>();
|
||||
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
applicationHandler = new Handler(player.getApplicationLooper());
|
||||
@ -252,14 +240,38 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
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(
|
||||
@Nullable PlayerWrapper oldPlayerWrapper, PlayerWrapper newPlayerWrapper) {
|
||||
playerWrapper = newPlayerWrapper;
|
||||
if (oldPlayerWrapper != null) {
|
||||
oldPlayerWrapper.removeListener(checkStateNotNull(this.playerListener));
|
||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
||||
oldPlayerWrapper.removeListener(wrapperListeners.get(i));
|
||||
}
|
||||
}
|
||||
PlayerListener playerListener = new PlayerListener(this, newPlayerWrapper);
|
||||
newPlayerWrapper.addListener(playerListener);
|
||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
||||
newPlayerWrapper.addListener(wrapperListeners.get(i));
|
||||
}
|
||||
this.playerListener = playerListener;
|
||||
|
||||
dispatchRemoteControllerTaskToLegacyStub(
|
||||
@ -291,6 +303,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
if (playerListener != null) {
|
||||
playerWrapper.removeListener(playerListener);
|
||||
}
|
||||
for (int i = 0; i < wrapperListeners.size(); i++) {
|
||||
playerWrapper.removeListener(wrapperListeners.get(i));
|
||||
}
|
||||
wrapperListeners.clear();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// Catch all exceptions to ensure the rest of this method to be executed as exceptions may be
|
||||
@ -1073,75 +1089,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
(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 */
|
||||
interface RemoteControllerTask {
|
||||
|
||||
|
@ -31,6 +31,7 @@ import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.GuardedBy;
|
||||
@ -156,7 +157,7 @@ public abstract class MediaSessionService extends Service {
|
||||
/** The action for {@link Intent} filter that must be declared by the service. */
|
||||
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
||||
|
||||
private static final String TAG = "MSessionService";
|
||||
private static final String TAG = "MSSImpl";
|
||||
|
||||
private final Object lock;
|
||||
private final Handler mainHandler;
|
||||
@ -425,8 +426,9 @@ public abstract class MediaSessionService extends Service {
|
||||
}
|
||||
addSession(session);
|
||||
}
|
||||
if (!session.getImpl().onMediaButtonEvent(intent)) {
|
||||
Log.w(TAG, "Ignoring unrecognized media button intent.");
|
||||
@Nullable KeyEvent keyEvent = actionFactory.getKeyEvent(intent);
|
||||
if (keyEvent != null) {
|
||||
getMediaNotificationManager().onMediaButtonEvent(session, keyEvent);
|
||||
}
|
||||
} else if (session != null && actionFactory.isCustomAction(intent)) {
|
||||
@Nullable String customAction = actionFactory.getCustomAction(intent);
|
||||
|
@ -39,7 +39,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -412,9 +411,6 @@ public class MediaSessionServiceTest {
|
||||
serviceController.startCommand(/* flags= */ 0, /* startId= */ 0);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -505,35 +501,21 @@ public class MediaSessionServiceTest {
|
||||
|
||||
private static final class TestServiceWithPlaybackResumption extends MediaSessionService {
|
||||
|
||||
private final List<MediaSession.ControllerInfo> callers;
|
||||
|
||||
private ImmutableList<MediaItem> mediaItems;
|
||||
@Nullable private MediaSession session;
|
||||
|
||||
public TestServiceWithPlaybackResumption() {
|
||||
callers = new ArrayList<>();
|
||||
mediaItems = ImmutableList.of();
|
||||
}
|
||||
private List<MediaItem> mediaItems = ImmutableList.of();
|
||||
|
||||
public void setMediaItems(List<MediaItem> mediaItems) {
|
||||
this.mediaItems = ImmutableList.copyOf(mediaItems);
|
||||
this.mediaItems = mediaItems;
|
||||
}
|
||||
|
||||
@Nullable private MediaSession session;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
ForwardingPlayer forwardingPlayer =
|
||||
new ForwardingPlayer(player) {
|
||||
@Override
|
||||
public void play() {
|
||||
callers.add(session.getControllerForCurrentRequest());
|
||||
super.play();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, forwardingPlayer)
|
||||
new MediaSession.Builder(context, player)
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
@ -564,14 +546,11 @@ public class MediaSessionServiceTest {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (session != null) {
|
||||
session.getPlayer().stop();
|
||||
session.getPlayer().clearMediaItems();
|
||||
session.getPlayer().release();
|
||||
session.release();
|
||||
callers.clear();
|
||||
session = null;
|
||||
}
|
||||
session.getPlayer().stop();
|
||||
session.getPlayer().clearMediaItems();
|
||||
session.getPlayer().release();
|
||||
session.release();
|
||||
session = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -41,10 +41,9 @@ android {
|
||||
dependencies {
|
||||
implementation project(modulePrefix + 'lib-session')
|
||||
implementation project(modulePrefix + 'test-session-common')
|
||||
implementation project(modulePrefix + 'test-data')
|
||||
implementation 'androidx.media:media:' + androidxMediaVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'androidx.test:core:' + androidxTestCoreVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation project(modulePrefix + 'test-data')
|
||||
androidTestImplementation project(modulePrefix + 'lib-exoplayer')
|
||||
androidTestImplementation project(modulePrefix + 'test-utils')
|
||||
|
@ -26,7 +26,6 @@ import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@ -44,7 +43,6 @@ import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.test.session.R;
|
||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||
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.utils.TestExoPlayerBuilder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@ -63,6 +61,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
@ -74,28 +73,16 @@ import org.junit.runner.RunWith;
|
||||
@LargeTest
|
||||
public class MediaSessionCallbackTest {
|
||||
|
||||
// Prepares the main looper.
|
||||
private static final String TAG = "MSessionCallbackTest";
|
||||
|
||||
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
|
||||
|
||||
@Rule
|
||||
public final HandlerThreadTestRule playerThreadTestRule =
|
||||
new HandlerThreadTestRule("MSessionCallbackTest:player");
|
||||
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
|
||||
|
||||
@Rule
|
||||
public final HandlerThreadTestRule controllerThreadTestRule =
|
||||
new HandlerThreadTestRule("MSessionCallbackTest:controller");
|
||||
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
|
||||
|
||||
@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 MockPlayer player;
|
||||
private ListeningExecutorService executorService;
|
||||
@ -105,7 +92,7 @@ public class MediaSessionCallbackTest {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
player =
|
||||
new MockPlayer.Builder()
|
||||
.setApplicationLooper(playerThreadTestRule.getHandler().getLooper())
|
||||
.setApplicationLooper(threadTestRule.getHandler().getLooper())
|
||||
.build();
|
||||
// Intentionally use an Executor with another thread to test asynchronous workflows involving
|
||||
// background tasks.
|
||||
@ -142,7 +129,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("testOnConnect_correctControllerVersions")
|
||||
.build());
|
||||
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(controllerVersion.get()).isEqualTo(MediaLibraryInfo.VERSION_INT);
|
||||
@ -198,7 +185,7 @@ public class MediaSessionCallbackTest {
|
||||
"onConnect_acceptWithMissingSessionCommand_buttonDisabledAndPermissionDenied")
|
||||
.build());
|
||||
RemoteMediaController remoteController =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
ImmutableList<CommandButton> layout = remoteController.getCustomLayout();
|
||||
|
||||
@ -228,7 +215,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("onConnect_emptyPlayerCommands_commandReleaseAlwaysIncluded")
|
||||
.build());
|
||||
RemoteMediaController remoteController =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
assertThat(remoteController.getAvailableCommands().size()).isEqualTo(1);
|
||||
assertThat(remoteController.getAvailableCommands().contains(Player.COMMAND_RELEASE)).isTrue();
|
||||
@ -250,7 +237,7 @@ public class MediaSessionCallbackTest {
|
||||
.setCallback(callback)
|
||||
.setId("testOnPostConnect_afterConnected")
|
||||
.build());
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@ -276,7 +263,7 @@ public class MediaSessionCallbackTest {
|
||||
.setCallback(callback)
|
||||
.setId("testOnPostConnect_afterConnectionRejected")
|
||||
.build());
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
|
||||
}
|
||||
|
||||
@ -309,7 +296,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("testOnCommandRequest")
|
||||
.build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.prepare();
|
||||
Thread.sleep(NO_RESPONSE_TIMEOUT_MS);
|
||||
@ -371,7 +358,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("testOnCustomCommand")
|
||||
.build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
@ -411,7 +398,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("testOnSetRating")
|
||||
.build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
SessionResult result = controller.setRating(testMediaId, testRating);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
@ -447,7 +434,7 @@ public class MediaSessionCallbackTest {
|
||||
.setId("testOnSetRating")
|
||||
.build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
SessionResult result = controller.setRating(testRating);
|
||||
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
@ -472,7 +459,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItem(mediaItem);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
@ -489,7 +476,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
||||
@ -511,7 +498,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.setMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
||||
@ -531,7 +518,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.setMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
||||
@ -551,7 +538,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.setMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
||||
@ -578,7 +565,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItem(mediaItem, /* startPositionMs= */ 1234);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||
@ -607,7 +594,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItem(mediaItem, /* resetPosition= */ true);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
@ -635,7 +622,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItems(mediaItems);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
@ -664,7 +651,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 1234);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||
@ -695,7 +682,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItems(mediaItems, /* resetPosition= */ true);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
@ -725,7 +712,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.addMediaItem(mediaItem);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
||||
@ -742,7 +729,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithoutLocalConfiguration);
|
||||
@ -763,7 +750,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.addMediaItemsIncludeLocalConfiguration(mediaItemsWithoutLocalConfiguration);
|
||||
@ -782,7 +769,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.addMediaItemIncludeLocalConfiguration(mediaItemWithLocalConfiguration);
|
||||
@ -802,7 +789,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
// Default MediaSession.Callback.onAddMediaItems will be called
|
||||
controller.addMediaItemsIncludeLocalConfiguration(fullMediaItems);
|
||||
@ -830,7 +817,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
controller.setMediaItem(existingItem);
|
||||
|
||||
controller.addMediaItem(/* index= */ 1, mediaItem);
|
||||
@ -862,7 +849,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.addMediaItems(mediaItems);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
|
||||
@ -892,7 +879,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
controller.setMediaItem(existingItem);
|
||||
|
||||
controller.addMediaItems(/* index= */ 1, mediaItems);
|
||||
@ -935,7 +922,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItem(mediaItem, /* startPositionMs= */ 100);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||
@ -973,7 +960,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||
@ -1013,7 +1000,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
@ -1052,7 +1039,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
controller.setMediaItems(mediaItems, true);
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||
|
||||
@ -1090,7 +1077,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.play();
|
||||
|
||||
@ -1111,7 +1098,7 @@ public class MediaSessionCallbackTest {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.play();
|
||||
|
||||
@ -1135,7 +1122,7 @@ public class MediaSessionCallbackTest {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
fail();
|
||||
Assert.fail();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
MediaTestUtils.createMediaItems(/* size= */ 10),
|
||||
@ -1147,7 +1134,7 @@ public class MediaSessionCallbackTest {
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.play();
|
||||
|
||||
@ -1186,7 +1173,7 @@ public class MediaSessionCallbackTest {
|
||||
Bundle testConnectionHints = new Bundle();
|
||||
testConnectionHints.putString("test_key", "test_value");
|
||||
|
||||
remoteControllerTestRule.createRemoteController(
|
||||
controllerTestRule.createRemoteController(
|
||||
session.getToken(), /* waitForConnection= */ false, testConnectionHints);
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
|
||||
@ -1212,107 +1199,20 @@ public class MediaSessionCallbackTest {
|
||||
})
|
||||
.build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
controllerTestRule.createRemoteController(session.getToken());
|
||||
controller.release();
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
seekToNextMediaItem_controllerListenerTriggeredByMasking_commandNotYetArrivedAtSession()
|
||||
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<MediaItem> currentMediaItemsOfPlayer = new ArrayList<>();
|
||||
AtomicReference<MediaController> controller = new AtomicReference<>();
|
||||
List<String> eventOrder = new ArrayList<>();
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
// 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) {
|
||||
currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem());
|
||||
eventOrder.add("player.onMediaItemTransition");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||
// Player still has the first item. Command has not yet arrived at the session.
|
||||
currentMediaItemsOfPlayer.add(testPlayer.getCurrentMediaItem());
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
postToControllerAndSync(controller.get()::seekToNextMediaItem);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(currentMediaItemsOfPlayer)
|
||||
.containsExactly(mediaItem1, mediaItem1, mediaItem2, mediaItem2)
|
||||
.inOrder();
|
||||
assertThat(eventOrder)
|
||||
.containsExactly(
|
||||
"controller.onMediaItemTransition",
|
||||
"controller.onEvents",
|
||||
"player.onMediaItemTransition",
|
||||
"player.onEvents")
|
||||
.inOrder();
|
||||
postToControllerAndSync(() -> controller.get().release());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekToNextMediaItem_playerListenerTriggeredByMasking_immediateCallHasStaleController()
|
||||
public void seekToNextMediaItem_inProcessController_correctMediaItemTransitionsEvents()
|
||||
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
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
@ -1320,88 +1220,48 @@ public class MediaSessionCallbackTest {
|
||||
exoPlayer.setMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
|
||||
return exoPlayer;
|
||||
});
|
||||
List<String> currentMediaIdsOfController = new ArrayList<>();
|
||||
List<String> capturedMediaItemIds = new ArrayList<>();
|
||||
List<Player.Events> capturedEvents = 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(
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
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() {
|
||||
@Override
|
||||
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||
postToControllerAndSync(
|
||||
() ->
|
||||
currentMediaIdsOfController.add(
|
||||
controller.get().getCurrentMediaItem().mediaId));
|
||||
eventOrder.add("player.onMediaItemTransition");
|
||||
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
||||
eventOrder.add("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");
|
||||
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
||||
capturedEvents.add(events);
|
||||
eventOrder.add("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);
|
||||
threadTestRule.getHandler().postAndSync(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());
|
||||
}
|
||||
assertThat(capturedMediaItemIds).containsExactly("id2", "id2").inOrder();
|
||||
assertThat(eventOrder).containsExactly("onMediaItemTransition", "onEvents").inOrder();
|
||||
assertThat(capturedEvents).hasSize(1);
|
||||
assertThat(capturedEvents.get(0).size()).isEqualTo(2);
|
||||
assertThat(capturedEvents.get(0).contains(Player.EVENT_MEDIA_ITEM_TRANSITION)).isTrue();
|
||||
assertThat(capturedEvents.get(0).contains(Player.EVENT_POSITION_DISCONTINUITY)).isTrue();
|
||||
}
|
||||
|
||||
private static MediaItem updateMediaItemWithLocalConfiguration(MediaItem mediaItem) {
|
||||
|
@ -15,13 +15,6 @@
|
||||
*/
|
||||
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.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||
@ -30,9 +23,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
@ -40,14 +31,11 @@ import android.os.SystemClock;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media3.common.ForwardingPlayer;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Log;
|
||||
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.MainLooperTestRule;
|
||||
import androidx.media3.test.session.common.TestHandler;
|
||||
@ -61,7 +49,6 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
@ -95,6 +82,7 @@ public class MediaSessionTest {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
handler = threadTestRule.getHandler();
|
||||
player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build();
|
||||
|
||||
session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
@ -103,7 +91,7 @@ public class MediaSessionTest {
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
if (TextUtils.equals(
|
||||
context.getPackageName(), controller.getPackageName())) {
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
@ -161,8 +149,7 @@ public class MediaSessionTest {
|
||||
// expected. pass-through
|
||||
}
|
||||
// Empty string as ID is allowed.
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setId("").build());
|
||||
new MediaSession.Builder(context, player).setId("").build().release();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -341,7 +328,7 @@ public class MediaSessionTest {
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
Future<SessionResult> result =
|
||||
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
||||
try {
|
||||
@ -355,7 +342,7 @@ public class MediaSessionTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostConnect(MediaSession session, ControllerInfo controller) {
|
||||
public void onPostConnect(MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
Future<SessionResult> result =
|
||||
session.sendCustomCommand(controller, testCommand, /* args= */ Bundle.EMPTY);
|
||||
try {
|
||||
@ -378,6 +365,10 @@ public class MediaSessionTest {
|
||||
/** Test {@link MediaSession#getSessionCompatToken()}. */
|
||||
@Test
|
||||
public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat() throws Exception {
|
||||
String expectedControllerCompatPackageName =
|
||||
(21 <= Util.SDK_INT && Util.SDK_INT < 24)
|
||||
? MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER
|
||||
: context.getPackageName();
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
@ -386,10 +377,9 @@ public class MediaSessionTest {
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
if (TextUtils.equals(
|
||||
getControllerCallerPackageName(controller),
|
||||
controller.getPackageName())) {
|
||||
expectedControllerCompatPackageName, controller.getPackageName())) {
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
}
|
||||
return MediaSession.ConnectionResult.reject();
|
||||
@ -426,7 +416,7 @@ public class MediaSessionTest {
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
controllerVersionRef.set(controller.getControllerVersion());
|
||||
connectedLatch.countDown();
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
@ -504,302 +494,4 @@ public class MediaSessionTest {
|
||||
|
||||
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