Introduce SessionPlaylistAgent

Bug: 74090741
Test: SessionPlaylistAgentTest
Change-Id: I7fdff75e9f42e3d38f4bb08ca904706b25ecc884
gugelfrei
Sungsoo Lim 6 years ago
parent fd0ccd539c
commit 337272811a

@ -40,6 +40,9 @@ LOCAL_SRC_FILES := \
LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
# TODO: Enable proguard (b/74090741)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_MULTILIB := first
LOCAL_JAVA_LIBRARIES += android-support-annotations

@ -0,0 +1,433 @@
/*
* 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.DataSourceDesc;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.OnDataSourceMissingHelper;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
public class SessionPlaylistAgent extends MediaPlaylistAgent {
private static final String TAG = "SessionPlaylistAgent";
@VisibleForTesting
static final int END_OF_PLAYLIST = -1;
@VisibleForTesting
static final int NO_VALID_ITEMS = -2;
private final PlayItem mEopPlayItem = new PlayItem(END_OF_PLAYLIST, null);
private final Object mLock = new Object();
private final MediaSession2 mSession;
// TODO: Set data sources properly into mPlayer (b/74090741)
@GuardedBy("mLock")
private MediaPlayerBase mPlayer;
@GuardedBy("mLock")
private OnDataSourceMissingHelper mDsdHelper;
// TODO: Check if having the same item is okay (b/74090741)
@GuardedBy("mLock")
private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
@GuardedBy("mLock")
private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
@GuardedBy("mLock")
private Map<MediaItem2, DataSourceDesc> mItemDsdMap = new ArrayMap<>();
@GuardedBy("mLock")
private MediaMetadata2 mMetadata;
@GuardedBy("mLock")
private int mRepeatMode;
@GuardedBy("mLock")
private int mShuffleMode;
@GuardedBy("mLock")
private PlayItem mCurrent;
private class PlayItem {
int shuffledIdx;
DataSourceDesc dsd;
MediaItem2 mediaItem;
PlayItem(int shuffledIdx) {
this(shuffledIdx, null);
}
PlayItem(int shuffledIdx, DataSourceDesc dsd) {
this.shuffledIdx = shuffledIdx;
if (shuffledIdx >= 0) {
this.mediaItem = mShuffledList.get(shuffledIdx);
if (dsd == null) {
synchronized (mLock) {
this.dsd = retrieveDataSourceDescLocked(this.mediaItem);
}
} else {
this.dsd = dsd;
}
}
}
boolean isValid() {
if (this == mEopPlayItem) {
return true;
}
if (mediaItem == null) {
return false;
}
if (dsd == null) {
return false;
}
if (shuffledIdx >= mShuffledList.size()) {
return false;
}
if (mediaItem != mShuffledList.get(shuffledIdx)) {
return false;
}
if (mediaItem.getDataSourceDesc() != null
&& !mediaItem.getDataSourceDesc().equals(dsd)) {
return false;
}
return true;
}
}
public SessionPlaylistAgent(@NonNull Context context, @NonNull MediaSession2 session,
@NonNull MediaPlayerBase player) {
super(context);
if (session == null) {
throw new IllegalArgumentException("session shouldn't be null");
}
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
mSession = session;
mPlayer = player;
}
public void setPlayer(MediaPlayerBase player) {
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
synchronized (mLock) {
mPlayer = player;
}
}
public void setOnDataSourceMissingHelper(OnDataSourceMissingHelper helper) {
synchronized (mLock) {
mDsdHelper = helper;
}
}
@Override
public @Nullable List<MediaItem2> getPlaylist() {
synchronized (mLock) {
return Collections.unmodifiableList(mPlaylist);
}
}
@Override
public void setPlaylist(@NonNull List<MediaItem2> list,
@Nullable MediaMetadata2 metadata) {
if (list == null) {
throw new IllegalArgumentException("list shouldn't be null");
}
synchronized (mLock) {
mItemDsdMap.clear();
mPlaylist.clear();
mPlaylist.addAll(list);
applyShuffleModeLocked();
mMetadata = metadata;
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
}
notifyPlaylistChanged();
}
@Override
public @Nullable MediaMetadata2 getPlaylistMetadata() {
return mMetadata;
}
@Override
public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
synchronized (mLock) {
if (metadata == mMetadata) {
return;
}
mMetadata = metadata;
}
notifyPlaylistMetadataChanged();
}
@Override
public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
index = clamp(index, mPlaylist.size());
int shuffledIdx = index;
mPlaylist.add(index, item);
if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
mShuffledList.add(index, item);
} else {
// Add the item in random position of mShuffledList.
shuffledIdx = ThreadLocalRandom.current().nextInt(mShuffledList.size() + 1);
mShuffledList.add(shuffledIdx, item);
}
if (!hasValidItem()) {
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
} else {
updateCurrentIfNeededLocked();
}
}
notifyPlaylistChanged();
}
@Override
public void removePlaylistItem(@NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (!mPlaylist.remove(item)) {
return;
}
mShuffledList.remove(item);
mItemDsdMap.remove(item);
updateCurrentIfNeededLocked();
}
notifyPlaylistChanged();
}
@Override
public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (mPlaylist.size() <= 0) {
return;
}
index = clamp(index, mPlaylist.size() - 1);
int shuffledIdx = mShuffledList.indexOf(mPlaylist.get(index));
mItemDsdMap.remove(mShuffledList.get(shuffledIdx));
mShuffledList.set(shuffledIdx, item);
mPlaylist.set(index, item);
if (!hasValidItem()) {
mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
} else {
updateCurrentIfNeededLocked();
}
}
notifyPlaylistChanged();
}
@Override
public void skipToPlaylistItem(@NonNull MediaItem2 item) {
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
synchronized (mLock) {
if (!hasValidItem() || item.equals(mCurrent.mediaItem)) {
return;
}
int shuffledIdx = mShuffledList.indexOf(item);
if (shuffledIdx < 0) {
return;
}
mCurrent = new PlayItem(shuffledIdx);
updateCurrentIfNeededLocked();
}
}
@Override
public void skipToPreviousItem() {
synchronized (mLock) {
if (!hasValidItem()) {
return;
}
PlayItem prev = getNextValidPlayItemLocked(mCurrent.shuffledIdx, -1);
if (prev != mEopPlayItem) {
mCurrent = prev;
}
updateCurrentIfNeededLocked();
}
}
@Override
public void skipToNextItem() {
synchronized (mLock) {
if (!hasValidItem()) {
return;
}
PlayItem next = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
if (next != mEopPlayItem) {
mCurrent = next;
}
updateCurrentIfNeededLocked();
}
}
@Override
public int getRepeatMode() {
return mRepeatMode;
}
@Override
public void setRepeatMode(int repeatMode) {
if (repeatMode < MediaPlaylistAgent.REPEAT_MODE_NONE
|| repeatMode > MediaPlaylistAgent.REPEAT_MODE_GROUP) {
return;
}
synchronized (mLock) {
if (mRepeatMode == repeatMode) {
return;
}
mRepeatMode = repeatMode;
}
notifyRepeatModeChanged();
}
@Override
public int getShuffleMode() {
return mShuffleMode;
}
@Override
public void setShuffleMode(int shuffleMode) {
if (shuffleMode < MediaPlaylistAgent.SHUFFLE_MODE_NONE
|| shuffleMode > MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
return;
}
synchronized (mLock) {
if (mShuffleMode == shuffleMode) {
return;
}
mShuffleMode = shuffleMode;
applyShuffleModeLocked();
}
notifyShuffleModeChanged();
}
@VisibleForTesting
int getCurShuffledIndex() {
return hasValidItem() ? mCurrent.shuffledIdx : NO_VALID_ITEMS;
}
private boolean hasValidItem() {
return mCurrent != null;
}
private DataSourceDesc retrieveDataSourceDescLocked(MediaItem2 item) {
DataSourceDesc dsd = item.getDataSourceDesc();
if (dsd != null) {
mItemDsdMap.put(item, dsd);
return dsd;
}
dsd = mItemDsdMap.get(item);
if (dsd != null) {
return dsd;
}
OnDataSourceMissingHelper helper = mDsdHelper;
if (helper != null) {
// TODO: Do not call onDataSourceMissing with the lock (b/74090741).
dsd = helper.onDataSourceMissing(mSession, item);
if (dsd != null) {
mItemDsdMap.put(item, dsd);
}
}
return dsd;
}
private PlayItem getNextValidPlayItemLocked(int curShuffledIdx, int direction) {
int size = mPlaylist.size();
if (curShuffledIdx == END_OF_PLAYLIST) {
curShuffledIdx = (direction > 0) ? -1 : size;
}
for (int i = 0; i < size; i++) {
curShuffledIdx += direction;
if (curShuffledIdx < 0 || curShuffledIdx >= mPlaylist.size()) {
if (mRepeatMode == REPEAT_MODE_NONE) {
return (i == size - 1) ? null : mEopPlayItem;
} else {
curShuffledIdx = curShuffledIdx < 0 ? mPlaylist.size() - 1 : 0;
}
}
DataSourceDesc dsd = retrieveDataSourceDescLocked(mShuffledList.get(curShuffledIdx));
if (dsd != null) {
return new PlayItem(curShuffledIdx, dsd);
}
}
return null;
}
private void updateCurrentIfNeededLocked() {
if (!hasValidItem() || mCurrent.isValid()) {
return;
}
int shuffledIdx = mShuffledList.indexOf(mCurrent.mediaItem);
if (shuffledIdx >= 0) {
// Added an item.
mCurrent.shuffledIdx = shuffledIdx;
return;
}
if (mCurrent.shuffledIdx >= mShuffledList.size()) {
mCurrent = getNextValidPlayItemLocked(mShuffledList.size() - 1, 1);
} else {
mCurrent.mediaItem = mShuffledList.get(mCurrent.shuffledIdx);
if (retrieveDataSourceDescLocked(mCurrent.mediaItem) == null) {
mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
}
}
return;
}
private void applyShuffleModeLocked() {
mShuffledList.clear();
mShuffledList.addAll(mPlaylist);
if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_ALL
|| mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
Collections.shuffle(mShuffledList);
}
}
// Clamps value to [0, size]
private static int clamp(int value, int size) {
if (value < 0) {
return 0;
}
return (value > size) ? size : value;
}
}

@ -0,0 +1,35 @@
# 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
android.test.runner.stubs \
android.test.base.stubs \
junit
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := MediaComponentsTest
LOCAL_INSTRUMENTATION_FOR := MediaComponents
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.media.tests">
<application android:label="Media API Test">
<uses-library android:name="android.test.runner" />
</application>
<!--
To run the tests use the command:
"adb shell am instrument -w com.android.media.tests/android.test.InstrumentationTestRunner"
-->
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.media.update"
android:label="Media API test" />
</manifest>

@ -0,0 +1,515 @@
/*
* 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 android.content.Context;
import android.media.DataSourceDesc;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaPlayer2;
import android.media.MediaPlayerBase;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.test.AndroidTestCase;
import android.util.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
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 MediaSession2 mSession;
private MediaPlayerBase mPlayer;
private SessionPlaylistAgent mAgent;
private MyDataSourceHelper 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 MediaSession2.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();
}
}
@Before
public void setUp() throws Exception {
HandlerThread handlerThread = new HandlerThread("SessionPlaylistAgent");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mHandlerExecutor = (runnable) -> {
mHandler.post(runnable);
};
mContext = getContext();
mPlayer = MediaPlayer2.create();
mSession = new MediaSession2.Builder(mContext)
.setPlayer(mPlayer)
.setSessionCallback(mHandlerExecutor,
new MediaSession2.SessionCallback(mContext) {})
.setId(TAG).build();
mDataSourceHelper = new MyDataSourceHelper();
mAgent = new SessionPlaylistAgent(mContext, mSession, mPlayer);
mAgent.setOnDataSourceMissingHelper(mDataSourceHelper);
mEventCallback = new MyPlaylistEventCallback(mWaitLock);
mAgent.registerPlaylistEventCallback(mHandlerExecutor, mEventCallback);
}
@After
public void tearDown() throws Exception {
mSession.close();
mPlayer.close();
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());
}
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(mContext, 0)
.setMediaId("TEST_MEDIA_ID_WITHOUT_DSD_" + key)
.build();
}
private MediaItem2 generateMediaItem(int key) {
return new MediaItem2.Builder(mContext, 0)
.setMediaId("TEST_MEDIA_ID_" + key)
.build();
}
}
Loading…
Cancel
Save