Bug: 74090741 Test: SessionPlaylistAgentTest Change-Id: I7fdff75e9f42e3d38f4bb08ca904706b25ecc884gugelfrei
parent
fd0ccd539c
commit
337272811a
@ -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…
Reference in new issue