blob: beb0848dea5574229b9ff6236779b7837cc89ad3 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.media;
import static org.mockito.Mockito.*;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.DataSourceDesc;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
import android.media.MediaPlayerBase.PlayerEventCallback;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.OnDataSourceMissingHelper;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.test.AndroidTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Tests {@link SessionPlaylistAgent}.
*/
public class SessionPlaylistAgentTest extends AndroidTestCase {
private static final String TAG = "SessionPlaylistAgentTest";
private static final int WAIT_TIME_MS = 1000;
private static final int INVALID_REPEAT_MODE = -100;
private static final int INVALID_SHUFFLE_MODE = -100;
private Handler mHandler;
private Executor mHandlerExecutor;
private Object mWaitLock = new Object();
private Context mContext;
private MediaSession2Impl mSessionImpl;
private MediaPlayerBase mPlayer;
private PlayerEventCallback mPlayerEventCallback;
private SessionPlaylistAgent mAgent;
private OnDataSourceMissingHelper mDataSourceHelper;
private MyPlaylistEventCallback mEventCallback;
public class MyPlaylistEventCallback extends MediaPlaylistAgent.PlaylistEventCallback {
boolean onPlaylistChangedCalled;
boolean onPlaylistMetadataChangedCalled;
boolean onRepeatModeChangedCalled;
boolean onShuffleModeChangedCalled;
private Object mWaitLock;
public MyPlaylistEventCallback(Object waitLock) {
mWaitLock = waitLock;
}
public void clear() {
onPlaylistChangedCalled = false;
onPlaylistMetadataChangedCalled = false;
onRepeatModeChangedCalled = false;
onShuffleModeChangedCalled = false;
}
public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
MediaMetadata2 metadata) {
synchronized (mWaitLock) {
onPlaylistChangedCalled = true;
mWaitLock.notify();
}
}
public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
MediaMetadata2 metadata) {
synchronized (mWaitLock) {
onPlaylistMetadataChangedCalled = true;
mWaitLock.notify();
}
}
public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
synchronized (mWaitLock) {
onRepeatModeChangedCalled = true;
mWaitLock.notify();
}
}
public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
synchronized (mWaitLock) {
onShuffleModeChangedCalled = true;
mWaitLock.notify();
}
}
}
public class MyDataSourceHelper implements OnDataSourceMissingHelper {
@Override
public DataSourceDesc onDataSourceMissing(MediaSession2 session, MediaItem2 item) {
if (item.getMediaId().contains("WITHOUT_DSD")) {
return null;
}
return new DataSourceDesc.Builder()
.setDataSource(getContext(), Uri.parse("dsd://test"))
.setMediaId(item.getMediaId())
.build();
}
}
public class MockPlayer extends MediaPlayerBase {
@Override
public void play() {
}
@Override
public void prepare() {
}
@Override
public void pause() {
}
@Override
public void reset() {
}
@Override
public void skipToNext() {
}
@Override
public void seekTo(long pos) {
}
@Override
public int getPlayerState() {
return 0;
}
@Override
public int getBufferingState() {
return 0;
}
@Override
public void setAudioAttributes(AudioAttributes attributes) {
}
@Override
public AudioAttributes getAudioAttributes() {
return null;
}
@Override
public void setDataSource(DataSourceDesc dsd) {
}
@Override
public void setNextDataSource(DataSourceDesc dsd) {
}
@Override
public void setNextDataSources(List<DataSourceDesc> dsds) {
}
@Override
public DataSourceDesc getCurrentDataSource() {
return null;
}
@Override
public void loopCurrent(boolean loop) {
}
@Override
public void setPlaybackSpeed(float speed) {
}
@Override
public void setPlayerVolume(float volume) {
}
@Override
public float getPlayerVolume() {
return 0;
}
@Override
public void registerPlayerEventCallback(Executor e, PlayerEventCallback cb) {
}
@Override
public void unregisterPlayerEventCallback(PlayerEventCallback cb) {
}
@Override
public void close() throws Exception {
}
}
@Before
public void setUp() throws Exception {
mContext = getContext();
// Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
// Dexmaker is used by mockito.
System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
HandlerThread handlerThread = new HandlerThread("SessionPlaylistAgent");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mHandlerExecutor = (runnable) -> {
mHandler.post(runnable);
};
mPlayer = mock(MockPlayer.class);
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
mPlayerEventCallback = (PlayerEventCallback) args[1];
return null;
}).when(mPlayer).registerPlayerEventCallback(Matchers.any(), Matchers.any());
mSessionImpl = mock(MediaSession2Impl.class);
mDataSourceHelper = new MyDataSourceHelper();
mAgent = new SessionPlaylistAgent(mSessionImpl, mPlayer);
mAgent.setOnDataSourceMissingHelper(mDataSourceHelper);
mEventCallback = new MyPlaylistEventCallback(mWaitLock);
mAgent.registerPlaylistEventCallback(mHandlerExecutor, mEventCallback);
}
@After
public void tearDown() throws Exception {
mHandler.getLooper().quitSafely();
mHandler = null;
mHandlerExecutor = null;
}
@Test
public void testSetAndGetShuflleMode() throws Exception {
int shuffleMode = mAgent.getShuffleMode();
if (shuffleMode != MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_NONE);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onShuffleModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_NONE, mAgent.getShuffleMode());
}
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_ALL);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onShuffleModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_ALL, mAgent.getShuffleMode());
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_GROUP);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onShuffleModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
// INVALID_SHUFFLE_MODE will not change the shuffle mode.
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setShuffleMode(INVALID_SHUFFLE_MODE);
mWaitLock.wait(WAIT_TIME_MS);
assertFalse(mEventCallback.onShuffleModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
}
@Test
public void testSetAndGetRepeatMode() throws Exception {
int repeatMode = mAgent.getRepeatMode();
if (repeatMode != MediaPlaylistAgent.REPEAT_MODE_NONE) {
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onRepeatModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.REPEAT_MODE_NONE, mAgent.getRepeatMode());
}
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onRepeatModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.REPEAT_MODE_ONE, mAgent.getRepeatMode());
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onRepeatModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.REPEAT_MODE_ALL, mAgent.getRepeatMode());
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_GROUP);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onRepeatModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
// INVALID_SHUFFLE_MODE will not change the shuffle mode.
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setRepeatMode(INVALID_REPEAT_MODE);
mWaitLock.wait(WAIT_TIME_MS);
assertFalse(mEventCallback.onRepeatModeChangedCalled);
}
assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
}
@Test
public void testSetPlaylist() throws Exception {
int listSize = 10;
createAndSetPlaylist(10);
assertEquals(listSize, mAgent.getPlaylist().size());
assertEquals(0, mAgent.getCurShuffledIndex());
}
@Test
public void testSkipItems() throws Exception {
int listSize = 5;
List<MediaItem2> playlist = createAndSetPlaylist(listSize);
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
// Test skipToPlaylistItem
for (int i = listSize - 1; i >= 0; --i) {
mAgent.skipToPlaylistItem(playlist.get(i));
assertEquals(i, mAgent.getCurShuffledIndex());
}
// Test skipToNextItem
// curPlayPos = 0
for (int curPlayPos = 0; curPlayPos < listSize - 1; ++curPlayPos) {
mAgent.skipToNextItem();
assertEquals(curPlayPos + 1, mAgent.getCurShuffledIndex());
}
mAgent.skipToNextItem();
assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
// Test skipToPrevious
// curPlayPos = listSize - 1
for (int curPlayPos = listSize - 1; curPlayPos > 0; --curPlayPos) {
mAgent.skipToPreviousItem();
assertEquals(curPlayPos - 1, mAgent.getCurShuffledIndex());
}
mAgent.skipToPreviousItem();
assertEquals(0, mAgent.getCurShuffledIndex());
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
// Test skipToPrevious with repeat mode all
// curPlayPos = 0
mAgent.skipToPreviousItem();
assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
// Test skipToNext with repeat mode all
// curPlayPos = listSize - 1
mAgent.skipToNextItem();
assertEquals(0, mAgent.getCurShuffledIndex());
mAgent.skipToPreviousItem();
// curPlayPos = listSize - 1, nextPlayPos = 0
// Test next play pos after setting repeat mode none.
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
}
@Test
public void testEditPlaylist() throws Exception {
int listSize = 5;
List<MediaItem2> playlist = createAndSetPlaylist(listSize);
// Test add item: [0 (cur), 1, 2, 3, 4] -> [0 (cur), 1, 5, 2, 3, 4]
mEventCallback.clear();
MediaItem2 item_5 = generateMediaItem(5);
synchronized (mWaitLock) {
playlist.add(2, item_5);
mAgent.addPlaylistItem(2, item_5);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
mEventCallback.clear();
// Move current: [0 (cur), 1, 5, 2, 3, 4] -> [0, 1, 5 (cur), 2, 3, 4]
mAgent.skipToPlaylistItem(item_5);
// Remove current item: [0, 1, 5 (cur), 2, 3, 4] -> [0, 1, 2 (cur), 3, 4]
synchronized (mWaitLock) {
playlist.remove(item_5);
mAgent.removePlaylistItem(item_5);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(2, mAgent.getCurShuffledIndex());
// Remove previous item: [0, 1, 2 (cur), 3, 4] -> [0, 2 (cur), 3, 4]
mEventCallback.clear();
MediaItem2 previousItem = playlist.get(1);
synchronized (mWaitLock) {
playlist.remove(previousItem);
mAgent.removePlaylistItem(previousItem);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Remove next item: [0, 2 (cur), 3, 4] -> [0, 2 (cur), 4]
mEventCallback.clear();
MediaItem2 nextItem = playlist.get(2);
synchronized (mWaitLock) {
playlist.remove(nextItem);
mAgent.removePlaylistItem(nextItem);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Replace item: [0, 2 (cur), 4] -> [0, 2 (cur), 5]
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.set(2, item_5);
mAgent.replacePlaylistItem(2, item_5);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Move last and remove the last item: [0, 2 (cur), 5] -> [0, 2, 5 (cur)] -> [0, 2 (cur)]
MediaItem2 lastItem = playlist.get(1);
mAgent.skipToPlaylistItem(lastItem);
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.remove(lastItem);
mAgent.removePlaylistItem(lastItem);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Remove all items
for (int i = playlist.size() - 1; i >= 0; --i) {
MediaItem2 item = playlist.get(i);
mAgent.skipToPlaylistItem(item);
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.remove(item);
mAgent.removePlaylistItem(item);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
}
assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
}
@Test
public void testPlaylistWithInvalidItem() throws Exception {
int listSize = 2;
List<MediaItem2> playlist = createAndSetPlaylist(listSize);
// Add item: [0 (cur), 1] -> [0 (cur), 3 (no_dsd), 1]
mEventCallback.clear();
MediaItem2 invalidItem2 = generateMediaItemWithoutDataSourceDesc(2);
synchronized (mWaitLock) {
playlist.add(1, invalidItem2);
mAgent.addPlaylistItem(1, invalidItem2);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(0, mAgent.getCurShuffledIndex());
// Test skip to next item: [0 (cur), 2 (no_dsd), 1] -> [0, 2 (no_dsd), 1 (cur)]
mAgent.skipToNextItem();
assertEquals(2, mAgent.getCurShuffledIndex());
// Test skip to previous item: [0, 2 (no_dsd), 1 (cur)] -> [0 (cur), 2 (no_dsd), 1]
mAgent.skipToPreviousItem();
assertEquals(0, mAgent.getCurShuffledIndex());
// Remove current item: [0 (cur), 2 (no_dsd), 1] -> [2 (no_dsd), 1 (cur)]
mEventCallback.clear();
MediaItem2 item = playlist.get(0);
synchronized (mWaitLock) {
playlist.remove(item);
mAgent.removePlaylistItem(item);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Remove current item: [2 (no_dsd), 1 (cur)] -> [2 (no_dsd)]
mEventCallback.clear();
item = playlist.get(1);
synchronized (mWaitLock) {
playlist.remove(item);
mAgent.removePlaylistItem(item);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
// Add invalid item: [2 (no_dsd)] -> [0 (no_dsd), 2 (no_dsd)]
MediaItem2 invalidItem0 = generateMediaItemWithoutDataSourceDesc(0);
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.add(0, invalidItem0);
mAgent.addPlaylistItem(0, invalidItem0);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
// Add valid item: [0 (no_dsd), 2 (no_dsd)] -> [0 (no_dsd), 1, 2 (no_dsd)]
MediaItem2 invalidItem1 = generateMediaItem(1);
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.add(1, invalidItem1);
mAgent.addPlaylistItem(1, invalidItem1);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(1, mAgent.getCurShuffledIndex());
// Replace the valid item with an invalid item:
// [0 (no_dsd), 1 (cur), 2 (no_dsd)] -> [0 (no_dsd), 3 (no_dsd), 2 (no_dsd)]
MediaItem2 invalidItem3 = generateMediaItemWithoutDataSourceDesc(3);
mEventCallback.clear();
synchronized (mWaitLock) {
playlist.set(1, invalidItem3);
mAgent.replacePlaylistItem(1, invalidItem3);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
assertPlaylistEquals(playlist, mAgent.getPlaylist());
assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
}
@Test
public void testPlaylistAfterOnCurrentDataSourceChanged() throws Exception {
int listSize = 2;
verify(mPlayer).registerPlayerEventCallback(Matchers.any(), Matchers.any());
createAndSetPlaylist(listSize);
assertEquals(0, mAgent.getCurShuffledIndex());
mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
assertEquals(1, mAgent.getCurShuffledIndex());
mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
mAgent.skipToNextItem();
assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
assertEquals(0, mAgent.getCurShuffledIndex());
mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
assertEquals(1, mAgent.getCurShuffledIndex());
mPlayerEventCallback.onCurrentDataSourceChanged(mPlayer, null);
assertEquals(0, mAgent.getCurShuffledIndex());
}
private List<MediaItem2> createAndSetPlaylist(int listSize) throws Exception {
List<MediaItem2> items = new ArrayList<>();
for (int i = 0; i < listSize; ++i) {
items.add(generateMediaItem(i));
}
mEventCallback.clear();
synchronized (mWaitLock) {
mAgent.setPlaylist(items, null);
mWaitLock.wait(WAIT_TIME_MS);
assertTrue(mEventCallback.onPlaylistChangedCalled);
}
return items;
}
private void assertPlaylistEquals(List<MediaItem2> expected, List<MediaItem2> actual) {
if (expected == actual) {
return;
}
assertTrue(expected != null && actual != null);
assertEquals(expected.size(), actual.size());
for (int i = 0; i < expected.size(); ++i) {
assertTrue(expected.get(i).equals(actual.get(i)));
}
}
private MediaItem2 generateMediaItemWithoutDataSourceDesc(int key) {
return new MediaItem2.Builder(0)
.setMediaId("TEST_MEDIA_ID_WITHOUT_DSD_" + key)
.build();
}
private MediaItem2 generateMediaItem(int key) {
return new MediaItem2.Builder(0)
.setMediaId("TEST_MEDIA_ID_" + key)
.build();
}
}