Add missing command checks to MediaSessionLegacyStub and PlayerWrapper

This player didn't fully check all player commands before calling the
respective methods.

PiperOrigin-RevId: 502353704
This commit is contained in:
tonihei 2023-01-16 12:39:38 +00:00 committed by Rohit Singh
parent 664ab72d09
commit a2a44cdc02
7 changed files with 638 additions and 57 deletions

View file

@ -1290,7 +1290,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
if (msg.what == MSG_PLAYER_INFO_CHANGED) {
playerInfo =
playerInfo.copyWithTimelineAndSessionPositionInfo(
getPlayerWrapper().getCurrentTimeline(),
getPlayerWrapper().getCurrentTimelineWithCommandCheck(),
getPlayerWrapper().createSessionPositionInfoForBundling());
dispatchOnPlayerInfoChanged(playerInfo, excludeTimeline, excludeTracks);
excludeTimeline = true;

View file

@ -23,7 +23,9 @@ import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD;
import static androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
@ -256,10 +258,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|| playbackState == STATE_ENDED
|| playbackState == STATE_IDLE) {
if (playbackState == STATE_IDLE) {
playerWrapper.prepare();
playerWrapper.prepareIfCommandAvailable();
} else if (playbackState == STATE_ENDED) {
playerWrapper.seekTo(
playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET);
playerWrapper.seekToDefaultPositionIfCommandAvailable();
}
playerWrapper.play();
} else {
@ -308,10 +309,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper();
@Player.State int playbackState = playerWrapper.getPlaybackState();
if (playbackState == Player.STATE_IDLE) {
playerWrapper.prepare();
playerWrapper.prepareIfCommandAvailable();
} else if (playbackState == Player.STATE_ENDED) {
playerWrapper.seekTo(
playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET);
playerWrapper.seekToDefaultPositionIfCommandAvailable();
}
if (sessionImpl.onPlayRequested()) {
playerWrapper.play();
@ -369,18 +369,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onSkipToNext() {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_NEXT,
controller -> sessionImpl.getPlayerWrapper().seekToNext(),
sessionCompat.getCurrentControllerInfo());
if (sessionImpl.getPlayerWrapper().isCommandAvailable(COMMAND_SEEK_TO_NEXT)) {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_NEXT,
controller -> sessionImpl.getPlayerWrapper().seekToNext(),
sessionCompat.getCurrentControllerInfo());
} else {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
controller -> sessionImpl.getPlayerWrapper().seekToNextMediaItem(),
sessionCompat.getCurrentControllerInfo());
}
}
@Override
public void onSkipToPrevious() {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_PREVIOUS,
controller -> sessionImpl.getPlayerWrapper().seekToPrevious(),
sessionCompat.getCurrentControllerInfo());
if (sessionImpl.getPlayerWrapper().isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)) {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_PREVIOUS,
controller -> sessionImpl.getPlayerWrapper().seekToPrevious(),
sessionCompat.getCurrentControllerInfo());
} else {
dispatchSessionTaskWithPlayerCommand(
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
controller -> sessionImpl.getPlayerWrapper().seekToPreviousMediaItem(),
sessionCompat.getCurrentControllerInfo());
}
}
@Override
@ -435,7 +449,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
dispatchSessionTaskWithSessionCommand(
SessionCommand.COMMAND_CODE_SESSION_SET_RATING,
controller -> {
@Nullable MediaItem currentItem = sessionImpl.getPlayerWrapper().getCurrentMediaItem();
@Nullable
MediaItem currentItem =
sessionImpl.getPlayerWrapper().getCurrentMediaItemWithCommandCheck();
if (currentItem == null) {
return;
}
@ -494,12 +510,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
Log.w(TAG, "onRemoveQueueItem(): Media ID shouldn't be null");
return;
}
Timeline timeline = sessionImpl.getPlayerWrapper().getCurrentTimeline();
PlayerWrapper player = sessionImpl.getPlayerWrapper();
if (!player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
Log.w(TAG, "Can't remove item by id without availabe COMMAND_GET_TIMELINE");
return;
}
Timeline timeline = player.getCurrentTimeline();
Timeline.Window window = new Timeline.Window();
for (int i = 0; i < timeline.getWindowCount(); i++) {
MediaItem mediaItem = timeline.getWindow(i, window).mediaItem;
if (TextUtils.equals(mediaItem.mediaId, mediaId)) {
sessionImpl.getPlayerWrapper().removeMediaItem(i);
player.removeMediaItem(i);
return;
}
}
@ -700,16 +721,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
postOrRun(
sessionImpl.getApplicationHandler(),
() -> {
Player player = sessionImpl.getPlayerWrapper();
PlayerWrapper player = sessionImpl.getPlayerWrapper();
player.setMediaItems(mediaItems);
@Player.State int playbackState = player.getPlaybackState();
if (playbackState == Player.STATE_IDLE) {
player.prepare();
player.prepareIfCommandAvailable();
} else if (playbackState == Player.STATE_ENDED) {
player.seekTo(/* positionMs= */ C.TIME_UNSET);
player.seekToDefaultPositionIfCommandAvailable();
}
if (play) {
player.play();
player.playIfCommandAvailable();
}
});
}
@ -875,19 +896,21 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
throws RemoteException {
// Tells the playlist change first, so current media item index change notification
// can point to the valid current media item in the playlist.
Timeline newTimeline = newPlayerWrapper.getCurrentTimeline();
Timeline newTimeline = newPlayerWrapper.getCurrentTimelineWithCommandCheck();
if (oldPlayerWrapper == null
|| !Util.areEqual(oldPlayerWrapper.getCurrentTimeline(), newTimeline)) {
|| !Util.areEqual(oldPlayerWrapper.getCurrentTimelineWithCommandCheck(), newTimeline)) {
onTimelineChanged(seq, newTimeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
}
MediaMetadata newPlaylistMetadata = newPlayerWrapper.getPlaylistMetadata();
MediaMetadata newPlaylistMetadata = newPlayerWrapper.getPlaylistMetadataWithCommandCheck();
if (oldPlayerWrapper == null
|| !Util.areEqual(oldPlayerWrapper.getPlaylistMetadata(), newPlaylistMetadata)) {
|| !Util.areEqual(
oldPlayerWrapper.getPlaylistMetadataWithCommandCheck(), newPlaylistMetadata)) {
onPlaylistMetadataChanged(seq, newPlaylistMetadata);
}
MediaMetadata newMediaMetadata = newPlayerWrapper.getMediaMetadata();
MediaMetadata newMediaMetadata = newPlayerWrapper.getMediaMetadataWithCommandCheck();
if (oldPlayerWrapper == null
|| !Util.areEqual(oldPlayerWrapper.getMediaMetadata(), newMediaMetadata)) {
|| !Util.areEqual(
oldPlayerWrapper.getMediaMetadataWithCommandCheck(), newMediaMetadata)) {
onMediaMetadataChanged(seq, newMediaMetadata);
}
if (oldPlayerWrapper == null
@ -904,9 +927,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
onDeviceInfoChanged(seq, newPlayerWrapper.getDeviceInfo());
// Rest of changes are all notified via PlaybackStateCompat.
@Nullable MediaItem newMediaItem = newPlayerWrapper.getCurrentMediaItem();
@Nullable MediaItem newMediaItem = newPlayerWrapper.getCurrentMediaItemWithCommandCheck();
if (oldPlayerWrapper == null
|| !Util.areEqual(oldPlayerWrapper.getCurrentMediaItem(), newMediaItem)) {
|| !Util.areEqual(oldPlayerWrapper.getCurrentMediaItemWithCommandCheck(), newMediaItem)) {
// Note: This will update both PlaybackStateCompat and metadata.
onMediaItemTransition(
seq, newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
@ -1135,7 +1158,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
PlayerWrapper player = sessionImpl.getPlayerWrapper();
volumeProviderCompat = player.createVolumeProviderCompat();
if (volumeProviderCompat == null) {
int streamType = MediaUtils.getLegacyStreamType(player.getAudioAttributes());
int streamType =
MediaUtils.getLegacyStreamType(player.getAudioAttributesWithCommandCheck());
sessionCompat.setPlaybackToLocal(streamType);
} else {
sessionCompat.setPlaybackToRemote(volumeProviderCompat);
@ -1158,10 +1182,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
private void updateMetadataIfChanged() {
Player player = sessionImpl.getPlayerWrapper();
@Nullable MediaItem currentMediaItem = player.getCurrentMediaItem();
MediaMetadata newMediaMetadata = player.getMediaMetadata();
long newDurationMs = player.getDuration();
PlayerWrapper player = sessionImpl.getPlayerWrapper();
@Nullable MediaItem currentMediaItem = player.getCurrentMediaItemWithCommandCheck();
MediaMetadata newMediaMetadata = player.getMediaMetadataWithCommandCheck();
long newDurationMs = player.getDurationWithCommandCheck();
String newMediaId =
currentMediaItem != null ? currentMediaItem.mediaId : MediaItem.DEFAULT_MEDIA_ID;
@Nullable

View file

@ -26,6 +26,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.Surface;
import android.view.SurfaceHolder;
@ -34,6 +35,7 @@ import android.view.TextureView;
import androidx.annotation.Nullable;
import androidx.media.VolumeProviderCompat;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.ForwardingPlayer;
import androidx.media3.common.MediaItem;
@ -43,9 +45,11 @@ import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.List;
@ -133,6 +137,12 @@ import java.util.List;
super.play();
}
public void playIfCommandAvailable() {
if (isCommandAvailable(COMMAND_PLAY_PAUSE)) {
play();
}
}
@Override
public void pause() {
verifyApplicationThread();
@ -145,6 +155,12 @@ import java.util.List;
super.prepare();
}
public void prepareIfCommandAvailable() {
if (isCommandAvailable(COMMAND_PREPARE)) {
prepare();
}
}
@Override
public void stop() {
verifyApplicationThread();
@ -163,6 +179,18 @@ import java.util.List;
super.seekToDefaultPosition(mediaItemIndex);
}
@Override
public void seekToDefaultPosition() {
verifyApplicationThread();
super.seekToDefaultPosition();
}
public void seekToDefaultPositionIfCommandAvailable() {
if (isCommandAvailable(Player.COMMAND_SEEK_TO_DEFAULT_POSITION)) {
seekToDefaultPosition();
}
}
@Override
public void seekTo(long positionMs) {
verifyApplicationThread();
@ -223,6 +251,10 @@ import java.util.List;
return super.getDuration();
}
public long getDurationWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) ? getDuration() : C.TIME_UNSET;
}
@Override
public long getBufferedPosition() {
verifyApplicationThread();
@ -355,6 +387,12 @@ import java.util.List;
return super.getAudioAttributes();
}
public AudioAttributes getAudioAttributesWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)
? getAudioAttributes()
: AudioAttributes.DEFAULT;
}
@Override
public void setMediaItem(MediaItem mediaItem) {
verifyApplicationThread();
@ -549,12 +587,22 @@ import java.util.List;
return super.getCurrentTimeline();
}
public Timeline getCurrentTimelineWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_TIMELINE) ? getCurrentTimeline() : Timeline.EMPTY;
}
@Override
public MediaMetadata getPlaylistMetadata() {
verifyApplicationThread();
return super.getPlaylistMetadata();
}
public MediaMetadata getPlaylistMetadataWithCommandCheck() {
return isCommandAvailable(Player.COMMAND_GET_MEDIA_ITEMS_METADATA)
? getPlaylistMetadata()
: MediaMetadata.EMPTY;
}
@Override
public int getRepeatMode() {
verifyApplicationThread();
@ -574,6 +622,11 @@ import java.util.List;
return super.getCurrentMediaItem();
}
@Nullable
public MediaItem getCurrentMediaItemWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) ? getCurrentMediaItem() : null;
}
@Override
public int getMediaItemCount() {
verifyApplicationThread();
@ -631,6 +684,10 @@ import java.util.List;
return super.getVolume();
}
public float getVolumeWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_VOLUME) ? getVolume() : 0;
}
@Override
public void setVolume(float volume) {
verifyApplicationThread();
@ -643,6 +700,10 @@ import java.util.List;
return super.getCurrentCues();
}
public CueGroup getCurrentCuesWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_TEXT) ? getCurrentCues() : CueGroup.EMPTY_TIME_ZERO;
}
@Override
public DeviceInfo getDeviceInfo() {
verifyApplicationThread();
@ -655,18 +716,32 @@ import java.util.List;
return super.getDeviceVolume();
}
public int getDeviceVolumeWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_DEVICE_VOLUME) ? getDeviceVolume() : 0;
}
@Override
public boolean isDeviceMuted() {
verifyApplicationThread();
return super.isDeviceMuted();
}
public boolean isDeviceMutedWithCommandCheck() {
return isCommandAvailable(Player.COMMAND_GET_DEVICE_VOLUME) && isDeviceMuted();
}
@Override
public void setDeviceVolume(int volume) {
verifyApplicationThread();
super.setDeviceVolume(volume);
}
public void setDeviceVolumeIfCommandAvailable(int volume) {
if (isCommandAvailable(COMMAND_SET_DEVICE_VOLUME)) {
setDeviceVolume(volume);
}
}
@Override
public void increaseDeviceVolume() {
verifyApplicationThread();
@ -729,6 +804,12 @@ import java.util.List;
return super.getMediaMetadata();
}
public MediaMetadata getMediaMetadataWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)
? getMediaMetadata()
: MediaMetadata.EMPTY;
}
@Override
public boolean isCommandAvailable(@Command int command) {
verifyApplicationThread();
@ -753,6 +834,71 @@ import java.util.List;
super.setTrackSelectionParameters(parameters);
}
@Override
public void seekToPrevious() {
verifyApplicationThread();
super.seekToPrevious();
}
@Override
public long getMaxSeekToPreviousPosition() {
verifyApplicationThread();
return super.getMaxSeekToPreviousPosition();
}
@Override
public void seekToNext() {
verifyApplicationThread();
super.seekToNext();
}
@Override
public Tracks getCurrentTracks() {
verifyApplicationThread();
return super.getCurrentTracks();
}
public Tracks getCurrentTracksWithCommandCheck() {
return isCommandAvailable(COMMAND_GET_TRACKS) ? getCurrentTracks() : Tracks.EMPTY;
}
@Nullable
@Override
public Object getCurrentManifest() {
verifyApplicationThread();
return super.getCurrentManifest();
}
@Override
public int getCurrentPeriodIndex() {
verifyApplicationThread();
return super.getCurrentPeriodIndex();
}
@Override
public boolean isCurrentMediaItemDynamic() {
verifyApplicationThread();
return super.isCurrentMediaItemDynamic();
}
@Override
public boolean isCurrentMediaItemLive() {
verifyApplicationThread();
return super.isCurrentMediaItemLive();
}
@Override
public boolean isCurrentMediaItemSeekable() {
verifyApplicationThread();
return super.isCurrentMediaItemSeekable();
}
@Override
public Size getSurfaceSize() {
verifyApplicationThread();
return super.getSurfaceSize();
}
public PlaybackStateCompat createPlaybackStateCompat() {
if (legacyStatusCode != STATUS_CODE_SUCCESS_COMPAT) {
return new PlaybackStateCompat.Builder()
@ -799,22 +945,28 @@ import java.util.List;
|| getAvailableCommands().contains(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) {
allActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
}
long queueItemId = MediaUtils.convertToQueueItemId(getCurrentMediaItemIndex());
long queueItemId =
isCommandAvailable(COMMAND_GET_TIMELINE)
? MediaUtils.convertToQueueItemId(getCurrentMediaItemIndex())
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
float playbackSpeed = getPlaybackParameters().speed;
float sessionPlaybackSpeed = isPlaying() ? playbackSpeed : 0f;
Bundle extras = new Bundle();
extras.putFloat(EXTRAS_KEY_PLAYBACK_SPEED_COMPAT, playbackSpeed);
@Nullable MediaItem currentMediaItem = getCurrentMediaItem();
@Nullable MediaItem currentMediaItem = getCurrentMediaItemWithCommandCheck();
if (currentMediaItem != null && !MediaItem.DEFAULT_MEDIA_ID.equals(currentMediaItem.mediaId)) {
extras.putString(EXTRAS_KEY_MEDIA_ID_COMPAT, currentMediaItem.mediaId);
}
boolean canReadPositions = isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM);
long compatPosition =
canReadPositions ? getCurrentPosition() : PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
long compatBufferedPosition = canReadPositions ? getBufferedPosition() : 0;
PlaybackStateCompat.Builder builder =
new PlaybackStateCompat.Builder()
.setState(
state, getCurrentPosition(), sessionPlaybackSpeed, SystemClock.elapsedRealtime())
.setState(state, compatPosition, sessionPlaybackSpeed, SystemClock.elapsedRealtime())
.setActions(allActions)
.setActiveQueueItemId(queueItemId)
.setBufferedPosition(getBufferedPosition())
.setBufferedPosition(compatBufferedPosition)
.setExtras(extras);
for (int i = 0; i < customLayout.size(); i++) {
@ -853,11 +1005,11 @@ import java.util.List;
}
}
Handler handler = new Handler(getApplicationLooper());
return new VolumeProviderCompat(
volumeControlType, getDeviceInfo().maxVolume, getDeviceVolume()) {
int currentVolume = getDeviceVolumeWithCommandCheck();
return new VolumeProviderCompat(volumeControlType, getDeviceInfo().maxVolume, currentVolume) {
@Override
public void onSetVolumeTo(int volume) {
postOrRun(handler, () -> setDeviceVolume(volume));
postOrRun(handler, () -> setDeviceVolumeIfCommandAvailable(volume));
}
@Override
@ -865,6 +1017,9 @@ import java.util.List;
postOrRun(
handler,
() -> {
if (!isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)) {
return;
}
switch (direction) {
case AudioManager.ADJUST_RAISE:
increaseDeviceVolume();
@ -879,7 +1034,7 @@ import java.util.List;
setDeviceMuted(false);
break;
case AudioManager.ADJUST_TOGGLE_MUTE:
setDeviceMuted(!isDeviceMuted());
setDeviceMuted(!isDeviceMutedWithCommandCheck());
break;
default:
Log.w(
@ -898,6 +1053,9 @@ import java.util.List;
* <p>This excludes window uid and period uid that wouldn't be preserved when bundling.
*/
public PositionInfo createPositionInfoForBundling() {
if (!isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) {
return SessionPositionInfo.DEFAULT_POSITION_INFO;
}
return new PositionInfo(
/* windowUid= */ null,
getCurrentMediaItemIndex(),
@ -916,6 +1074,9 @@ import java.util.List;
* <p>This excludes window uid and period uid that wouldn't be preserved when bundling.
*/
public SessionPositionInfo createSessionPositionInfoForBundling() {
if (!isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) {
return SessionPositionInfo.DEFAULT;
}
return new SessionPositionInfo(
createPositionInfoForBundling(),
isPlayingAd(),
@ -941,25 +1102,25 @@ import java.util.List;
getRepeatMode(),
getShuffleModeEnabled(),
getVideoSize(),
getCurrentTimeline(),
getPlaylistMetadata(),
getVolume(),
getAudioAttributes(),
getCurrentCues(),
getCurrentTimelineWithCommandCheck(),
getPlaylistMetadataWithCommandCheck(),
getVolumeWithCommandCheck(),
getAudioAttributesWithCommandCheck(),
getCurrentCuesWithCommandCheck(),
getDeviceInfo(),
getDeviceVolume(),
isDeviceMuted(),
getDeviceVolumeWithCommandCheck(),
isDeviceMutedWithCommandCheck(),
getPlayWhenReady(),
PlayerInfo.PLAY_WHEN_READY_CHANGE_REASON_DEFAULT,
getPlaybackSuppressionReason(),
getPlaybackState(),
isPlaying(),
isLoading(),
getMediaMetadata(),
getMediaMetadataWithCommandCheck(),
getSeekBackIncrement(),
getSeekForwardIncrement(),
getMaxSeekToPreviousPosition(),
getCurrentTracks(),
getCurrentTracksWithCommandCheck(),
getTrackSelectionParameters());
}

View file

@ -16,6 +16,7 @@
package androidx.media3.session;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
import android.os.Looper;
@ -42,6 +43,7 @@ public class PlayerWrapperTest {
@Before
public void setUp() {
playerWrapper = new PlayerWrapper(player);
when(player.isCommandAvailable(anyInt())).thenReturn(true);
when(player.getApplicationLooper()).thenReturn(Looper.myLooper());
}

View file

@ -18,7 +18,9 @@ package androidx.media3.session;
import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
import static androidx.media3.common.Player.COMMAND_PREPARE;
import static androidx.media3.common.Player.STATE_ENDED;
import static androidx.media3.common.Player.STATE_IDLE;
import static androidx.media3.common.Player.STATE_READY;
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
@ -204,7 +206,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
public void play() throws Exception {
public void play_whileReady_callsPlay() throws Exception {
player.playbackState = STATE_READY;
session =
new MediaSession.Builder(context, player)
.setId("play")
@ -217,6 +220,92 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@Test
public void play_whileIdle_callsPrepareAndPlay() throws Exception {
player.playbackState = STATE_IDLE;
session =
new MediaSession.Builder(context, player)
.setId("play")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@Test
public void play_whileIdleWithoutPrepareCommandAvailable_callsJustPlay() throws Exception {
player.playbackState = STATE_IDLE;
player.commands =
new Player.Commands.Builder().addAllCommands().remove(Player.COMMAND_PREPARE).build();
session =
new MediaSession.Builder(context, player)
.setId("play")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@Test
public void play_whileEnded_callsSeekToDefaultPositionAndPlay() throws Exception {
player.playbackState = STATE_ENDED;
session =
new MediaSession.Builder(context, player)
.setId("play")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
}
@Test
public void play_whileEndedWithoutSeekToDefaultPositionCommandAvailable_callsJustPlay()
throws Exception {
player.playbackState = STATE_ENDED;
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_SEEK_TO_DEFAULT_POSITION)
.build();
session =
new MediaSession.Builder(context, player)
.setId("play")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
}
@Test
@ -428,7 +517,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
public void skipToPrevious() throws Exception {
public void skipToPrevious_withAllCommandsAvailable_callsSeekToPrevious() throws Exception {
session =
new MediaSession.Builder(context, player)
.setId("skipToPrevious")
@ -444,7 +533,29 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
public void skipToNext() throws Exception {
public void skipToPrevious_withoutSeekToPreviousCommandAvailable_callsSeekToPreviousMediaItem()
throws Exception {
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_SEEK_TO_PREVIOUS)
.build();
session =
new MediaSession.Builder(context, player)
.setId("skipToPrevious")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToPrevious();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM, TIMEOUT_MS);
}
@Test
public void skipToNext_withAllCommandsAvailable_callsSeekToNext() throws Exception {
session =
new MediaSession.Builder(context, player)
.setId("skipToNext")
@ -459,6 +570,25 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
}
@Test
public void skipToNext_withoutSeekToNextCommandAvailable_callsSeekToNextMediaItem()
throws Exception {
player.commands =
new Player.Commands.Builder().addAllCommands().remove(Player.COMMAND_SEEK_TO_NEXT).build();
session =
new MediaSession.Builder(context, player)
.setId("skipToNext")
.setCallback(new TestSessionCallback())
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToNext();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT_MEDIA_ITEM, TIMEOUT_MS);
}
@Test
public void skipToQueueItem() throws Exception {
session =
@ -1049,6 +1179,101 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
}
@Test
public void prepareFromMediaUri_withoutAvailablePrepareCommand_justCallsSetMediaItems()
throws Exception {
MediaItem resolvedMediaItem = MediaItem.fromUri(TEST_URI);
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<List<MediaItem>> onAddMediaItems(
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
return Futures.immediateFuture(ImmutableList.of(resolvedMediaItem));
}
};
player.commands =
new Player.Commands.Builder().addAllCommands().remove(COMMAND_PREPARE).build();
session =
new MediaSession.Builder(context, player)
.setId("prepareFromMediaUri")
.setCallback(callback)
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().prepareFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
}
@Test
public void playFromMediaUri_withoutAvailablePrepareCommand_justCallsSetMediaItemsAndPlay()
throws Exception {
MediaItem resolvedMediaItem = MediaItem.fromUri(TEST_URI);
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<List<MediaItem>> onAddMediaItems(
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
return Futures.immediateFuture(ImmutableList.of(resolvedMediaItem));
}
};
player.commands =
new Player.Commands.Builder().addAllCommands().remove(COMMAND_PREPARE).build();
session =
new MediaSession.Builder(context, player)
.setId("prepareFromMediaUri")
.setCallback(callback)
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().playFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
}
@Test
public void playFromMediaUri_withoutAvailablePrepareAndPlayCommand_justCallsSetMediaItems()
throws Exception {
MediaItem resolvedMediaItem = MediaItem.fromUri(TEST_URI);
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<List<MediaItem>> onAddMediaItems(
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
return Futures.immediateFuture(ImmutableList.of(resolvedMediaItem));
}
};
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.removeAll(COMMAND_PREPARE, COMMAND_PLAY_PAUSE)
.build();
session =
new MediaSession.Builder(context, player)
.setId("prepareFromMediaUri")
.setCallback(callback)
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().playFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
}
@Test
public void setRating() throws Exception {
int ratingType = RatingCompat.RATING_5_STARS;

View file

@ -220,7 +220,7 @@ public class MediaSessionKeyEventTest {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
}

View file

@ -38,12 +38,19 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.ForwardingPlayer;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.Rating;
import androidx.media3.common.StarRating;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.text.CueGroup;
import androidx.media3.session.MediaSession.ControllerInfo;
import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.media3.test.session.common.MainLooperTestRule;
@ -262,6 +269,168 @@ public class MediaSessionPermissionTest {
TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT));
}
@Test
public void setPlayer_withoutAvailableCommands_doesNotCallProtectedPlayerGetters()
throws Exception {
MockPlayer mockPlayer =
new MockPlayer.Builder()
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
// Set remote device info to ensure we also cover the volume provider compat setup.
mockPlayer.deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
Player player =
new ForwardingPlayer(mockPlayer) {
@Override
public boolean isCommandAvailable(int command) {
return false;
}
@Override
public Tracks getCurrentTracks() {
throw new UnsupportedOperationException();
}
@Override
public MediaMetadata getMediaMetadata() {
throw new UnsupportedOperationException();
}
@Override
public MediaMetadata getPlaylistMetadata() {
throw new UnsupportedOperationException();
}
@Override
public Timeline getCurrentTimeline() {
throw new UnsupportedOperationException();
}
@Override
public int getCurrentPeriodIndex() {
throw new UnsupportedOperationException();
}
@Override
public int getCurrentMediaItemIndex() {
throw new UnsupportedOperationException();
}
@Override
public int getNextMediaItemIndex() {
throw new UnsupportedOperationException();
}
@Override
public int getPreviousMediaItemIndex() {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public MediaItem getCurrentMediaItem() {
throw new UnsupportedOperationException();
}
@Override
public long getDuration() {
throw new UnsupportedOperationException();
}
@Override
public long getCurrentPosition() {
throw new UnsupportedOperationException();
}
@Override
public long getBufferedPosition() {
throw new UnsupportedOperationException();
}
@Override
public long getTotalBufferedDuration() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCurrentMediaItemDynamic() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCurrentMediaItemLive() {
throw new UnsupportedOperationException();
}
@Override
public boolean isPlayingAd() {
throw new UnsupportedOperationException();
}
@Override
public int getCurrentAdGroupIndex() {
throw new UnsupportedOperationException();
}
@Override
public int getCurrentAdIndexInAdGroup() {
throw new UnsupportedOperationException();
}
@Override
public long getContentDuration() {
throw new UnsupportedOperationException();
}
@Override
public long getContentPosition() {
throw new UnsupportedOperationException();
}
@Override
public long getContentBufferedPosition() {
throw new UnsupportedOperationException();
}
@Override
public AudioAttributes getAudioAttributes() {
throw new UnsupportedOperationException();
}
@Override
public CueGroup getCurrentCues() {
throw new UnsupportedOperationException();
}
@Override
public int getDeviceVolume() {
throw new UnsupportedOperationException();
}
@Override
public boolean isDeviceMuted() {
throw new UnsupportedOperationException();
}
};
MediaSession session = new MediaSession.Builder(context, player).setId(SESSION_ID).build();
MediaController controller =
new MediaController.Builder(context, session.getToken())
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.buildAsync()
.get();
// Test passes if none of the protected player getters have been called.
threadTestRule
.getHandler()
.postAndSync(
() -> {
controller.release();
session.release();
player.release();
});
}
private ControllerInfo getTestControllerInfo() {
List<ControllerInfo> controllers = session.getConnectedControllers();
assertThat(controllers).isNotNull();