Unnest session vct directories

Tested:
  $ ./gradlew :media-test-session-current:cAT
  $ blaze test test_session_current/src/androidTest:test_with_current_support_app
  The tests run but seem flaky, not related to this change.
PiperOrigin-RevId: 373677924
This commit is contained in:
gyumin 2021-05-14 00:07:23 +01:00 committed by Oliver Woodman
parent 6d3e9fc251
commit bd4ba4c583
85 changed files with 21325 additions and 0 deletions

View File

@ -0,0 +1,21 @@
// Copyright 2019 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.
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies {
api 'com.google.truth:truth:' + truthVersion
implementation project(modulePrefix + 'library-common')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.test:core:' + androidxTestCoreVersion
}

View File

@ -0,0 +1,18 @@
<?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 package="com.google.android.exoplayer2.session.vct.common">
<uses-sdk />
</manifest>

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (String controllerId, 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.google.android.exoplayer2.session.vct.common;
import android.content.ComponentName;
interface IRemoteMediaBrowserCompat {
void create(String browserId, in ComponentName componentName);
// MediaBrowserCompat Methods
void connect(String browserId, boolean waitForConnection);
void disconnect(String browserId);
boolean isConnected(String browserId);
ComponentName getServiceComponent(String browserId);
String getRoot(String browserId);
Bundle getExtras(String browserId);
Bundle getConnectedSessionToken(String browserId);
void subscribe(String browserId, String parentId, in Bundle options);
void unsubscribe(String browserId, String parentId);
void getItem(String browserId, String mediaId);
void search(String browserId, String query, in Bundle extras);
void sendCustomAction(String browserId, String action, in Bundle extras);
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (String controllerId, 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.google.android.exoplayer2.session.vct.common;
interface IRemoteMediaBrowserServiceCompat {
void setProxyForTest(String testName);
void notifyChildrenChanged(String parentId);
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (String controllerId, 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.google.android.exoplayer2.session.vct.common;
import android.net.Uri;
import android.os.ResultReceiver;
interface IRemoteMediaController {
void create(
boolean isBrowser,
String controllerId,
in Bundle token,
in Bundle connectionHints,
boolean waitForConnection);
// MediaController Methods
Bundle getConnectedSessionToken(String controllerId);
void play(String controllerId);
void pause(String controllerId);
void setPlayWhenReady(String controllerId, boolean playWhenReady);
void prepare(String controllerId);
void seekToDefaultPosition(String controllerId);
void seekToDefaultPositionWithWindowIndex(String controllerId, int windowIndex);
void seekTo(String controllerId, long positionMs);
void seekToWithWindowIndex(String controllerId, int windowIndex, long positionMs);
void setPlaybackParameters(String controllerId, in Bundle playbackParametersBundle);
void setPlaybackSpeed(String controllerId, float speed);
void setMediaItems1(String controllerId, in List<Bundle> mediaItems, boolean resetPosition);
void setMediaItems2(
String controllerId, in List<Bundle> mediaItems, int startWindowIndex, long startPositionMs);
void createAndSetFakeMediaItems(String controllerId, int size);
void setMediaUri(String controllerId, in Uri uri, in Bundle extras);
void setPlaylistMetadata(String controllerId, in Bundle playlistMetadata);
void addMediaItems(String controllerId, int index, in List<Bundle> mediaItems);
void removeMediaItems(String controllerId, int fromIndex, int toIndex);
void moveMediaItems(String controllerId, int fromIndex, int toIndex, int newIndex);
void previous(String controllerId);
void next(String controllerId);
void setShuffleModeEnabled(String controllerId, boolean shuffleModeEnabled);
void setRepeatMode(String controllerId, int repeatMode);
void setVolumeTo(String controllerId, int value, int flags);
void adjustVolume(String controllerId, int direction, int flags);
void setVolume(String controllerId, float volume);
void setDeviceVolume(String controllerId, int volume);
void increaseDeviceVolume(String controllerId);
void decreaseDeviceVolume(String controllerId);
void setDeviceMuted(String controllerId, boolean muted);
Bundle sendCustomCommand(String controllerId, in Bundle command, in Bundle args);
Bundle setRating(String controllerId, String mediaId, in Bundle rating);
void release(String controllerId);
void stop(String controllerId);
// MediaBrowser methods
Bundle getLibraryRoot(String controllerId, in Bundle libraryParams);
Bundle subscribe(String controllerId, String parentId, in Bundle libraryParams);
Bundle unsubscribe(String controllerId, String parentId);
Bundle getChildren(
String controllerId,
String parentId,
int page,
int pageSize,
in Bundle libraryParams);
Bundle getItem(String controllerId, String mediaId);
Bundle search(String controllerId, String query, in Bundle libraryParams);
Bundle getSearchResult(
String controllerId, String query, int page, int pageSize, in Bundle libraryParams);
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (String controllerId, 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.google.android.exoplayer2.session.vct.common;
import android.net.Uri;
import android.os.ResultReceiver;
interface IRemoteMediaControllerCompat {
void create(String controllerId, in Bundle token, boolean waitForConnection);
// MediaControllerCompat Methods
void addQueueItem(String controllerId, in Bundle description);
void addQueueItemWithIndex(String controllerId, in Bundle description, int index);
void removeQueueItem(String controllerId, in Bundle description);
void setVolumeTo(String controllerId, int value, int flags);
void adjustVolume(String controllerId, int direction, int flags);
void sendCommand(String controllerId, String command, in Bundle params, in ResultReceiver cb);
// TransportControl methods
void prepare(String controllerId);
void prepareFromMediaId(String controllerId, String mediaId, in Bundle extras);
void prepareFromSearch(String controllerId, String query, in Bundle extras);
void prepareFromUri(String controllerId, in Uri uri, in Bundle extras);
void play(String controllerId);
void playFromMediaId(String controllerId, String mediaId, in Bundle extras);
void playFromSearch(String controllerId, String query, in Bundle extras);
void playFromUri(String controllerId, in Uri uri, in Bundle extras);
void skipToQueueItem(String controllerId, long id);
void pause(String controllerId);
void stop(String controllerId);
void seekTo(String controllerId, long pos);
void setPlaybackSpeed(String controllerId, float speed);
void skipToNext(String controllerId);
void skipToPrevious(String controllerId);
void setRating(String controllerId, in Bundle rating);
void setRatingWithExtras(String controllerId, in Bundle rating, in Bundle extras);
void setCaptioningEnabled(String controllerId, boolean enabled);
void setRepeatMode(String controllerId, int repeatMode);
void setShuffleMode(String controllerId, int shuffleMode);
void sendCustomAction(String controllerId, in Bundle customAction, in Bundle args);
void sendCustomActionWithName(String controllerId, String action, in Bundle args);
}

View File

@ -0,0 +1,79 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import android.os.Bundle;
import android.os.ResultReceiver;
interface IRemoteMediaSession {
void create(String sessionId, in Bundle tokenExtras);
// MediaSession Methods
Bundle getToken(String sessionId);
Bundle getCompatToken(String sessionId);
void setSessionPositionUpdateDelayMs(String sessionId, long updateDelayMs);
void setPlayer(String sessionId, in Bundle playerBundle);
void broadcastCustomCommand(String sessionId, in Bundle command, in Bundle args);
void sendCustomCommand(
String sessionId, in Bundle controller, in Bundle command, in Bundle args);
void release(String sessionId);
void setAvailableCommands(String sessionId, in Bundle controller, in Bundle sessionCommands, in Bundle playerCommands);
void setCustomLayout(String sessionId, in Bundle controller, in List<Bundle> layout);
// Player Methods
void setPlayWhenReady(String sessionId, boolean playWhenReady, int reason);
void setPlaybackState(String sessionId, int state);
void setCurrentPosition(String sessionId, long pos);
void setBufferedPosition(String sessionId, long pos);
void setDuration(String sessionId, long duration);
void setBufferedPercentage(String sessionId, int bufferedPercentage);
void setTotalBufferedDuration(String sessionId, long totalBufferedDuration);
void setCurrentLiveOffset(String sessionId, long currentLiveOffset);
void setContentDuration(String sessionId, long contentDuration);
void setContentPosition(String sessionId, long contentPosition);
void setContentBufferedPosition(String sessionId, long contentBufferedPosition);
void setPlaybackParameters(String sessionId, in Bundle playbackParametersBundle);
void setIsPlayingAd(String sessionId, boolean isPlayingAd);
void setCurrentAdGroupIndex(String sessionId, int currentAdGroupIndex);
void setCurrentAdIndexInAdGroup(String sessionId, int currentAdIndexInAdGroup);
void notifyPlayerError(String sessionId, in Bundle playerErrorBundle);
void notifyPlayWhenReadyChanged(String sessionId, boolean playWhenReady, int reason);
void notifyPlaybackStateChanged(String sessionId, int state);
void notifyIsPlayingChanged(String sessionId, boolean isPlaying);
void notifyIsLoadingChanged(String sessionId, boolean isLoading);
void notifyPositionDiscontinuity(String sessionId,
in Bundle oldPositionBundle, in Bundle newPositionBundle, int reason);
void notifyPlaybackParametersChanged(String sessionId, in Bundle playbackParametersBundle);
void notifyMediaItemTransition(String sessionId, int index, int reason);
void notifyAudioAttributesChanged(String sessionId, in Bundle audioAttributes);
void notifyVideoSizeChanged(String sessionId, in Bundle videoSize);
void notifyAvailableCommandsChanged(String sessionId, in Bundle commandsBundle);
boolean surfaceExists(String sessionId);
void setTimeline(String sessionId, in Bundle timeline);
void createAndSetFakeTimeline(String sessionId, int windowCount);
void setPlaylistMetadata(String sessionId, in Bundle metadata);
void setShuffleModeEnabled(String sessionId, boolean shuffleMode);
void setRepeatMode(String sessionId, int repeatMode);
void setCurrentWindowIndex(String sessionId, int index);
void notifyTimelineChanged(String sessionId, int reason);
void notifyPlaylistMetadataChanged(String sessionId);
void notifyShuffleModeEnabledChanged(String sessionId);
void notifyRepeatModeChanged(String sessionId);
void notifyDeviceVolumeChanged(String sessionId, int volume, boolean muted);
void notifyDeviceInfoChanged(String sessionId, in Bundle deviceInfo);
}

View File

@ -0,0 +1,44 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import android.app.PendingIntent;
import android.os.Bundle;
import android.os.ResultReceiver;
// Here, we use Bundle instead of the *Compat class (which implement parcelable).
// This is to avoid making dependency of testlib module on media library.
interface IRemoteMediaSessionCompat {
void create(String sessionTag);
// MediaSessionCompat Methods
Bundle getSessionToken(String sessionTag);
void release(String sessionTag);
void setPlaybackToLocal(String sessionTag, int stream);
void setPlaybackToRemote(String sessionTag, int volumeControl, int maxVolume, int currentVolume);
void setPlaybackState(String sessionTag, in Bundle stateBundle);
void setMetadata(String sessionTag, in Bundle metadataBundle);
void setQueue(String sessionTag, in Bundle queueBundle);
void setQueueTitle(String sessionTag, in CharSequence title);
void setRepeatMode(String sessionTag, int repeatMode);
void setShuffleMode(String sessionTag, int shuffleMode);
void setSessionActivity(String sessionTag, in PendingIntent pi);
void setFlags(String sessionTag, int flags);
void setRatingType(String sessionTag, int type);
void sendSessionEvent(String sessionTag, String event, in Bundle extras);
void setCaptioningEnabled(String sessionTag, boolean enabled);
}

View File

@ -0,0 +1,117 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import android.content.ComponentName;
/** Common constants for testing purpose. */
public class CommonConstants {
public static final String SUPPORT_APP_PACKAGE_NAME = "com.google.android.exoplayer2.session.vct";
public static final ComponentName MEDIA2_SESSION_PROVIDER_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MediaSessionProviderService");
public static final ComponentName MEDIA2_CONTROLLER_PROVIDER_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MediaControllerProviderService");
public static final ComponentName MEDIA_SESSION_COMPAT_PROVIDER_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MediaSessionCompatProviderService");
public static final ComponentName MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MediaControllerCompatProviderService");
public static final ComponentName MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MediaBrowserCompatProviderService");
public static final ComponentName MOCK_MEDIA2_SESSION_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MockMediaSessionService");
public static final ComponentName MOCK_MEDIA2_LIBRARY_SERVICE =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MockMediaLibraryService");
public static final ComponentName MOCK_MEDIA_BROWSER_SERVICE_COMPAT =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME,
"com.google.android.exoplayer2.session.MockMediaBrowserServiceCompat");
public static final String ACTION_MEDIA2_SESSION =
"com.google.android.exoplayer2.session.vct.action.MEDIA2_SESSION";
public static final String ACTION_MEDIA2_CONTROLLER =
"com.google.android.exoplayer2.session.vct.action.MEDIA2_CONTROLLER";
public static final String ACTION_MEDIA_SESSION_COMPAT =
"com.google.android.exoplayer2.session.vct.action.MEDIA_SESSION_COMPAT";
public static final String ACTION_MEDIA_CONTROLLER_COMPAT =
"com.google.android.exoplayer2.session.vct.action.MEDIA_CONTROLLER_COMPAT";
public static final String ACTION_MEDIA_BROWSER_COMPAT =
"com.google.android.exoplayer2.session.vct.action.MEDIA_BROWSER_COMPAT";
// Keys for arguments.
public static final String KEY_PLAYER_ERROR = "playerError";
public static final String KEY_AUDIO_ATTRIBUTES = "audioAttributes";
public static final String KEY_TIMELINE = "timeline";
public static final String KEY_CURRENT_WINDOW_INDEX = "currentWindowIndex";
public static final String KEY_CURRENT_PERIOD_INDEX = "currentPeriodIndex";
public static final String KEY_DURATION = "duration";
public static final String KEY_CURRENT_POSITION = "currentPosition";
public static final String KEY_BUFFERED_POSITION = "bufferedPosition";
public static final String KEY_BUFFERED_PERCENTAGE = "bufferedPercentage";
public static final String KEY_TOTAL_BUFFERED_DURATION = "totalBufferedDuration";
public static final String KEY_CURRENT_LIVE_OFFSET = "currentLiveOffset";
public static final String KEY_CONTENT_DURATION = "contentDuration";
public static final String KEY_CONTENT_POSITION = "contentPosition";
public static final String KEY_CONTENT_BUFFERED_POSITION = "contentBufferedPosition";
public static final String KEY_PLAYBACK_PARAMETERS = "playbackParameters";
public static final String KEY_MEDIA_ITEM = "mediaItem";
public static final String KEY_PLAYLIST_METADATA = "playlistMetadata";
public static final String KEY_ARGUMENTS = "arguments";
public static final String KEY_DEVICE_INFO = "deviceInfo";
public static final String KEY_DEVICE_VOLUME = "deviceVolume";
public static final String KEY_DEVICE_MUTED = "deviceMuted";
public static final String KEY_VIDEO_SIZE = "videoSize";
public static final String KEY_VOLUME = "volume";
public static final String KEY_PLAY_WHEN_READY = "playWhenReady";
public static final String KEY_PLAYBACK_SUPPRESSION_REASON = "playbackSuppressionReason";
public static final String KEY_PLAYBACK_STATE = "playbackState";
public static final String KEY_IS_PLAYING = "isPlaying";
public static final String KEY_IS_LOADING = "isLoading";
public static final String KEY_REPEAT_MODE = "repeatMode";
public static final String KEY_SHUFFLE_MODE_ENABLED = "shuffleModeEnabled";
public static final String KEY_IS_PLAYING_AD = "isPlayingAd";
public static final String KEY_CURRENT_AD_GROUP_INDEX = "currentAdGroupIndex";
public static final String KEY_CURRENT_AD_INDEX_IN_AD_GROUP = "currentAdIndexInAdGroup";
// SessionCompat arguments
public static final String KEY_SESSION_COMPAT_TOKEN = "sessionCompatToken";
public static final String KEY_PLAYBACK_STATE_COMPAT = "playbackStateCompat";
public static final String KEY_METADATA_COMPAT = "metadataCompat";
public static final String KEY_QUEUE = "queue";
// Default test name
public static final String DEFAULT_TEST_NAME = "defaultTestName";
private CommonConstants() {}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2019 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.google.android.exoplayer2.session.vct.common;
import android.annotation.SuppressLint;
import android.os.Parcel;
import android.os.Parcelable;
/** Custom Parcelable class to test sending/receiving user parcelables between processes. */
@SuppressLint("BanParcelableUsage")
public class CustomParcelable implements Parcelable {
private int value;
public CustomParcelable(int value) {
this.value = value;
}
@Override
public int describeContents() {
return 0;
}
@SuppressLint("UnknownNullness") // Parcel dest
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(value);
}
public static final Parcelable.Creator<CustomParcelable> CREATOR =
new Parcelable.Creator<CustomParcelable>() {
@Override
public CustomParcelable createFromParcel(Parcel in) {
int value = in.readInt();
return new CustomParcelable(value);
}
@Override
public CustomParcelable[] newArray(int size) {
return new CustomParcelable[size];
}
};
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session.vct.common;
import android.os.Build;
import android.os.HandlerThread;
import java.util.concurrent.Executor;
import org.junit.rules.ExternalResource;
/** TestRule for providing a handler and an executor for {@link HandlerThread}. */
public final class HandlerThreadTestRule extends ExternalResource {
private final String threadName;
private TestHandler handler;
private Executor executor;
public HandlerThreadTestRule(String threadName) {
this.threadName = threadName;
}
@Override
protected void before() {
HandlerThread handlerThread = new HandlerThread(threadName);
handlerThread.start();
TestHandler handler = new TestHandler(handlerThread.getLooper());
executor = handler::post;
this.handler = handler;
}
@Override
protected void after() {
try {
if (Build.VERSION.SDK_INT >= 18) {
handler.getLooper().quitSafely();
} else {
handler.getLooper().quit();
}
} finally {
handler = null;
executor = null;
}
}
/** Gets the handler for the thread. */
public TestHandler getHandler() {
if (handler == null) {
throw new IllegalStateException("It should be called between before() and after()");
}
return handler;
}
/** Gets the executor that executes the commands on the thread. */
public Executor getExecutor() {
if (executor == null) {
throw new IllegalStateException("It should be called between before() and after()");
}
return executor;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session.vct.common;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/** TestRule for preparing main looper. */
public final class MainLooperTestRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
prepare();
base.evaluate();
}
};
}
private void prepare() {
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
// Prepare the main looper if it hasn't.
// Some framework APIs always run on the main looper.
if (Looper.getMainLooper() == null) {
Looper.prepareMainLooper();
}
// Initialize AudioManager on the main thread to workaround b/78617702 that
// audio focus listener is called on the thread where the AudioManager was
// originally initialized.
// Without posting this, audio focus listeners wouldn't be called because the
// listeners would be posted to the test thread (here) where it waits until the
// tests are finished.
Context context = ApplicationProvider.getApplicationContext();
AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
});
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
/** Constants for calling MediaBrowser methods. */
public class MediaBrowserConstants {
public static final String ROOT_ID = "rootId";
public static final Bundle ROOT_EXTRAS = new Bundle();
public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
public static final String MEDIA_ID_GET_NULL_ITEM = "media_id_get_null_item";
public static final String PARENT_ID = "parent_id";
public static final String PARENT_ID_LONG_LIST = "parent_id_long_list";
public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
public static final String PARENT_ID_ERROR = "parent_id_error";
public static final List<String> GET_CHILDREN_RESULT = new ArrayList<>();
public static final int CHILDREN_COUNT = 100;
public static final int LONG_LIST_COUNT = 5000;
public static final String SEARCH_QUERY = "search_query";
public static final String SEARCH_QUERY_LONG_LIST = "search_query_long_list";
public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
public static final long SEARCH_TIME_IN_MS = 5_000;
public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
public static final String SEARCH_QUERY_ERROR = "search_query_error";
public static final List<String> SEARCH_RESULT = new ArrayList<>();
public static final int SEARCH_RESULT_COUNT = 50;
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL =
"subscribe_id_notify_children_changed_to_all";
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE =
"subscribe_id_notify_children_changed_to_one";
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID =
"subscribe_id_notify_children_changed_to_all_with_non_subscribed_id";
public static final String SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID =
"subscribe_id_notify_children_changed_to_one_with_non_subscribed_id";
public static final int NOTIFY_CHILDREN_CHANGED_ITEM_COUNT = 101;
public static final Bundle NOTIFY_CHILDREN_CHANGED_EXTRAS = TestUtils.createTestBundle();
public static final String CUSTOM_ACTION = "customAction";
public static final Bundle CUSTOM_ACTION_EXTRAS = new Bundle();
public static final String CUSTOM_ACTION_ASSERT_PARAMS = "assertParams";
static {
ROOT_EXTRAS.putString(ROOT_ID, ROOT_ID);
CUSTOM_ACTION_EXTRAS.putString(CUSTOM_ACTION, CUSTOM_ACTION);
GET_CHILDREN_RESULT.clear();
String getChildrenMediaIdPrefix = "get_children_media_id_";
for (int i = 0; i < CHILDREN_COUNT; i++) {
GET_CHILDREN_RESULT.add(getChildrenMediaIdPrefix + i);
}
SEARCH_RESULT.clear();
String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
SEARCH_RESULT.add(getSearchResultMediaIdPrefix + i);
}
}
private MediaBrowserConstants() {}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session.vct.common;
/** Constants for calling MediaBrowserServiceCompat methods. */
public class MediaBrowserServiceCompatConstants {
public static final String TEST_CONNECT_REJECTED = "testConnect_rejected";
public static final String TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE =
"testOnChildrenChanged_subscribeAndUnsubscribe";
private MediaBrowserServiceCompatConstants() {}
}

View File

@ -0,0 +1,28 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
/** Constants for calling MediaSession methods. */
public class MediaSessionConstants {
// Test method names
public static final String TEST_GET_SESSION_ACTIVITY = "testGetSessionActivity";
public static final String TEST_CONTROLLER_CALLBACK_SESSION_REJECTS =
"testControllerCallback_sessionRejects";
private MediaSessionConstants() {}
}

View File

@ -0,0 +1,22 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import android.app.Activity;
/** An empty activity used for testing. */
public class MockActivity extends Activity {}

View File

@ -0,0 +1,81 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import static com.google.common.truth.Truth.assertWithMessage;
import androidx.annotation.NonNull;
/**
* Utility used for testing that allows to poll for a certain condition to happen within a timeout.
*/
// It's forked from androidx.testutils.PollingCheck.
public abstract class PollingCheck {
private static final long TIME_SLICE_MS = 50;
private final long timeoutMs;
/** The condition that the PollingCheck should use to proceed successfully. */
public interface PollingCheckCondition {
/** @return Whether the polling condition has been met. */
boolean canProceed() throws Exception;
}
private PollingCheck(long timeoutMs) {
this.timeoutMs = timeoutMs;
}
protected abstract boolean check() throws Exception;
/** Start running the polling check. */
public void run() throws Exception {
if (check()) {
return;
}
long timeoutMs = this.timeoutMs;
while (timeoutMs > 0) {
try {
Thread.sleep(TIME_SLICE_MS);
} catch (InterruptedException e) {
throw new AssertionError("unexpected InterruptedException");
}
if (check()) {
return;
}
timeoutMs -= TIME_SLICE_MS;
}
assertWithMessage("unexpected timeout").fail();
}
/**
* Instantiate and start polling for a given condition.
*
* @param timeoutMs Timeout in milliseconds.
* @param condition The condition to check for success.
*/
public static void waitFor(long timeoutMs, @NonNull PollingCheckCondition condition)
throws Exception {
new PollingCheck(timeoutMs) {
@Override
protected boolean check() throws Exception {
return condition.canProceed();
}
}.run();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2019 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.google.android.exoplayer2.session.vct.common;
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/** An activity used for surface test */
public class SurfaceActivity extends Activity {
private SurfaceHolder firstSurfaceHolder;
private SurfaceHolder secondSurfaceHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TestUtils.setKeepScreenOn(this);
setContentView(R.layout.activity_surface);
SurfaceView firstSurfaceView = findViewById(R.id.surface_view_first);
firstSurfaceHolder = firstSurfaceView.getHolder();
SurfaceView secondSurfaceView = findViewById(R.id.surface_view_second);
secondSurfaceHolder = secondSurfaceView.getHolder();
}
public SurfaceHolder getFirstSurfaceHolder() {
return firstSurfaceHolder;
}
public SurfaceHolder getSecondSurfaceHolder() {
return secondSurfaceHolder;
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session.vct.common;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
/** Handler for testing. */
public class TestHandler extends Handler {
private static final long DEFAULT_TIMEOUT_MS = LONG_TIMEOUT_MS;
public TestHandler(@NonNull Looper looper) {
super(looper);
}
/** Posts {@link Runnable} and waits until it finishes, or runs it directly on the same looper. */
public void postAndSync(@NonNull TestRunnable runnable) throws Exception {
postAndSync(runnable, DEFAULT_TIMEOUT_MS);
}
/** Posts {@link Runnable} and waits until it finishes, or runs it directly on the same looper. */
public void postAndSync(@NonNull TestRunnable runnable, long timeoutMs) throws Exception {
if (getLooper() == Looper.myLooper()) {
runnable.run();
} else {
AtomicReference<Exception> exception = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
post(
() -> {
try {
runnable.run();
} catch (Exception e) {
exception.set(e);
}
latch.countDown();
});
assertThat(latch.await(timeoutMs, MILLISECONDS)).isTrue();
if (exception.get() != null) {
throw exception.get();
}
}
}
/**
* Posts {@link Callable} and returns the result when it finishes, or calls it directly on the
* same looper.
*/
public <V> V postAndSync(@NonNull Callable<V> callable) throws Exception {
return postAndSync(callable, DEFAULT_TIMEOUT_MS);
}
/**
* Posts {@link Callable} and returns the result when it finishes, or calls it directly on the
* same looper.
*/
public <V> V postAndSync(@NonNull Callable<V> callable, long timeoutMs) throws Exception {
if (getLooper() == Looper.myLooper()) {
return callable.call();
} else {
AtomicReference<V> result = new AtomicReference<>();
AtomicReference<Exception> exception = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
post(
() -> {
try {
result.set(callable.call());
} catch (Exception e) {
exception.set(e);
}
latch.countDown();
});
assertThat(latch.await(timeoutMs, MILLISECONDS)).isTrue();
if (exception.get() != null) {
throw exception.get();
}
return result.get();
}
}
/** {@link Runnable} variant which can throw a checked exception. */
public interface TestRunnable {
void run() throws Exception;
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.google.android.exoplayer2.session.vct.common;
import static android.content.Context.KEYGUARD_SERVICE;
import android.app.Activity;
import android.app.KeyguardManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Util;
import java.util.Locale;
/** Provides utility methods for testing purpose. */
public class TestUtils {
public static final long TIMEOUT_MS = 5_000;
public static final long NO_RESPONSE_TIMEOUT_MS = 500;
public static final long SERVICE_CONNECTION_TIMEOUT_MS = 3_000;
public static final long VOLUME_CHANGE_TIMEOUT_MS = 5_000;
public static final long LONG_TIMEOUT_MS = 10_000;
/**
* Compares contents of two throwables for both message and class.
*
* @param a a throwable
* @param b another throwable
* @return {@code true} if two throwables are the same class and same messages. {@code false}
* otherwise.
*/
public static boolean equals(@Nullable Throwable a, @Nullable Throwable b) {
if (a == null || b == null) {
return a == b;
}
return a.getClass() == b.getClass() && TextUtils.equals(a.getMessage(), b.getMessage());
}
/**
* Compares contents of two bundles.
*
* @param a a bundle
* @param b another bundle
* @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
* incorrect if any bundle contains a bundle.
*/
public static boolean equals(Bundle a, Bundle b) {
return contains(a, b) && contains(b, a);
}
/**
* Checks whether a Bundle contains another bundle.
*
* @param a a bundle
* @param b another bundle
* @return {@code true} if a contains b. {@code false} otherwise. This may be incorrect if any
* bundle contains a bundle.
*/
public static boolean contains(Bundle a, Bundle b) {
if (a == b) {
return true;
}
if (a == null || b == null) {
return b == null;
}
if (!a.keySet().containsAll(b.keySet())) {
return false;
}
for (String key : b.keySet()) {
if (!Util.areEqual(a.get(key), b.get(key))) {
return false;
}
}
return true;
}
/**
* Create a bundle for testing purpose.
*
* @return the newly created bundle.
*/
public static Bundle createTestBundle() {
Bundle bundle = new Bundle();
bundle.putString("test_key", "test_value");
return bundle;
}
/** Gets the expected mediaId for the windowIndex when testing with a fake timeline. */
public static String getMediaIdInFakeTimeline(int windowIndex) {
return String.format(Locale.US, "%08d", windowIndex);
}
@UiThread
static void setKeepScreenOn(Activity activity) {
if (Build.VERSION.SDK_INT >= 27) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
activity.setTurnScreenOn(true);
activity.setShowWhenLocked(true);
KeyguardManager keyguardManager =
(KeyguardManager) activity.getSystemService(KEYGUARD_SERVICE);
keyguardManager.requestDismissKeyguard(activity, null);
} else {
activity
.getWindow()
.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
private TestUtils() {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<SurfaceView
android:id="@+id/surface_view_first"
android:layout_width="match_parent"
android:layout_height="match_parent">
</SurfaceView>
<SurfaceView
android:id="@+id/surface_view_second"
android:layout_width="match_parent"
android:layout_height="match_parent">
</SurfaceView>
</LinearLayout>

View File

@ -0,0 +1,45 @@
// Copyright 2021 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.
apply from: "$gradle.ext.exoplayerSettingsDir/constants.gradle"
apply plugin: 'com.android.application'
android {
compileSdkVersion project.ext.compileSdkVersion
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
// TODO(b/178560255): Remove explicit "group" after moving to androidx package.
group 'androidx.media3'
dependencies {
implementation project(modulePrefix + 'library-session')
implementation project(modulePrefix + 'test-session-common')
androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
androidTestImplementation 'androidx.test.ext:truth:' + androidxTestTruthVersion
androidTestImplementation 'androidx.test:core:' + androidxTestCoreVersion
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 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.google.android.exoplayer2.session.vct.test">
<uses-sdk android:minSdkVersion="16"/>
</manifest>

View File

@ -0,0 +1,447 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.LONG_LIST_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.MediaBrowser.BrowserCallback;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
import com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for {@link MediaBrowser.BrowserCallback}.
*
* <p>This test inherits {@link MediaControllerCallbackTest} to ensure that inherited APIs from
* {@link MediaController} works cleanly.
*/
// TODO: (internal cleanup) Move tests that aren't related with callbacks.
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserCallbackTest extends MediaControllerCallbackTest {
private static final String TAG = "MediaBrowserCallbackTest";
@Before
public void setControllerType() {
controllerTestRule.setControllerType(MediaBrowser.class);
}
private MediaBrowser createBrowser() throws Exception {
return createBrowser(null, null);
}
private MediaBrowser createBrowser(
@Nullable Bundle connectionHints, @Nullable BrowserCallback callback) throws Exception {
SessionToken token = new SessionToken(context, MOCK_MEDIA2_LIBRARY_SERVICE);
return (MediaBrowser)
controllerTestRule.createController(token, true, connectionHints, callback);
}
@Test
public void getLibraryRoot() throws Exception {
LibraryParams params =
new LibraryParams.Builder()
.setOffline(true)
.setRecent(true)
.setExtras(new Bundle())
.build();
MediaBrowser browser = createBrowser();
setExpectedLibraryParam(browser, params);
LibraryResult result = browser.getLibraryRoot(params).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(result.item.mediaId).isEqualTo(ROOT_ID);
assertThat(TestUtils.equals(ROOT_EXTRAS, result.params.extras)).isTrue();
}
@Test
public void getItem() throws Exception {
String mediaId = MediaBrowserConstants.MEDIA_ID_GET_ITEM;
LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(result.item.mediaId).isEqualTo(mediaId);
}
@Test
public void getItem_unknownId() throws Exception {
String mediaId = "random_media_id";
LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_ERROR_BAD_VALUE);
assertThat(result.item).isNull();
}
@Test
public void getItem_nullResult() throws Exception {
String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
// Exception will be thrown in the service side, and the process will be crashed.
// In that case one of following will happen
// Case 1) Process is crashed. Pending ListenableFuture will get error
// Case 2) Due to the frequent crashes with other tests, process may not crash immediately
// because the Android shows dialog 'xxx keeps stopping' and defer sending
// SIG_KILL until the user's explicit action.
try {
LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
// Case 1.
assertThat(result.resultCode).isNotEqualTo(RESULT_SUCCESS);
} catch (TimeoutException e) {
// Case 2.
}
// Clean up RemoteMediaSession proactively to avoid crash at cleanUp()
try {
remoteSession.cleanUp();
} catch (RemoteException e) {
// Expected
}
remoteSession = null;
}
@Test
public void getChildren() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID;
int page = 4;
int pageSize = 10;
LibraryParams params = MediaTestUtils.createLibraryParams();
MediaBrowser browser = createBrowser();
setExpectedLibraryParam(browser, params);
LibraryResult result =
browser.getChildren(parentId, page, pageSize, params).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(result.params).isNull();
MediaTestUtils.assertPaginatedListHasIds(
result.items, MediaBrowserConstants.GET_CHILDREN_RESULT, page, pageSize);
}
@Test
@LargeTest
public void getChildren_withLongList() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID_LONG_LIST;
int page = 0;
int pageSize = Integer.MAX_VALUE;
LibraryParams params = MediaTestUtils.createLibraryParams();
MediaBrowser browser = createBrowser();
setExpectedLibraryParam(browser, params);
LibraryResult result =
browser.getChildren(parentId, page, pageSize, params).get(LONG_TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(result.params).isNull();
assertThat(result.items).hasSize(LONG_LIST_COUNT);
for (int i = 0; i < result.items.size(); i++) {
assertThat(result.items.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
}
@Test
public void getChildren_emptyResult() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
MediaBrowser browser = createBrowser();
LibraryResult result = browser.getChildren(parentId, 1, 1, null).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(result.items.size()).isEqualTo(0);
}
@Test
public void getChildren_nullResult() throws Exception {
String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
MediaBrowser browser = createBrowser();
LibraryResult result = browser.getChildren(parentId, 1, 1, null).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isNotEqualTo(RESULT_SUCCESS);
assertThat(result.items).isNull();
}
@Test
public void searchCallbacks() throws Exception {
String query = MediaBrowserConstants.SEARCH_QUERY;
int page = 4;
int pageSize = 10;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latchForSearch = new CountDownLatch(1);
BrowserCallback callback =
new BrowserCallback() {
@Override
public void onSearchResultChanged(
MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
assertThat(queryOut).isEqualTo(query);
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
assertThat(itemCount).isEqualTo(MediaBrowserConstants.SEARCH_RESULT_COUNT);
latchForSearch.countDown();
}
};
// Request the search.
MediaBrowser browser = createBrowser(null, callback);
setExpectedLibraryParam(browser, testParams);
LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
// Get the search result.
result =
browser.getSearchResult(query, page, pageSize, testParams).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
MediaTestUtils.assertPaginatedListHasIds(
result.items, MediaBrowserConstants.SEARCH_RESULT, page, pageSize);
}
@Test
@LargeTest
public void searchCallbacks_withLongList() throws Exception {
String query = MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
int page = 0;
int pageSize = Integer.MAX_VALUE;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback callback =
new BrowserCallback() {
@Override
public void onSearchResultChanged(
MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
assertThat(queryOut).isEqualTo(query);
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
assertThat(itemCount).isEqualTo(MediaBrowserConstants.LONG_LIST_COUNT);
latch.countDown();
}
};
MediaBrowser browser = createBrowser(null, callback);
setExpectedLibraryParam(browser, testParams);
LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
result =
browser
.getSearchResult(query, page, pageSize, testParams)
.get(LONG_TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
for (int i = 0; i < result.items.size(); i++) {
assertThat(result.items.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
}
@Test
@LargeTest
public void onSearchResultChanged_searchTakesTime() throws Exception {
String query = MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback callback =
new BrowserCallback() {
@Override
public void onSearchResultChanged(
MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
assertThat(queryOut).isEqualTo(query);
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
assertThat(itemCount).isEqualTo(MediaBrowserConstants.SEARCH_RESULT_COUNT);
latch.countDown();
}
};
MediaBrowser browser = createBrowser(null, callback);
setExpectedLibraryParam(browser, testParams);
LibraryResult result =
browser
.search(query, testParams)
.get(MediaBrowserConstants.SEARCH_TIME_IN_MS + TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
}
@Test
public void onSearchResultChanged_emptyResult() throws Exception {
String query = MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback callback =
new BrowserCallback() {
@Override
public void onSearchResultChanged(
MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
assertThat(queryOut).isEqualTo(query);
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
assertThat(itemCount).isEqualTo(0);
latch.countDown();
}
};
MediaBrowser browser = createBrowser(null, callback);
setExpectedLibraryParam(browser, testParams);
LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
}
@Test
public void onChildrenChanged_calledWhenSubscribed() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged().
String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback controllerCallbackProxy =
new BrowserCallback() {
@Override
public void onChildrenChanged(
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
assertThat(parentId).isEqualTo(expectedParentId);
assertThat(itemCount).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
latch.countDown();
}
};
LibraryResult result =
createBrowser(null, controllerCallbackProxy)
.subscribe(expectedParentId, null)
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
// notifyChildrenChanged() in its callback onSubscribe().
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback controllerCallbackProxy =
new BrowserCallback() {
@Override
public void onChildrenChanged(
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
assertThat(parentId).isEqualTo(expectedParentId);
assertThat(itemCount).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
latch.countDown();
}
};
LibraryResult result =
createBrowser(null, controllerCallbackProxy)
.subscribe(expectedParentId, null)
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
// notifyChildrenChanged(ControllerInfo) in its callback onSubscribe().
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged().
String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback controllerCallbackProxy =
new BrowserCallback() {
@Override
public void onChildrenChanged(
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
latch.countDown();
}
};
LibraryResult result =
createBrowser(null, controllerCallbackProxy)
.subscribe(subscribedMediaId, null)
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
// notifyChildrenChanged() in its callback onSubscribe(), but with a different media ID.
// Therefore, onChildrenChanged() should not be called.
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
@Test
public void onChildrenChanged_notCalledWhenNotSubscribed2() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
CountDownLatch latch = new CountDownLatch(1);
BrowserCallback controllerCallbackProxy =
new BrowserCallback() {
@Override
public void onChildrenChanged(
MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
latch.countDown();
}
};
LibraryResult result =
createBrowser(null, controllerCallbackProxy)
.subscribe(subscribedMediaId, null)
.get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
// The MediaLibrarySession in MockMediaLibraryService is supposed to call
// notifyChildrenChanged(ControllerInfo) in its callback onSubscribe(),
// but with a different media ID.
// Therefore, onChildrenChanged() should not be called.
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
private void setExpectedLibraryParam(MediaBrowser browser, LibraryParams params)
throws Exception {
SessionCommand command = new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null);
Bundle args = new Bundle();
args.putBundle(CUSTOM_ACTION_ASSERT_PARAMS, params.toBundle());
SessionResult result = browser.sendCustomCommand(command, args).get(TIMEOUT_MS, MILLISECONDS);
assertThat(result.resultCode).isEqualTo(SessionResult.RESULT_SUCCESS);
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA_BROWSER_SERVICE_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.MediaBrowser.BrowserCallback;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/** Tests for {@link BrowserCallback} with {@link MediaBrowserServiceCompat}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserCallbackWithMediaBrowserServiceCompatTest {
private final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaBrowserCallbackTestWithMediaBrowserServiceCompat");
private final MediaControllerTestRule controllerTestRule =
new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
private Context context;
private RemoteMediaBrowserServiceCompat remoteService;
private MediaBrowser createBrowser(boolean waitForConnect, @Nullable BrowserCallback callback)
throws Exception {
SessionToken token = new SessionToken(context, MOCK_MEDIA_BROWSER_SERVICE_COMPAT);
return (MediaBrowser)
controllerTestRule.createController(
token, waitForConnect, /* connectionHints= */ null, callback);
}
@Before
public void setUp() {
controllerTestRule.setControllerType(MediaBrowser.class);
context = ApplicationProvider.getApplicationContext();
remoteService = new RemoteMediaBrowserServiceCompat(context);
}
@After
public void cleanUp() throws Exception {
remoteService.release();
}
@Test
public void connect() throws Exception {
createBrowser(/* waitForConnect= */ true, new BrowserCallback() {});
// If connection failed, exception will be thrown inside of #createBrowser().
}
@Test
public void connect_rejected() throws Exception {
remoteService.setProxyForTest(TEST_CONNECT_REJECTED);
CountDownLatch latch = new CountDownLatch(1);
createBrowser(
/* waitForConnect= */ false,
new BrowserCallback() {
@Override
public void onConnected(MediaController controller) {
assertWithMessage("shouldn't allow connection").fail();
}
@Override
public void onDisconnected(@NonNull MediaController controller) {
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onChildrenChanged_subscribeAndUnsubscribe() throws Exception {
String testParentId = "testOnChildrenChanged";
CountDownLatch latch = new CountDownLatch(2);
BrowserCallback browserCallback =
new BrowserCallback() {
@Override
public void onChildrenChanged(
@NonNull MediaBrowser browser,
@NonNull String parentId,
int itemCount,
@Nullable LibraryParams params) {
// Triggered by both subscribe and notifyChildrenChanged().
// Shouldn't be called after the unsubscribe().
assertThat(latch.getCount()).isNotEqualTo(0);
assertThat(parentId).isEqualTo(testParentId);
assertThat(params).isNull();
latch.countDown();
}
};
remoteService.setProxyForTest(TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE);
MediaBrowser browser = createBrowser(/* waitForConnect= */ true, browserCallback);
LibraryResult resultForSubscribe =
browser.subscribe(testParentId, null).get(TIMEOUT_MS, MILLISECONDS);
assertThat(resultForSubscribe.resultCode).isEqualTo(RESULT_SUCCESS);
remoteService.notifyChildrenChanged(testParentId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
LibraryResult resultForUnsubscribe =
browser.unsubscribe(testParentId).get(TIMEOUT_MS, MILLISECONDS);
assertThat(resultForUnsubscribe.resultCode).isEqualTo(RESULT_SUCCESS);
// Unsubscribe takes some time. Wait for some time.
Thread.sleep(TIMEOUT_MS);
remoteService.notifyChildrenChanged(testParentId);
// This shouldn't trigger browser's onChildrenChanged().
// Wait for some time. Exception will be thrown in the callback if error happens.
Thread.sleep(TIMEOUT_MS);
}
}

View File

@ -0,0 +1,463 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CHILDREN_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.LONG_LIST_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_ERROR;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_ERROR;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserCompat.CustomActionCallback;
import android.support.v4.media.MediaBrowserCompat.ItemCallback;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.MediaBrowserCompat.SearchCallback;
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaBrowserCompat} with {@link MediaLibraryService}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserCompatWithMediaLibraryServiceTest
extends MediaBrowserCompatWithMediaSessionServiceTest {
@Override
ComponentName getServiceComponent() {
return MOCK_MEDIA2_LIBRARY_SERVICE;
}
@Test
public void getRoot() throws Exception {
// The MockMediaLibraryService gives MediaBrowserConstants.ROOT_ID as root ID, and
// MediaBrowserConstants.ROOT_EXTRAS as extras.
handler.postAndSync(
() -> {
browserCompat =
new MediaBrowserCompat(
context, getServiceComponent(), connectionCallback, /* rootHint= */ null);
});
connectAndWait();
assertThat(browserCompat.getRoot()).isEqualTo(ROOT_ID);
// Note: Cannot use equals() here because browser compat's extra contains server version,
// extra binder, and extra messenger.
assertThat(TestUtils.contains(browserCompat.getExtras(), ROOT_EXTRAS)).isTrue();
}
@Test
public void getItem() throws InterruptedException {
String mediaId = MEDIA_ID_GET_ITEM;
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.getItem(
mediaId,
new ItemCallback() {
@Override
public void onItemLoaded(MediaItem item) {
assertThat(item.getMediaId()).isEqualTo(mediaId);
assertThat(item).isNotNull();
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getItem_nullResult() throws InterruptedException {
String mediaId = "random_media_id";
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.getItem(
mediaId,
new ItemCallback() {
@Override
public void onItemLoaded(MediaItem item) {
assertThat(item).isNull();
latch.countDown();
}
@Override
public void onError(@NonNull String itemId) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getChildren() throws InterruptedException {
String testParentId = PARENT_ID;
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.subscribe(
testParentId,
new SubscriptionCallback() {
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(children).isNotNull();
assertThat(children.size()).isEqualTo(GET_CHILDREN_RESULT.size());
// Compare the given results with originals.
for (int i = 0; i < children.size(); i++) {
assertThat(children.get(i).getMediaId()).isEqualTo(GET_CHILDREN_RESULT.get(i));
}
latch.countDown();
}
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children, @NonNull Bundle option) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getChildren_withLongList() throws InterruptedException {
String testParentId = PARENT_ID_LONG_LIST;
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.subscribe(
testParentId,
new SubscriptionCallback() {
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(children).isNotNull();
assertThat(children.size() < LONG_LIST_COUNT).isTrue();
// Compare the given results with originals.
for (int i = 0; i < children.size(); i++) {
assertThat(children.get(i).getMediaId())
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
latch.countDown();
}
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children, @NonNull Bundle option) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getChildren_withPagination() throws InterruptedException {
String testParentId = PARENT_ID;
int page = 4;
int pageSize = 10;
Bundle extras = new Bundle();
extras.putString(testParentId, testParentId);
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
Bundle option = new Bundle();
option.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
option.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
browserCompat.subscribe(
testParentId,
option,
new SubscriptionCallback() {
@Override
public void onChildrenLoaded(
@NonNull String parentId,
@NonNull List<MediaItem> children,
@NonNull Bundle options) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(option.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(page);
assertThat(option.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(pageSize);
assertThat(children).isNotNull();
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, CHILDREN_COUNT);
// Compare the given results with originals.
for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
int relativeIndex = originalIndex - fromIndex;
assertThat(children.get(relativeIndex).getMediaId())
.isEqualTo(GET_CHILDREN_RESULT.get(originalIndex));
}
latch.countDown();
}
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getChildren_emptyResult() throws InterruptedException {
String testParentId = PARENT_ID_NO_CHILDREN;
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.subscribe(
testParentId,
new SubscriptionCallback() {
@Override
public void onChildrenLoaded(
@NonNull String parentId, @NonNull List<MediaItem> children) {
assertThat(children).isNotNull();
assertThat(children.size()).isEqualTo(0);
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getChildren_nullResult() throws InterruptedException {
String testParentId = PARENT_ID_ERROR;
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.subscribe(
testParentId,
new SubscriptionCallback() {
@Override
public void onError(@NonNull String parentId) {
assertThat(parentId).isEqualTo(testParentId);
latch.countDown();
}
@Override
public void onChildrenLoaded(
@NonNull String parentId,
@NonNull List<MediaItem> children,
@NonNull Bundle options) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void search() throws InterruptedException {
String testQuery = SEARCH_QUERY;
int page = 4;
int pageSize = 10;
Bundle testExtras = new Bundle();
testExtras.putString(testQuery, testQuery);
testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.search(
testQuery,
testExtras,
new SearchCallback() {
@Override
public void onSearchResult(
@NonNull String query, Bundle extras, @NonNull List<MediaItem> items) {
assertThat(query).isEqualTo(testQuery);
assertThat(TestUtils.equals(testExtras, extras)).isTrue();
int expectedSize =
Math.max(Math.min(pageSize, SEARCH_RESULT_COUNT - pageSize * page), 0);
assertThat(items.size()).isEqualTo(expectedSize);
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, SEARCH_RESULT_COUNT);
// Compare the given results with originals.
for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
int relativeIndex = originalIndex - fromIndex;
assertThat(items.get(relativeIndex).getMediaId())
.isEqualTo(SEARCH_RESULT.get(originalIndex));
}
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void search_withLongList() throws InterruptedException {
String testQuery = SEARCH_QUERY_LONG_LIST;
int page = 0;
int pageSize = Integer.MAX_VALUE;
Bundle testExtras = new Bundle();
testExtras.putString(testQuery, testQuery);
testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.search(
testQuery,
testExtras,
new SearchCallback() {
@Override
public void onSearchResult(
@NonNull String query, Bundle extras, @NonNull List<MediaItem> items) {
assertThat(query).isEqualTo(testQuery);
assertThat(TestUtils.equals(testExtras, extras)).isTrue();
assertThat(items).isNotNull();
assertThat(items.size() < LONG_LIST_COUNT).isTrue();
for (int i = 0; i < items.size(); i++) {
assertThat(items.get(i).getMediaId())
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
latch.countDown();
}
});
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void search_emptyResult() throws InterruptedException {
String testQuery = SEARCH_QUERY_EMPTY_RESULT;
Bundle testExtras = new Bundle();
testExtras.putString(testQuery, testQuery);
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.search(
testQuery,
testExtras,
new SearchCallback() {
@Override
public void onSearchResult(
@NonNull String query, Bundle extras, @NonNull List<MediaItem> items) {
assertThat(query).isEqualTo(testQuery);
assertThat(TestUtils.equals(testExtras, extras)).isTrue();
assertThat(items).isNotNull();
assertThat(items.size()).isEqualTo(0);
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void search_error() throws InterruptedException {
String testQuery = SEARCH_QUERY_ERROR;
Bundle testExtras = new Bundle();
testExtras.putString(testQuery, testQuery);
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.search(
testQuery,
testExtras,
new SearchCallback() {
@Override
public void onError(@NonNull String query, Bundle extras) {
assertThat(query).isEqualTo(testQuery);
assertThat(TestUtils.equals(testExtras, extras)).isTrue();
latch.countDown();
}
@Override
public void onSearchResult(
@NonNull String query, Bundle extras, @NonNull List<MediaItem> items) {
assertWithMessage("").fail();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
// TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
@Test
public void customAction() throws InterruptedException {
Bundle testArgs = new Bundle();
testArgs.putString("args_key", "args_value");
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.sendCustomAction(
CUSTOM_ACTION,
testArgs,
new CustomActionCallback() {
@Override
public void onResult(String action, Bundle extras, Bundle resultData) {
assertThat(action).isEqualTo(CUSTOM_ACTION);
assertThat(TestUtils.equals(testArgs, extras)).isTrue();
assertThat(TestUtils.equals(CUSTOM_ACTION_EXTRAS, resultData)).isTrue();
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
// TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
@Test
public void customAction_rejected() throws InterruptedException {
// This action will not be allowed by the library session.
String testAction = "random_custom_action";
connectAndWait();
CountDownLatch latch = new CountDownLatch(1);
browserCompat.sendCustomAction(
testAction,
null,
new CustomActionCallback() {
@Override
public void onResult(String action, Bundle extras, Bundle resultData) {
latch.countDown();
}
});
assertWithMessage("BrowserCompat shouldn't receive custom command")
.that(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS))
.isFalse();
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.session.MediaControllerCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/** Tests for {@link MediaBrowserCompat} with {@link MediaSessionService}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserCompatWithMediaSessionServiceTest {
private final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaBrowserCompatTestWithMediaSessionService");
private final MediaControllerTestRule controllerTestRule =
new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
Context context;
TestHandler handler;
MediaBrowserCompat browserCompat;
TestConnectionCallback connectionCallback;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
handler = threadTestRule.getHandler();
connectionCallback = new TestConnectionCallback();
handler.postAndSync(
() -> {
// Make browser's internal handler to be initialized with test thread.
browserCompat =
new MediaBrowserCompat(context, getServiceComponent(), connectionCallback, null);
});
}
@After
public void cleanUp() {
if (browserCompat != null) {
browserCompat.disconnect();
browserCompat = null;
}
}
ComponentName getServiceComponent() {
return MOCK_MEDIA2_SESSION_SERVICE;
}
void connectAndWait() throws InterruptedException {
browserCompat.connect();
assertThat(connectionCallback.connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS))
.isTrue();
}
@Test
public void connect() throws InterruptedException {
connectAndWait();
assertThat(connectionCallback.failedLatch.getCount()).isNotEqualTo(0);
}
@Ignore
@Test
public void connect_rejected() throws InterruptedException {
// TODO: Connect the browser to the session service whose onConnect() returns null.
assertThat(connectionCallback.failedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(connectionCallback.connectedLatch.getCount()).isNotEqualTo(0);
}
@Test
public void getSessionToken() throws Exception {
connectAndWait();
MediaControllerCompat controller =
new MediaControllerCompat(context, browserCompat.getSessionToken());
assertThat(controller.getPackageName())
.isEqualTo(browserCompat.getServiceComponent().getPackageName());
}
class TestConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
public final CountDownLatch connectedLatch = new CountDownLatch(1);
public final CountDownLatch suspendedLatch = new CountDownLatch(1);
public final CountDownLatch failedLatch = new CountDownLatch(1);
TestConnectionCallback() {
super();
}
@Override
public void onConnected() {
super.onConnected();
connectedLatch.countDown();
}
@Override
public void onConnectionSuspended() {
super.onConnectionSuspended();
suspendedLatch.countDown();
}
@Override
public void onConnectionFailed() {
super.onConnectionFailed();
failedLatch.countDown();
}
}
}

View File

@ -0,0 +1,453 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
import androidx.media.MediaBrowserServiceCompat.Result;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
import com.google.android.exoplayer2.session.MockMediaBrowserServiceCompat.Proxy;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaBrowserServiceCompat} with {@link MediaBrowser}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private Context context;
private SessionToken token;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
token =
new SessionToken(
context, new ComponentName(context, LocalMockMediaBrowserServiceCompat.class));
}
@Test
public void onGetRootCalledByGetLibraryRoot() throws Exception {
String testMediaId = "testOnGetRootCalledByGetLibraryRoot";
Bundle testExtras = new Bundle();
testExtras.putString(testMediaId, testMediaId);
LibraryParams testParams =
new LibraryParams.Builder().setSuggested(true).setExtras(testExtras).build();
Bundle testReturnedExtras = new Bundle(testExtras);
testReturnedExtras.putBoolean(BrowserRoot.EXTRA_OFFLINE, true);
BrowserRoot browserRoot = new BrowserRoot(testMediaId, testReturnedExtras);
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
assertThat(clientPackageName).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
if (rootHints != null && rootHints.keySet().contains(testMediaId)) {
MediaTestUtils.assertLibraryParamsEquals(testParams, rootHints);
// This should happen because getLibraryRoot() is called with testExtras.
latch.countDown();
return browserRoot;
}
// For other random connection requests.
return new BrowserRoot("rootId", null);
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getLibraryRoot(testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
assertThat(result.item.mediaId).isEqualTo(testMediaId);
LibraryParams returnedParams = result.params;
assertThat(returnedParams.recent)
.isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
assertThat(returnedParams.offline)
.isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
assertThat(returnedParams.suggested)
.isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
// Note that TestUtils#equals() cannot be used for this because
// MediaBrowserServiceCompat adds extra_client_version to the rootHints.
assertThat(TestUtils.contains(returnedParams.extras, testExtras)).isTrue();
}
@Test
public void onLoadItemCalledByGetItem() throws Exception {
String testMediaId = "test_media_item";
MediaBrowserCompat.MediaItem testItem = createBrowserMediaItem(testMediaId);
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadItem(String itemId, Result<MediaBrowserCompat.MediaItem> result) {
assertThat(itemId).isEqualTo(testMediaId);
result.sendResult(testItem);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getItem(testMediaId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
assertItemEquals(testItem, result.item);
}
@Test
public void onLoadItemCalledByGetItem_nullResult() throws Exception {
String testMediaId = "test_media_item";
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadItem(String itemId, Result<MediaBrowserCompat.MediaItem> result) {
assertThat(itemId).isEqualTo(testMediaId);
result.sendResult(null);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getItem(testMediaId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onLoadChildrenWithoutOptionsCalledByGetChildrenWithoutOptions() throws Exception {
String testParentId = "test_media_parent";
int testPage = 2;
int testPageSize = 4;
List<MediaBrowserCompat.MediaItem> testFullMediaItemList =
createBrowserMediaItems((testPage + 1) * testPageSize);
List<MediaBrowserCompat.MediaItem> testPaginatedMediaItemList =
testFullMediaItemList.subList(
testPage * testPageSize,
Math.min((testPage + 1) * testPageSize, testFullMediaItemList.size()));
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result) {
assertThat(parentId).isEqualTo(testParentId);
result.sendResult(testFullMediaItemList);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
assertItemsEquals(testPaginatedMediaItemList, result.items);
}
@Test
public void onLoadChildrenWithOptionsCalledByGetChildrenWithoutOptions() throws Exception {
String testParentId = "test_media_parent";
int testPage = 2;
int testPageSize = 4;
List<MediaBrowserCompat.MediaItem> testMediaItemList = createBrowserMediaItems(testPageSize);
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result) {
assertWithMessage("This isn't expected to be called").fail();
}
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
assertThat(options.keySet().size()).isEqualTo(2);
result.sendResult(testMediaItemList);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
assertItemsEquals(testMediaItemList, result.items);
assertThat(result.params).isNull();
}
@Test
public void onLoadChildrenWithOptionsCalledByGetChildrenWithoutOptions_nullResult()
throws Exception {
String testParentId = "test_media_parent";
int testPage = 2;
int testPageSize = 4;
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
result.sendResult(null);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
assertThat(result.params).isNull();
}
@Test
public void onLoadChildrenWithOptionsCalledByGetChildrenWithOptions() throws Exception {
String testParentId = "test_media_parent";
int testPage = 2;
int testPageSize = 4;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
List<MediaBrowserCompat.MediaItem> testMediaItemList =
createBrowserMediaItems(testPageSize / 2);
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result, Bundle options) {
assertThat(parentId).isEqualTo(testParentId);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
assertThat(TestUtils.contains(options, testParams.extras)).isTrue();
result.sendResult(testMediaItemList);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
assertItemsEquals(testMediaItemList, result.items);
assertThat(result.params).isNull();
}
@Test
public void onLoadChildrenCalledBySubscribe() throws Exception {
String testParentId = "testOnLoadChildrenCalledBySubscribe";
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch subscribeLatch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result, Bundle option) {
assertThat(parentId).isEqualTo(testParentId);
MediaTestUtils.assertLibraryParamsEquals(testParams, option);
result.sendResult(Collections.emptyList());
subscribeLatch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.subscribe(testParentId, testParams);
assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onLoadChildrenCalledBySubscribe_failed() throws Exception {
String testParentId = "testOnLoadChildrenCalledBySubscribe_failed";
CountDownLatch subscribeLatch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaBrowserCompat.MediaItem>> result, Bundle option) {
assertThat(parentId).isEqualTo(testParentId);
// Cannot use Result#sendError() for sending error here. The API is specific to
// custom action.
result.sendResult(null);
subscribeLatch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.subscribe(testParentId, null);
assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onSearchCalledBySearch() throws Exception {
String testQuery = "search_query";
int testPage = 2;
int testPageSize = 4;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
List<MediaBrowserCompat.MediaItem> testFullSearchResult =
createBrowserMediaItems((testPage + 1) * testPageSize + 3);
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onSearch(
String query, Bundle extras, Result<List<MediaBrowserCompat.MediaItem>> result) {
assertThat(query).isEqualTo(testQuery);
MediaTestUtils.assertLibraryParamsEquals(testParams, extras);
result.sendResult(testFullSearchResult);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.search(testQuery, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onSearchCalledBySearch_nullResult() throws Exception {
String testQuery = "search_query";
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onSearch(
String query, Bundle extras, Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.search(testQuery, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onSearchCalledByGetSearchResult() throws Exception {
String testQuery = "search_query";
int testPage = 2;
int testPageSize = 4;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onSearch(
String query, Bundle extras, Result<List<MediaBrowserCompat.MediaItem>> result) {
assertThat(query).isEqualTo(testQuery);
MediaTestUtils.assertLibraryParamsEquals(testParams, extras);
assertThat(extras.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
assertThat(extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
result.sendResult(Collections.emptyList());
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
}
@Test
public void onSearchCalledByGetSearchResult_nullResult() throws Exception {
String testQuery = "search_query";
int testPage = 2;
int testPageSize = 4;
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
new Proxy() {
@Override
public void onSearch(
String query, Bundle extras, Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
LibraryResult result = browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
}
private static MediaBrowserCompat.MediaItem createBrowserMediaItem(String mediaId) {
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle("title: " + mediaId)
.build();
return new MediaBrowserCompat.MediaItem(desc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
}
private static List<MediaBrowserCompat.MediaItem> createBrowserMediaItems(int size) {
List<MediaBrowserCompat.MediaItem> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(createBrowserMediaItem("browserItem_" + i));
}
return list;
}
private static void assertItemEquals(
MediaBrowserCompat.MediaItem browserItem, MediaItem commonItem) {
assertThat(commonItem.mediaId).isEqualTo(browserItem.getMediaId());
assertThat(commonItem.mediaMetadata.title).isEqualTo(browserItem.getDescription().getTitle());
}
private static void assertItemsEquals(
List<MediaBrowserCompat.MediaItem> browserItemList, List<MediaItem> commonItemList) {
assertThat(commonItemList.size()).isEqualTo(browserItemList.size());
for (int i = 0; i < browserItemList.size(); i++) {
assertItemEquals(browserItemList.get(i), commonItemList.get(i));
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.google.android.exoplayer2.session;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.runner.RunWith;
/** Tests for {@link MediaBrowser}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserTest extends MediaControllerTest {
@Before
public void setControllerType() {
controllerTestRule.setControllerType(MediaBrowser.class);
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.os.RemoteException;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.util.ExoFlags;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/** Tests for {@link MediaController.ControllerCallback} with {@link MediaSessionCompat}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaControllerCallbackWithMediaSessionCompatTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private static final int EVENT_ON_EVENTS = C.INDEX_UNSET;
private final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaControllerCallbackTest");
private final MediaControllerTestRule controllerTestRule =
new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
private Context context;
private RemoteMediaSessionCompat session;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
session = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
}
@After
public void cleanUp() throws RemoteException {
session.cleanUp();
}
@Test
public void onEvents_whenOnRepeatModeChanges_isCalledAfterOtherCallbacks() throws Exception {
Player.Events testEvents =
new Player.Events(new ExoFlags.Builder().add(EVENT_REPEAT_MODE_CHANGED).build());
CopyOnWriteArrayList<Integer> callbackEventCodes = new CopyOnWriteArrayList<>();
MediaController controller = controllerTestRule.createController(session.getSessionToken());
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<Player.Events> eventsRef = new AtomicReference<>();
SessionPlayer.PlayerCallback callback =
new SessionPlayer.PlayerCallback() {
@Override
public void onRepeatModeChanged(@Player.RepeatMode int repeatMode) {
callbackEventCodes.add(EVENT_REPEAT_MODE_CHANGED);
latch.countDown();
}
@Override
public void onEvents(Player player, Player.Events events) {
callbackEventCodes.add(EVENT_ON_EVENTS);
eventsRef.set(events);
latch.countDown();
}
};
controller.addListener(callback);
session.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_GROUP);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callbackEventCodes).containsExactly(EVENT_REPEAT_MODE_CHANGED, EVENT_ON_EVENTS);
assertThat(eventsRef.get()).isEqualTo(testEvents);
}
}

View File

@ -0,0 +1,947 @@
/*
* 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.google.android.exoplayer2.session;
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID;
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_USER_RATING;
import static com.google.android.exoplayer2.Player.STATE_ENDED;
import static com.google.android.exoplayer2.Player.STATE_READY;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.media.AudioAttributesCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.HeartRating;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.Player.State;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.PollingCheck;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaControllerCompat.Callback} with {@link MediaSession}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaControllerCompatCallbackWithMediaSessionTest {
private static final String TAG = "MCCCallbackTestWithMS2";
private static final float EPSILON = 1e-6f;
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
private Context context;
private TestHandler handler;
private RemoteMediaSession session;
private MediaControllerCompat controllerCompat;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
handler = threadTestRule.getHandler();
session = new RemoteMediaSession(TAG, context, null);
controllerCompat = new MediaControllerCompat(context, session.getCompatToken());
}
@After
public void cleanUp() throws Exception {
session.release();
}
@Test
public void gettersAfterConnected() throws Exception {
@State int testState = STATE_READY;
int testBufferingPosition = 1500;
float testSpeed = 1.5f;
int testItemIndex = 0;
List<MediaItem> testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 3);
testMediaItems.set(
testItemIndex,
new MediaItem.Builder()
.setMediaId(testMediaItems.get(testItemIndex).mediaId)
.setMediaMetadata(
new MediaMetadata.Builder()
.setUserRating(new HeartRating(/* isHeart= */ true))
.build())
.build());
Timeline testTimeline = new PlaylistTimeline(testMediaItems);
String testPlaylistTitle = "testPlaylistTitle";
MediaMetadata testPlaylistMetadata =
new MediaMetadata.Builder().setTitle(testPlaylistTitle).build();
boolean testShuffleModeEnabled = true;
@RepeatMode int testRepeatMode = Player.REPEAT_MODE_ONE;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setPlaybackState(testState)
.setBufferedPosition(testBufferingPosition)
.setPlaybackParameters(new PlaybackParameters(testSpeed))
.setTimeline(testTimeline)
.setPlaylistMetadata(testPlaylistMetadata)
.setCurrentMediaItem(testMediaItems.get(testItemIndex))
.setShuffleModeEnabled(testShuffleModeEnabled)
.setRepeatMode(testRepeatMode)
.build();
session.setPlayer(playerConfig);
MediaControllerCompat controller = new MediaControllerCompat(context, session.getCompatToken());
CountDownLatch latch = new CountDownLatch(1);
controller.registerCallback(
new MediaControllerCompat.Callback() {
@Override
public void onSessionReady() {
latch.countDown();
}
},
handler);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(
MediaUtils.convertToPlaybackState(
controller.getPlaybackState(),
controller.getMetadata(),
/* timeDiffMs= */ C.TIME_UNSET))
.isEqualTo(testState);
assertThat(controller.getPlaybackState().getBufferedPosition())
.isEqualTo(testBufferingPosition);
assertThat(controller.getPlaybackState().getPlaybackSpeed()).isWithin(EPSILON).of(testSpeed);
assertThat(controller.getMetadata().getString(METADATA_KEY_MEDIA_ID))
.isEqualTo(testMediaItems.get(testItemIndex).mediaId);
assertThat(controller.getRatingType()).isEqualTo(RatingCompat.RATING_HEART);
List<QueueItem> queue = controller.getQueue();
assertThat(queue).isNotNull();
assertThat(queue).hasSize(testTimeline.getWindowCount());
for (int i = 0; i < testTimeline.getWindowCount(); i++) {
assertThat(queue.get(i).getDescription().getMediaId())
.isEqualTo(testMediaItems.get(i).mediaId);
}
assertThat(testPlaylistTitle).isEqualTo(controller.getQueueTitle().toString());
assertThat(PlaybackStateCompat.SHUFFLE_MODE_ALL).isEqualTo(controller.getShuffleMode());
assertThat(PlaybackStateCompat.REPEAT_MODE_ONE).isEqualTo(controller.getRepeatMode());
}
@Test
public void getError_withPlayerErrorAfterConnected_returnsError() throws Exception {
ExoPlaybackException testPlayerError = ExoPlaybackException.createForRemote("testremote");
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setPlayerError(testPlayerError).build();
session.setPlayer(playerConfig);
MediaControllerCompat controller = new MediaControllerCompat(context, session.getCompatToken());
CountDownLatch latch = new CountDownLatch(1);
controller.registerCallback(
new MediaControllerCompat.Callback() {
@Override
public void onSessionReady() {
latch.countDown();
}
},
handler);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertPlaybackStateCompatErrorEquals(controller.getPlaybackState(), testPlayerError);
}
@Test
public void playerError_notified() throws Exception {
ExoPlaybackException playerError =
ExoPlaybackException.createForUnexpected(new RuntimeException("player error"));
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<PlaybackStateCompat> playbackStateCompatRef = new AtomicReference<>();
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateCompatRef.set(state);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().notifyPlayerError(playerError);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
PlaybackStateCompat state = playbackStateCompatRef.get();
assertPlaybackStateCompatErrorEquals(state, playerError);
}
@Test
public void repeatModeChange() throws Exception {
@PlaybackStateCompat.RepeatMode int testRepeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
CountDownLatch latch = new CountDownLatch(1);
AtomicInteger repeatModeRef = new AtomicInteger();
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
repeatModeRef.set(repeatMode);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().setRepeatMode(Player.REPEAT_MODE_ALL);
session.getMockPlayer().notifyRepeatModeChanged();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(repeatModeRef.get()).isEqualTo(testRepeatMode);
assertThat(controllerCompat.getRepeatMode()).isEqualTo(testRepeatMode);
}
@Test
public void shuffleModeChange() throws Exception {
@PlaybackStateCompat.ShuffleMode
int testShuffleModeEnabled = PlaybackStateCompat.SHUFFLE_MODE_ALL;
CountDownLatch latch = new CountDownLatch(1);
AtomicInteger shuffleModeRef = new AtomicInteger();
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
shuffleModeRef.set(shuffleMode);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().setShuffleModeEnabled(/* shuffleModeEnabled= */ true);
session.getMockPlayer().notifyShuffleModeEnabledChanged();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(shuffleModeRef.get()).isEqualTo(testShuffleModeEnabled);
assertThat(controllerCompat.getShuffleMode()).isEqualTo(testShuffleModeEnabled);
}
@Test
public void release() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onSessionDestroyed() {
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.release();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void setPlayer_isNotified() throws Exception {
@State int testState = STATE_READY;
boolean testPlayWhenReady = true;
long testDurationMs = 200;
long testCurrentPositionMs = 11;
long testBufferedPositionMs = 100;
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
int testItemIndex = 0;
List<MediaItem> testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 3);
testMediaItems.set(
testItemIndex,
new MediaItem.Builder()
.setMediaId(testMediaItems.get(testItemIndex).mediaId)
.setMediaMetadata(
new MediaMetadata.Builder()
.setUserRating(new HeartRating(/* isHeart= */ true))
.build())
.build());
Timeline testTimeline = new PlaylistTimeline(testMediaItems);
String testPlaylistTitle = "testPlaylistTitle";
MediaMetadata testPlaylistMetadata =
new MediaMetadata.Builder().setTitle(testPlaylistTitle).build();
boolean testShuffleModeEnabled = true;
@RepeatMode int testRepeatMode = Player.REPEAT_MODE_ONE;
AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
AtomicInteger shuffleModeRef = new AtomicInteger();
AtomicInteger repeatModeRef = new AtomicInteger();
CountDownLatch latchForPlaybackState = new CountDownLatch(1);
CountDownLatch latchForMetadata = new CountDownLatch(1);
CountDownLatch latchForQueue = new CountDownLatch(2);
CountDownLatch latchForShuffleMode = new CountDownLatch(1);
CountDownLatch latchForRepeatMode = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateRef.set(state);
latchForPlaybackState.countDown();
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
metadataRef.set(metadata);
latchForMetadata.countDown();
}
@Override
public void onQueueChanged(List<QueueItem> queue) {
latchForQueue.countDown();
}
@Override
public void onQueueTitleChanged(CharSequence title) {
queueTitleRef.set(title);
latchForQueue.countDown();
}
@Override
public void onRepeatModeChanged(int repeatMode) {
repeatModeRef.set(repeatMode);
latchForRepeatMode.countDown();
}
@Override
public void onShuffleModeChanged(int shuffleMode) {
shuffleModeRef.set(shuffleMode);
latchForShuffleMode.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setPlaybackState(testState)
.setPlayWhenReady(testPlayWhenReady)
.setCurrentPosition(testCurrentPositionMs)
.setBufferedPosition(testBufferedPositionMs)
.setDuration(testDurationMs)
.setPlaybackParameters(playbackParameters)
.setTimeline(testTimeline)
.setPlaylistMetadata(testPlaylistMetadata)
.setCurrentMediaItem(testMediaItems.get(testItemIndex))
.setShuffleModeEnabled(testShuffleModeEnabled)
.setRepeatMode(testRepeatMode)
.build();
session.setPlayer(playerConfig);
assertThat(latchForPlaybackState.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateRef.get().getBufferedPosition()).isEqualTo(testBufferedPositionMs);
assertThat(playbackStateRef.get().getPosition()).isEqualTo(testCurrentPositionMs);
assertThat(playbackStateRef.get().getPlaybackSpeed())
.isWithin(EPSILON)
.of(playbackParameters.speed);
assertThat(latchForMetadata.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(metadataRef.get().getString(METADATA_KEY_MEDIA_ID))
.isEqualTo(testMediaItems.get(testItemIndex).mediaId);
assertThat(metadataRef.get().getLong(METADATA_KEY_DURATION)).isEqualTo(testDurationMs);
@PlaybackStateCompat.State
int playbackStateFromControllerCompat =
MediaUtils.convertToPlaybackState(
playbackStateRef.get(), metadataRef.get(), /* timeDiffMs= */ C.TIME_UNSET);
assertThat(playbackStateFromControllerCompat).isEqualTo(testState);
assertThat(metadataRef.get().getRating(METADATA_KEY_USER_RATING).hasHeart()).isTrue();
assertThat(latchForQueue.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
List<QueueItem> queue = controllerCompat.getQueue();
assertThat(queue).hasSize(testTimeline.getWindowCount());
for (int i = 0; i < testTimeline.getWindowCount(); i++) {
assertThat(queue.get(i).getDescription().getMediaId())
.isEqualTo(testMediaItems.get(i).mediaId);
}
assertThat(queueTitleRef.get().toString()).isEqualTo(testPlaylistTitle);
assertThat(latchForShuffleMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(shuffleModeRef.get()).isEqualTo(PlaybackStateCompat.SHUFFLE_MODE_ALL);
assertThat(latchForRepeatMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(repeatModeRef.get()).isEqualTo(PlaybackStateCompat.REPEAT_MODE_ONE);
}
@Test
public void setPlayer_playbackTypeChangedToRemote() throws Exception {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 25);
int legacyPlaybackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
int deviceVolume = 10;
CountDownLatch playbackInfoNotified = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
if (info.getPlaybackType() == legacyPlaybackType
&& info.getMaxVolume() == deviceInfo.maxVolume
&& info.getCurrentVolume() == deviceVolume) {
playbackInfoNotified.countDown();
}
}
};
controllerCompat.registerCallback(callback, handler);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfo)
.setDeviceVolume(deviceVolume)
.build();
session.setPlayer(playerConfig);
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackType);
assertThat(info.getMaxVolume()).isEqualTo(deviceInfo.maxVolume);
assertThat(info.getCurrentVolume()).isEqualTo(deviceVolume);
}
@Test
public void setPlayer_playbackTypeChangedToLocal() throws Exception {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setDeviceInfo(deviceInfo).build();
session.setPlayer(playerConfig);
DeviceInfo deviceInfoToUpdate =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 0, /* maxVolume= */ 10);
int legacyPlaybackTypeToUpdate = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
int legacyStream = AudioManager.STREAM_RING;
AudioAttributesCompat attrsCompat =
new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream).build();
AudioAttributes attrs = MediaUtils.convertToAudioAttributes(attrsCompat);
CountDownLatch playbackInfoNotified = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
if (info.getPlaybackType() == legacyPlaybackTypeToUpdate
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
playbackInfoNotified.countDown();
}
}
};
controllerCompat.registerCallback(callback, handler);
Bundle playerConfigToUpdate =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfoToUpdate)
.setAudioAttributes(attrs)
.build();
session.setPlayer(playerConfigToUpdate);
// In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local.
if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
PollingCheck.waitFor(
TIMEOUT_MS,
() -> {
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
return info.getPlaybackType() == legacyPlaybackTypeToUpdate
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream;
});
} else {
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackTypeToUpdate);
assertThat(info.getAudioAttributes().getLegacyStreamType()).isEqualTo(legacyStream);
}
}
@Test
public void setPlayer_playbackTypeNotChanged_local() throws Exception {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 0, /* maxVolume= */ 10);
int legacyPlaybackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
int legacyStream = AudioManager.STREAM_RING;
AudioAttributesCompat attrsCompat =
new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream).build();
AudioAttributes attrs = MediaUtils.convertToAudioAttributes(attrsCompat);
CountDownLatch playbackInfoNotified = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
if (info.getPlaybackType() == legacyPlaybackType
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
playbackInfoNotified.countDown();
}
}
};
controllerCompat.registerCallback(callback, handler);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfo)
.setAudioAttributes(attrs)
.build();
session.setPlayer(playerConfig);
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
if (Build.VERSION.SDK_INT >= 21) {
PollingCheck.waitFor(
TIMEOUT_MS,
() -> {
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
return info.getPlaybackType() == legacyPlaybackType
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream;
});
} else {
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackType);
assertThat(info.getAudioAttributes().getLegacyStreamType()).isEqualTo(legacyStream);
}
}
@Test
public void setPlayer_playbackTypeNotChanged_remote() throws Exception {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfo)
.setDeviceVolume(1)
.build();
session.setPlayer(playerConfig);
DeviceInfo deviceInfoToUpdate =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 25);
int legacyPlaybackTypeToUpdate = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
int deviceVolumeToUpdate = 10;
CountDownLatch playbackInfoNotified = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
if (info.getPlaybackType() == legacyPlaybackTypeToUpdate
&& info.getMaxVolume() == deviceInfoToUpdate.maxVolume
&& info.getCurrentVolume() == deviceVolumeToUpdate) {
playbackInfoNotified.countDown();
}
}
};
controllerCompat.registerCallback(callback, handler);
Bundle playerConfigToUpdate =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfoToUpdate)
.setDeviceVolume(deviceVolumeToUpdate)
.build();
session.setPlayer(playerConfigToUpdate);
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
if (Build.VERSION.SDK_INT >= 21) {
PollingCheck.waitFor(
TIMEOUT_MS,
() -> {
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
return info.getPlaybackType() == legacyPlaybackTypeToUpdate
&& info.getMaxVolume() == deviceInfoToUpdate.maxVolume
&& info.getCurrentVolume() == deviceVolumeToUpdate;
});
} else {
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackTypeToUpdate);
assertThat(info.getMaxVolume()).isEqualTo(deviceInfoToUpdate.maxVolume);
assertThat(info.getCurrentVolume()).isEqualTo(deviceVolumeToUpdate);
}
}
@Test
public void onPlaybackParametersChanged_notifiesPlaybackStateCompatChanges() throws Exception {
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateRef.set(state);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().notifyPlaybackParametersChanged(playbackParameters);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateRef.get().getPlaybackSpeed())
.isWithin(EPSILON)
.of(playbackParameters.speed);
assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed())
.isWithin(EPSILON)
.of(playbackParameters.speed);
}
@Test
public void playbackStateChange_playWhenReadyBecomesFalseWhenReady_notifiesPaused()
throws Exception {
session
.getMockPlayer()
.setPlayWhenReady(
/* playWhenReady= */ true,
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY);
session.getMockPlayer().notifyIsPlayingChanged(false);
AtomicReference<PlaybackStateCompat> playbackStateCompatRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
playbackStateCompatRef.set(playbackStateCompat);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session
.getMockPlayer()
.notifyPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
}
@Test
public void playbackStateChange_playWhenReadyBecomesTrueWhenBuffering_notifiesBuffering()
throws Exception {
session
.getMockPlayer()
.setPlayWhenReady(/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
session.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_BUFFERING);
session.getMockPlayer().notifyIsPlayingChanged(false);
AtomicReference<PlaybackStateCompat> playbackStateCompatRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
playbackStateCompatRef.set(playbackStateCompat);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session
.getMockPlayer()
.notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState())
.isEqualTo(PlaybackStateCompat.STATE_BUFFERING);
}
@Test
public void playbackStateChange_playbackStateBecomesEnded_notifiesPaused() throws Exception {
session
.getMockPlayer()
.setPlayWhenReady(
/* playWhenReady= */ true,
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY);
session.getMockPlayer().notifyIsPlayingChanged(false);
AtomicReference<PlaybackStateCompat> playbackStateCompatRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
playbackStateCompatRef.set(playbackStateCompat);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().notifyPlaybackStateChanged(STATE_ENDED);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
}
@Test
public void playbackStateChange_isPlayingBecomesTrue_notifiesPlaying() throws Exception {
session.getMockPlayer().notifyIsPlayingChanged(false);
AtomicReference<PlaybackStateCompat> playbackStateCompatRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
playbackStateCompatRef.set(playbackStateCompat);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().notifyIsPlayingChanged(true);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState())
.isEqualTo(PlaybackStateCompat.STATE_PLAYING);
}
@Test
public void playbackStateChange_positionDiscontinuityNotifies_updatesPosition() throws Exception {
long testSeekPosition = 1300;
AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateRef.set(state);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().setCurrentPosition(testSeekPosition);
session
.getMockPlayer()
.notifyPositionDiscontinuity(
/* oldPosition= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
/* newPosition= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
Player.DISCONTINUITY_REASON_SEEK);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateRef.get().getPosition()).isEqualTo(testSeekPosition);
assertThat(controllerCompat.getPlaybackState().getPosition()).isEqualTo(testSeekPosition);
}
@Test
public void currentMediaItemChange() throws Exception {
int testItemIndex = 3;
long testPosition = 1234;
String testDisplayTitle = "displayTitle";
List<MediaItem> testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 5);
testMediaItems.set(
testItemIndex,
new MediaItem.Builder()
.setMediaId(testMediaItems.get(testItemIndex).mediaId)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(testDisplayTitle).build())
.build());
Timeline timeline = new PlaylistTimeline(testMediaItems);
session.getMockPlayer().setTimeline(timeline);
AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
CountDownLatch latchForMetadata = new CountDownLatch(1);
CountDownLatch latchForPlaybackState = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
metadataRef.set(metadata);
latchForMetadata.countDown();
}
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
playbackStateRef.set(state);
latchForPlaybackState.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().setCurrentWindowIndex(testItemIndex);
session.getMockPlayer().setCurrentPosition(testPosition);
session
.getMockPlayer()
.notifyMediaItemTransition(testItemIndex, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
assertThat(latchForMetadata.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(metadataRef.get().getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE))
.isEqualTo(testDisplayTitle);
assertThat(
controllerCompat
.getMetadata()
.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE))
.isEqualTo(testDisplayTitle);
assertThat(latchForPlaybackState.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateRef.get().getPosition()).isEqualTo(testPosition);
assertThat(controllerCompat.getPlaybackState().getPosition()).isEqualTo(testPosition);
assertThat(playbackStateRef.get().getActiveQueueItemId())
.isEqualTo(MediaUtils.convertToQueueItemId(testItemIndex));
assertThat(controllerCompat.getPlaybackState().getActiveQueueItemId())
.isEqualTo(MediaUtils.convertToQueueItemId(testItemIndex));
}
@Test
public void playlistChange() throws Exception {
AtomicReference<List<QueueItem>> queueRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onQueueChanged(List<QueueItem> queue) {
queueRef.set(queue);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 5);
session.getMockPlayer().setTimeline(timeline);
session.getMockPlayer().notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
List<QueueItem> queueFromParam = queueRef.get();
List<QueueItem> queueFromGetter = controllerCompat.getQueue();
assertThat(queueFromParam).hasSize(timeline.getWindowCount());
assertThat(queueFromGetter).hasSize(timeline.getWindowCount());
Timeline.Window window = new Timeline.Window();
for (int i = 0; i < timeline.getWindowCount(); i++) {
assertThat(queueFromParam.get(i).getDescription().getMediaId())
.isEqualTo(timeline.getWindow(i, window).mediaItem.mediaId);
assertThat(queueFromGetter.get(i).getDescription().getMediaId())
.isEqualTo(timeline.getWindow(i, window).mediaItem.mediaId);
}
}
@Test
public void playlistChange_longList() throws Exception {
AtomicReference<List<QueueItem>> queueRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onQueueChanged(List<QueueItem> queue) {
queueRef.set(queue);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
int listSize = 5_000;
session.getMockPlayer().createAndSetFakeTimeline(listSize);
session.getMockPlayer().notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
List<QueueItem> queueFromParam = queueRef.get();
List<QueueItem> queueFromGetter = controllerCompat.getQueue();
if (Build.VERSION.SDK_INT >= 21) {
assertThat(queueFromParam).hasSize(listSize);
assertThat(queueFromGetter).hasSize(listSize);
} else {
// Below API 21, only the initial part of the playlist is sent to the
// MediaControllerCompat when the list is too long.
assertThat(queueFromParam.size() < listSize).isTrue();
assertThat(queueFromGetter).hasSize(queueFromParam.size());
}
for (int i = 0; i < listSize; i++) {
assertThat(queueFromParam.get(i).getDescription().getMediaId())
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
assertThat(queueFromGetter.get(i).getDescription().getMediaId())
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
}
@Test
public void playlistMetadataChange() throws Exception {
AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onQueueTitleChanged(CharSequence title) {
queueTitleRef.set(title);
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
String playlistTitle = "playlistTitle";
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle(playlistTitle).build();
session.getMockPlayer().setPlaylistMetadata(playlistMetadata);
session.getMockPlayer().notifyPlaylistMetadataChanged();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(queueTitleRef.get().toString()).isEqualTo(playlistTitle);
}
@Test
public void onAudioInfoChanged_isCalledByVolumeChange() throws Exception {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setDeviceInfo(deviceInfo)
.setDeviceVolume(1)
.build();
session.setPlayer(playerConfig);
int targetVolume = 3;
CountDownLatch targetVolumeNotified = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
if (info.getCurrentVolume() == targetVolume) {
targetVolumeNotified.countDown();
}
}
};
controllerCompat.registerCallback(callback, handler);
session.getMockPlayer().notifyDeviceVolumeChanged(targetVolume, /* muted= */ false);
assertThat(targetVolumeNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(controllerCompat.getPlaybackInfo().getCurrentVolume()).isEqualTo(targetVolume);
}
private static void assertPlaybackStateCompatErrorEquals(
PlaybackStateCompat state, ExoPlaybackException playerError) {
assertThat(state.getState()).isEqualTo(PlaybackStateCompat.STATE_ERROR);
assertThat(state.getErrorCode()).isEqualTo(PlaybackStateCompat.ERROR_CODE_UNKNOWN_ERROR);
assertThat(state.getErrorMessage()).isEqualTo(playerError.getMessage());
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2019 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.common.truth.Truth.assertThat;
import android.os.RemoteException;
import android.view.Surface;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.SurfaceActivity;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/** Tests for {@link MediaController#setVideoSurface(Surface)}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaControllerSurfaceTest {
private static final String TAG = "MC_SurfaceTest";
private final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
private final MediaControllerTestRule controllerTestRule =
new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
private SurfaceActivity activity;
private RemoteMediaSession remoteSession;
@Rule
public ActivityTestRule<SurfaceActivity> activityRule =
new ActivityTestRule<>(SurfaceActivity.class);
@Before
public void setUp() throws Exception {
activity = activityRule.getActivity();
remoteSession =
new RemoteMediaSession(
DEFAULT_TEST_NAME, ApplicationProvider.getApplicationContext(), null);
}
@After
public void cleanUp() throws RemoteException {
remoteSession.cleanUp();
}
@Test
public void setVideoSurface() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
}
@Test
public void setVideoSurface_withNull_clearsSurface() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
}
@Test
public void clearVideoSurface() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface());
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
}
@Test
public void clearVideoSurface_withTheSameSurface() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(testSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
}
@Test
public void clearVideoSurface_withDifferentSurface_doesNothing() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
Surface anotherSurface = activity.getSecondSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(anotherSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
}
@Test
public void clearVideoSurface_withNull_doesNothing() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
}
}

View File

@ -0,0 +1,805 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.HeartRating;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.PollingCheck;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/** Tests for {@link MediaController}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaControllerTest {
private static final String TAG = "MediaControllerTest";
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
final MediaControllerTestRule controllerTestRule = new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
private final List<RemoteMediaSession> remoteSessionList = new ArrayList<>();
private Context context;
private RemoteMediaSession remoteSession;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
remoteSession = createRemoteMediaSession(DEFAULT_TEST_NAME, null);
}
@After
public void cleanUp() throws RemoteException {
for (int i = 0; i < remoteSessionList.size(); i++) {
RemoteMediaSession session = remoteSessionList.get(i);
if (session != null) {
session.cleanUp();
}
}
}
@Test
public void builder() throws Exception {
MediaController.Builder builder;
try {
builder = new MediaController.Builder(null);
assertWithMessage("null context shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaController.Builder(context);
builder.setSessionToken(null);
assertWithMessage("null token shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaController.Builder(context);
builder.setSessionCompatToken(null);
assertWithMessage("null compat token shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaController.Builder(context);
builder.setControllerCallback(null);
assertWithMessage("null callback shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaController.Builder(context);
builder.setApplicationLooper(null);
assertWithMessage("null looper shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
MediaController controller =
new MediaController.Builder(context)
.setSessionToken(remoteSession.getToken())
.setControllerCallback(new ControllerCallback() {})
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
threadTestRule.getHandler().postAndSync(controller::release);
}
@Test
public void getSessionActivity() throws Exception {
RemoteMediaSession session = createRemoteMediaSession(TEST_GET_SESSION_ACTIVITY, null);
MediaController controller = controllerTestRule.createController(session.getToken());
PendingIntent sessionActivity = controller.getSessionActivity();
assertThat(sessionActivity).isNotNull();
if (Build.VERSION.SDK_INT >= 17) {
// PendingIntent#getCreatorPackage() is added in API 17.
assertThat(sessionActivity.getCreatorPackage()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
// TODO: Add getPid/getUid in MediaControllerProviderService and compare them.
// assertThat(sessionActivity.getCreatorUid()).isEqualTo(remoteSession.getUid());
}
session.cleanUp();
}
@Test
public void getPackageName() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
assertThat(controller.getConnectedToken().getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
}
@Test
public void getTokenExtras() throws Exception {
Bundle testTokenExtras = TestUtils.createTestBundle();
RemoteMediaSession session = createRemoteMediaSession("testGetExtras", testTokenExtras);
MediaController controller = controllerTestRule.createController(session.getToken());
SessionToken connectedToken = controller.getConnectedToken();
assertThat(connectedToken).isNotNull();
assertThat(TestUtils.equals(testTokenExtras, connectedToken.getExtras())).isTrue();
}
@Test
public void isConnected() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
assertThat(controller.isConnected()).isTrue();
remoteSession.release();
controllerTestRule.waitForDisconnect(controller, true);
assertThat(controller.isConnected()).isFalse();
}
@Test
public void close_beforeConnected() throws Exception {
MediaController controller =
controllerTestRule.createController(
remoteSession.getToken(), /* waitForConnect= */ false, null, /* callback= */ null);
threadTestRule.getHandler().postAndSync(controller::release);
}
@Test
public void close_twice() throws Exception {
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
threadTestRule.getHandler().postAndSync(controller::release);
threadTestRule.getHandler().postAndSync(controller::release);
}
@Test
public void gettersAfterConnected() throws Exception {
long currentPositionMs = 11;
long contentPositionMs = 33;
long durationMs = 200;
long bufferedPositionMs = 100;
int bufferedPercentage = 50;
long totalBufferedDurationMs = 120;
long currentLiveOffsetMs = 10;
long contentDurationMs = 300;
long contentBufferedPositionMs = 240;
boolean isPlayingAd = true;
int currentAdGroupIndex = 33;
int currentAdIndexInAdGroup = 22;
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 0.5f);
boolean playWhenReady = true;
@Player.PlaybackSuppressionReason
int playbackSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
@Player.State int playbackState = Player.STATE_READY;
boolean isPlaying = true;
boolean isLoading = true;
MediaItem currentMediaItem = MediaTestUtils.createConvergedMediaItem(/* mediaId= */ "current");
boolean isShuffleModeEnabled = true;
@RepeatMode int repeatMode = Player.REPEAT_MODE_ONE;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentPosition(currentPositionMs)
.setContentPosition(contentPositionMs)
.setDuration(durationMs)
.setBufferedPosition(bufferedPositionMs)
.setBufferedPercentage(bufferedPercentage)
.setTotalBufferedDuration(totalBufferedDurationMs)
.setCurrentLiveOffset(currentLiveOffsetMs)
.setContentDuration(contentDurationMs)
.setContentBufferedPosition(contentBufferedPositionMs)
.setIsPlayingAd(isPlayingAd)
.setCurrentAdGroupIndex(currentAdGroupIndex)
.setCurrentAdIndexInAdGroup(currentAdIndexInAdGroup)
.setPlaybackParameters(playbackParameters)
.setCurrentMediaItem(currentMediaItem)
.setPlayWhenReady(playWhenReady)
.setPlaybackSuppressionReason(playbackSuppressionReason)
.setPlaybackState(playbackState)
.setIsPlaying(isPlaying)
.setIsLoading(isLoading)
.setShuffleModeEnabled(isShuffleModeEnabled)
.setRepeatMode(repeatMode)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(0L));
AtomicLong currentPositionMsRef = new AtomicLong();
AtomicLong contentPositionMsRef = new AtomicLong();
AtomicLong durationMsRef = new AtomicLong();
AtomicLong bufferedPositionMsRef = new AtomicLong();
AtomicInteger bufferedPercentageRef = new AtomicInteger();
AtomicLong totalBufferedDurationMsRef = new AtomicLong();
AtomicLong currentLiveOffsetMsRef = new AtomicLong();
AtomicLong contentDurationMsRef = new AtomicLong();
AtomicLong contentBufferedPositionMsRef = new AtomicLong();
AtomicBoolean isPlayingAdRef = new AtomicBoolean();
AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
AtomicReference<PlaybackParameters> playbackParametersRef = new AtomicReference<>();
AtomicReference<MediaItem> mediaItemRef = new AtomicReference<>();
AtomicBoolean playWhenReadyRef = new AtomicBoolean();
AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
AtomicInteger playbackStateRef = new AtomicInteger();
AtomicBoolean isPlayingRef = new AtomicBoolean();
AtomicBoolean isLoadingRef = new AtomicBoolean();
AtomicBoolean isShuffleModeEnabledRef = new AtomicBoolean();
AtomicInteger repeatModeRef = new AtomicInteger();
threadTestRule
.getHandler()
.postAndSync(
() -> {
currentPositionMsRef.set(controller.getCurrentPosition());
contentPositionMsRef.set(controller.getContentPosition());
durationMsRef.set(controller.getDuration());
bufferedPositionMsRef.set(controller.getBufferedPosition());
bufferedPercentageRef.set(controller.getBufferedPercentage());
totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
contentDurationMsRef.set(controller.getContentDuration());
contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
playbackParametersRef.set(controller.getPlaybackParameters());
isPlayingAdRef.set(controller.isPlayingAd());
currentAdGroupIndexRef.set(controller.getCurrentAdGroupIndex());
currentAdIndexInAdGroupRef.set(controller.getCurrentAdIndexInAdGroup());
mediaItemRef.set(controller.getCurrentMediaItem());
playWhenReadyRef.set(controller.getPlayWhenReady());
playbackSuppressionReasonRef.set(controller.getPlaybackSuppressionReason());
playbackStateRef.set(controller.getPlaybackState());
isPlayingRef.set(controller.isPlaying());
isLoadingRef.set(controller.isLoading());
isShuffleModeEnabledRef.set(controller.getShuffleModeEnabled());
repeatModeRef.set(controller.getRepeatMode());
});
assertThat(currentPositionMsRef.get()).isEqualTo(currentPositionMs);
assertThat(contentPositionMsRef.get()).isEqualTo(contentPositionMs);
assertThat(durationMsRef.get()).isEqualTo(durationMs);
assertThat(bufferedPositionMsRef.get()).isEqualTo(bufferedPositionMs);
assertThat(bufferedPercentageRef.get()).isEqualTo(bufferedPercentage);
assertThat(totalBufferedDurationMsRef.get()).isEqualTo(totalBufferedDurationMs);
assertThat(currentLiveOffsetMsRef.get()).isEqualTo(currentLiveOffsetMs);
assertThat(contentDurationMsRef.get()).isEqualTo(contentDurationMs);
assertThat(contentBufferedPositionMsRef.get()).isEqualTo(contentBufferedPositionMs);
assertThat(playbackParametersRef.get()).isEqualTo(playbackParameters);
assertThat(isPlayingAdRef.get()).isEqualTo(isPlayingAd);
assertThat(currentAdGroupIndexRef.get()).isEqualTo(currentAdGroupIndex);
assertThat(currentAdIndexInAdGroupRef.get()).isEqualTo(currentAdIndexInAdGroup);
MediaTestUtils.assertMediaIdEquals(currentMediaItem, mediaItemRef.get());
assertThat(playWhenReadyRef.get()).isEqualTo(playWhenReady);
assertThat(playbackSuppressionReasonRef.get()).isEqualTo(playbackSuppressionReason);
assertThat(playbackStateRef.get()).isEqualTo(playbackState);
assertThat(isPlayingRef.get()).isEqualTo(isPlaying);
assertThat(isLoadingRef.get()).isEqualTo(isLoading);
assertThat(isShuffleModeEnabledRef.get()).isEqualTo(isShuffleModeEnabled);
assertThat(repeatModeRef.get()).isEqualTo(repeatMode);
}
@Test
public void getPlayerError() throws Exception {
ExoPlaybackException testPlayerError = ExoPlaybackException.createForRemote("test");
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setPlayerError(testPlayerError).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
ExoPlaybackException playerError =
threadTestRule.getHandler().postAndSync(controller::getPlayerError);
assertThat(TestUtils.equals(playerError, testPlayerError)).isTrue();
}
@Test
public void getVideoSize_returnsVideoSizeOfPlayerInSession() throws Exception {
VideoSize testVideoSize =
new VideoSize(
/* width= */ 100,
/* height= */ 42,
/* unappliedRotationDegrees= */ 90,
/* pixelWidthHeightRatio= */ 1.2f);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setVideoSize(testVideoSize).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
VideoSize videoSize = threadTestRule.getHandler().postAndSync(controller::getVideoSize);
assertThat(videoSize).isEqualTo(testVideoSize);
}
@Test
public void futuresCompleted_AvailableCommandsChange() throws Exception {
RemoteMediaSession session = remoteSession;
MediaController controller = controllerTestRule.createController(session.getToken());
SessionCommands.Builder builder = new SessionCommands.Builder();
SessionCommand setRatingCommand =
new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING);
SessionCommand customCommand = new SessionCommand("custom", null);
int trials = 100;
CountDownLatch latch = new CountDownLatch(trials * 2);
for (int trial = 0; trial < trials; trial++) {
if (trial % 2 == 0) {
builder.add(setRatingCommand);
builder.add(customCommand);
} else {
builder.remove(setRatingCommand);
builder.remove(customCommand);
}
session.setAvailableCommands(builder.build(), Player.Commands.EMPTY);
String testMediaId = "testMediaId";
Rating testRating = new HeartRating(/* hasHeart= */ true);
controller.setRating(testMediaId, testRating).addListener(latch::countDown, Runnable::run);
controller
.sendCustomCommand(customCommand, null)
.addListener(latch::countDown, Runnable::run);
}
assertWithMessage("All futures should be completed")
.that(latch.await(LONG_TIMEOUT_MS, MILLISECONDS))
.isTrue();
}
@Test
public void getPlaylistMetadata_returnsPlaylistMetadataOfPlayerInSession() throws Exception {
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setPlaylistMetadata(playlistMetadata)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
assertThat(threadTestRule.getHandler().postAndSync(controller::getPlaylistMetadata))
.isEqualTo(playlistMetadata);
}
@Test
public void getAudioAttributes_returnsAudioAttributesOfPlayerInSession() throws Exception {
AudioAttributes testAttributes =
new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MUSIC).build();
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setAudioAttributes(testAttributes).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
AudioAttributes attributes =
threadTestRule.getHandler().postAndSync(controller::getAudioAttributes);
assertThat(attributes).isEqualTo(testAttributes);
}
@Test
public void getVolume_returnsVolumeOfPlayerInSession() throws Exception {
float testVolume = .5f;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setVolume(testVolume).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
float volume = threadTestRule.getHandler().postAndSync(controller::getVolume);
assertThat(volume).isEqualTo(testVolume);
}
@Test
public void getCurrentWindowIndex() throws Exception {
int testWindowIndex = 1;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentWindowIndex(testWindowIndex)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int currentWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getCurrentWindowIndex);
assertThat(currentWindowIndex).isEqualTo(testWindowIndex);
}
@Test
public void getCurrentPeriodIndex() throws Exception {
int testPeriodIndex = 1;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentPeriodIndex(testPeriodIndex)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int currentPeriodIndex =
threadTestRule.getHandler().postAndSync(controller::getCurrentPeriodIndex);
assertThat(currentPeriodIndex).isEqualTo(testPeriodIndex);
}
@Test
public void getPreviousWindowIndex() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(1)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int previousWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
assertThat(previousWindowIndex).isEqualTo(0);
}
@Test
public void getPreviousWindowIndex_withRepeatModeOne() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(1)
.setRepeatMode(Player.REPEAT_MODE_ONE)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int previousWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
assertThat(previousWindowIndex).isEqualTo(0);
}
@Test
public void getPreviousWindowIndex_atTheFirstWindow() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(0)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int previousWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
assertThat(previousWindowIndex).isEqualTo(C.INDEX_UNSET);
}
@Test
public void getPreviousWindowIndex_atTheFirstWindowWithRepeatModeAll() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(0)
.setRepeatMode(Player.REPEAT_MODE_ALL)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int previousWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
assertThat(previousWindowIndex).isEqualTo(2);
}
@Test
public void getPreviousWindowIndex_withShuffleModeEnabled() throws Exception {
Timeline timeline =
new PlaylistTimeline(
MediaTestUtils.createConvergedMediaItems(/* size= */ 3),
/* shuffledIndices= */ new int[] {0, 2, 1});
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(2)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(true)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int previousWindowIndex =
threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
assertThat(previousWindowIndex).isEqualTo(0);
}
@Test
public void getNextWindowIndex() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(1)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
assertThat(nextWindowIndex).isEqualTo(2);
}
@Test
public void getNextWindowIndex_withRepeatModeOne() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(1)
.setRepeatMode(Player.REPEAT_MODE_ONE)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
assertThat(nextWindowIndex).isEqualTo(2);
}
@Test
public void getNextWindowIndex_atTheLastWindow() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(2)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
assertThat(nextWindowIndex).isEqualTo(C.INDEX_UNSET);
}
@Test
public void getNextWindowIndex_atTheLastWindowWithRepeatModeAll() throws Exception {
Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(2)
.setRepeatMode(Player.REPEAT_MODE_ALL)
.setShuffleModeEnabled(false)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
assertThat(nextWindowIndex).isEqualTo(0);
}
@Test
public void getNextWindowIndex_withShuffleModeEnabled() throws Exception {
Timeline timeline =
new PlaylistTimeline(
MediaTestUtils.createConvergedMediaItems(/* size= */ 3),
/* shuffledIndices= */ new int[] {0, 2, 1});
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setTimeline(timeline)
.setCurrentWindowIndex(2)
.setRepeatMode(Player.REPEAT_MODE_OFF)
.setShuffleModeEnabled(true)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
assertThat(nextWindowIndex).isEqualTo(1);
}
@Test
public void getMediaItemCount() throws Exception {
int windowCount = 3;
Timeline timeline = MediaTestUtils.createTimeline(windowCount);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setTimeline(timeline).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
int mediaItemCount = threadTestRule.getHandler().postAndSync(controller::getMediaItemCount);
assertThat(mediaItemCount).isEqualTo(windowCount);
}
@Test
public void getMediaItemAt() throws Exception {
int windowCount = 3;
int windowIndex = 1;
Timeline timeline = MediaTestUtils.createTimeline(windowCount);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setTimeline(timeline).build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
MediaItem mediaItem =
threadTestRule.getHandler().postAndSync(() -> controller.getMediaItemAt(windowIndex));
assertThat(mediaItem)
.isEqualTo(timeline.getWindow(windowIndex, new Timeline.Window()).mediaItem);
}
private RemoteMediaSession createRemoteMediaSession(String id, Bundle tokenExtras)
throws Exception {
RemoteMediaSession session = new RemoteMediaSession(id, context, tokenExtras);
remoteSessionList.add(session);
return session;
}
@Test
public void getCurrentPosition_whenNotPlaying_doesNotAdvance() throws Exception {
long testCurrentPositionMs = 100L;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setIsPlaying(false)
.setCurrentPosition(testCurrentPositionMs)
.setDuration(10_000L)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
long currentPositionMs =
threadTestRule
.getHandler()
.postAndSync(
() -> {
controller.setTimeDiffMs(50L);
return controller.getCurrentPosition();
});
assertThat(currentPositionMs).isEqualTo(testCurrentPositionMs);
}
@Test
public void getCurrentPosition_whenPlaying_advances() throws Exception {
long testCurrentPosition = 100L;
PlaybackParameters testPlaybackParameters = new PlaybackParameters(/* speed= */ 2.0f);
long testTimeDiff = 50L;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setIsPlaying(true)
.setCurrentPosition(testCurrentPosition)
.setDuration(10_000L)
.setPlaybackParameters(testPlaybackParameters)
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
long currentPositionMs =
threadTestRule
.getHandler()
.postAndSync(
() -> {
controller.setTimeDiffMs(testTimeDiff);
return controller.getCurrentPosition();
});
long expectedCurrentPositionMs =
testCurrentPosition + (long) (testTimeDiff * testPlaybackParameters.speed);
assertThat(currentPositionMs).isEqualTo(expectedCurrentPositionMs);
}
@Test
public void getContentPosition_whenPlayingAd_doesNotAdvance() throws Exception {
long testContentPosition = 100L;
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder()
.setContentPosition(testContentPosition)
.setDuration(10_000L)
.setIsPlaying(true)
.setIsPlayingAd(true)
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f))
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
long contentPositionMs =
threadTestRule
.getHandler()
.postAndSync(
() -> {
controller.setTimeDiffMs(50L);
return controller.getContentPosition();
});
assertThat(contentPositionMs).isEqualTo(testContentPosition);
}
@Test
public void getBufferedPosition_withPeriodicUpdate_updatedWithoutCallback() throws Exception {
long testBufferedPosition = 999L;
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
remoteSession.getMockPlayer().setBufferedPosition(testBufferedPosition);
remoteSession.setSessionPositionUpdateDelayMs(0L);
PollingCheck.waitFor(
TIMEOUT_MS,
() -> {
long bufferedPosition =
threadTestRule.getHandler().postAndSync(controller::getBufferedPosition);
return bufferedPosition == testBufferedPosition;
});
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.util.Log;
import java.util.Map;
import org.junit.rules.ExternalResource;
/**
* TestRule for managing {@link MediaController} instances. This class is not thread-safe, so call
* its methods on the junit test thread only.
*/
public final class MediaControllerTestRule extends ExternalResource {
private static final String TAG = "MediaControllerTestRule";
private final HandlerThreadTestRule handlerThreadTestRule;
private final Map<MediaController, TestBrowserCallback> controllers = new ArrayMap<>();
private volatile Context context;
private volatile Class<? extends MediaController> controllerType = MediaController.class;
public MediaControllerTestRule(HandlerThreadTestRule handlerThreadTestRule) {
this.handlerThreadTestRule = handlerThreadTestRule;
}
@Override
protected void before() {
context = ApplicationProvider.getApplicationContext();
}
@Override
protected void after() {
for (MediaController controller : controllers.keySet()) {
try {
handlerThreadTestRule.getHandler().postAndSync(controller::release);
} catch (Exception e) {
Log.e(TAG, "Exception in release", e);
}
}
controllers.clear();
}
/**
* Sets a subtype of {@link MediaController} to be instantiated by {@link #createController}. It
* can be either {@link MediaController} or {@link MediaBrowser}. The default is {@link
* MediaController}.
*/
public void setControllerType(Class<? extends MediaController> controllerType) {
if (!(controllerType == MediaController.class || controllerType == MediaBrowser.class)) {
throw new IllegalArgumentException("Illegal controllerType, " + controllerType);
}
this.controllerType = controllerType;
}
/**
* Creates {@link MediaController} from {@link MediaSessionCompat.Token} with default options
* waiting for connection.
*/
@NonNull
public MediaController createController(@NonNull MediaSessionCompat.Token token)
throws Exception {
return createController(token, /* waitForConnect= */ true, /* callback= */ null);
}
/** Creates {@link MediaController} from {@link MediaSessionCompat.Token}. */
@NonNull
public MediaController createController(
@NonNull MediaSessionCompat.Token token,
boolean waitForConnect,
@Nullable ControllerCallback callback)
throws Exception {
TestBrowserCallback testCallback = new TestBrowserCallback(callback);
MediaController controller = createControllerOnHandler(token, testCallback);
controllers.put(controller, testCallback);
if (waitForConnect) {
testCallback.waitForConnect(true);
}
return controller;
}
@NonNull
private MediaController createControllerOnHandler(
@NonNull MediaSessionCompat.Token token, @NonNull TestBrowserCallback callback)
throws Exception {
// Create controller on the test handler, for changing MediaBrowserCompat's Handler
// Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
// and commands wouldn't be run if tests codes waits on the test handler.
return handlerThreadTestRule
.getHandler()
.postAndSync(
() -> {
if (controllerType == MediaBrowser.class) {
return new MediaBrowser.Builder(context)
.setSessionCompatToken(token)
.setControllerCallback(callback)
.build();
} else {
return new MediaController.Builder(context)
.setSessionCompatToken(token)
.setControllerCallback(callback)
.build();
}
});
}
/**
* Creates {@link MediaController} from {@link SessionToken} with default options waiting for
* connection.
*/
@NonNull
public MediaController createController(@NonNull SessionToken token) throws Exception {
return createController(
token, /* waitForConnect= */ true, /* connectionHints= */ null, /* callback= */ null);
}
/** Creates {@link MediaController} from {@link SessionToken}. */
@NonNull
public MediaController createController(
@NonNull SessionToken token,
boolean waitForConnect,
@Nullable Bundle connectionHints,
@Nullable ControllerCallback callback)
throws Exception {
TestBrowserCallback testCallback = new TestBrowserCallback(callback);
MediaController controller = createControllerOnHandler(token, connectionHints, testCallback);
controllers.put(controller, testCallback);
if (waitForConnect) {
testCallback.waitForConnect(true);
}
return controller;
}
@NonNull
private MediaController createControllerOnHandler(
@NonNull SessionToken token,
@Nullable Bundle connectionHints,
@NonNull TestBrowserCallback callback)
throws Exception {
// Create controller on the test handler, for changing MediaBrowserCompat's Handler
// Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
// and commands wouldn't be run if tests codes waits on the test handler.
return handlerThreadTestRule
.getHandler()
.postAndSync(
() -> {
if (controllerType == MediaBrowser.class) {
MediaBrowser.Builder builder =
new MediaBrowser.Builder(context)
.setSessionToken(token)
.setControllerCallback(callback);
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
return builder.build();
} else {
MediaController.Builder builder =
new MediaController.Builder(context)
.setSessionToken(token)
.setControllerCallback(callback);
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
return builder.build();
}
});
}
public void waitForConnect(MediaController controller, boolean expected)
throws InterruptedException {
controllers.get(controller).waitForConnect(expected);
}
public void waitForDisconnect(MediaController controller, boolean expected)
throws InterruptedException {
controllers.get(controller).waitForDisconnect(expected);
}
public void setRunnableForOnCustomCommand(MediaController controller, Runnable runnable) {
controllers.get(controller).setRunnableForOnCustomCommand(runnable);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.STATE_READY;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Build;
import android.os.HandlerThread;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import com.google.android.exoplayer2.Player.State;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaController} with framework MediaSession, which exists since Android-L. */
@RunWith(AndroidJUnit4.class)
@LargeTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) // For framework MediaSession
public class MediaControllerWithFrameworkMediaSessionTest {
private static final String TAG = "MediaControllerWithFrameworkMediaSessionTest";
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private Context context;
private TestHandler handler;
private MediaSession fwkSession;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
TestHandler handler = new TestHandler(handlerThread.getLooper());
this.handler = handler;
fwkSession = new android.media.session.MediaSession(context, TAG);
fwkSession.setActive(true);
fwkSession.setFlags(
MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
fwkSession.setCallback(new android.media.session.MediaSession.Callback() {}, handler);
}
@After
public void cleanUp() {
if (fwkSession != null) {
fwkSession.release();
fwkSession = null;
}
if (handler != null) {
if (Build.VERSION.SDK_INT >= 18) {
handler.getLooper().quitSafely();
} else {
handler.getLooper().quit();
}
handler = null;
}
}
@Test
public void onConnected_calledAfterCreated() throws Exception {
CountDownLatch connectedLatch = new CountDownLatch(1);
MediaController.ControllerCallback callback =
new MediaController.ControllerCallback() {
@Override
public void onConnected(MediaController controller) {
connectedLatch.countDown();
}
};
MediaController controller =
new MediaController.Builder(context)
.setSessionCompatToken(MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
.setControllerCallback(callback)
.setApplicationLooper(handler.getLooper())
.build();
try {
assertThat(connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
} finally {
handler.postAndSync(controller::release);
}
}
@Test
public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Exception {
CountDownLatch connectedLatch = new CountDownLatch(1);
CountDownLatch playbackStateChangedLatch = new CountDownLatch(1);
AtomicInteger playbackStateRef = new AtomicInteger();
AtomicBoolean playWhenReadyRef = new AtomicBoolean();
MediaController.ControllerCallback callback =
new MediaController.ControllerCallback() {
@Override
public void onConnected(MediaController controller) {
connectedLatch.countDown();
}
};
MediaController controller =
new MediaController.Builder(context)
.setSessionCompatToken(MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
.setControllerCallback(callback)
.setApplicationLooper(handler.getLooper())
.build();
SessionPlayer.PlayerCallback playerCallback =
new SessionPlayer.PlayerCallback() {
@Override
public void onPlaybackStateChanged(@State int state) {
playbackStateRef.set(state);
playWhenReadyRef.set(controller.getPlayWhenReady());
playbackStateChangedLatch.countDown();
}
};
try {
controller.addListener(playerCallback);
assertThat(connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
fwkSession.setPlaybackState(
new PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1.0f)
.build());
assertThat(playbackStateChangedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateRef.get()).isEqualTo(STATE_READY);
assertThat(playWhenReadyRef.get()).isTrue();
} finally {
handler.postAndSync(controller::release);
}
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.CountDownLatch;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for {@link MediaLibrarySession.MediaLibrarySessionCallback}.
*
* <p>TODO: Make this class extend MediaSessionCallbackTest. TODO: Create MediaLibrarySessionTest
* which extends MediaSessionTest.
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
public class MediaLibrarySessionCallbackTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaLibrarySessionCallbackTest");
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
private Context context;
private MockPlayer player;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
player =
new MockPlayer.Builder()
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
}
@Test
public void onSubscribe() throws Exception {
String testParentId = "testSubscribeId";
LibraryParams testParams = MediaTestUtils.createLibraryParams();
CountDownLatch latch = new CountDownLatch(1);
MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
new MediaLibrarySession.MediaLibrarySessionCallback() {
@Override
@NonNull
public ListenableFuture<LibraryResult> onSubscribe(
@NonNull MediaLibrarySession session,
@NonNull MediaSession.ControllerInfo browser,
@NonNull String parentId,
LibraryParams params) {
assertThat(parentId).isEqualTo(testParentId);
MediaTestUtils.assertLibraryParamsEquals(testParams, params);
latch.countDown();
return new LibraryResult(RESULT_SUCCESS).asFuture();
}
};
MockMediaLibraryService service = new MockMediaLibraryService();
service.attachBaseContext(context);
MediaLibrarySession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaLibrarySession.Builder(service, player, sessionCallback)
.setId("testOnSubscribe")
.build());
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
browser.subscribe(testParentId, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onUnsubscribe() throws Exception {
String testParentId = "testUnsubscribeId";
CountDownLatch latch = new CountDownLatch(1);
MediaLibrarySession.MediaLibrarySessionCallback sessionCallback =
new MediaLibrarySession.MediaLibrarySessionCallback() {
@Override
@NonNull
public ListenableFuture<LibraryResult> onUnsubscribe(
@NonNull MediaLibrarySession session,
@NonNull MediaSession.ControllerInfo browser,
@NonNull String parentId) {
assertThat(parentId).isEqualTo(testParentId);
latch.countDown();
return new LibraryResult(RESULT_SUCCESS).asFuture();
}
};
MockMediaLibraryService service = new MockMediaLibraryService();
service.attachBaseContext(context);
MediaLibrarySession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaLibrarySession.Builder(service, player, sessionCallback)
.setId("testOnUnsubscribe")
.build());
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
browser.unsubscribe(testParentId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.STATE_IDLE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.os.Build;
import android.os.HandlerThread;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaSession} and {@link MediaController} in the same process. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionAndControllerTest {
private Context context;
private TestHandler handler;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
HandlerThread handlerThread = new HandlerThread("MediaSessionAndControllerTest");
handlerThread.start();
handler = new TestHandler(handlerThread.getLooper());
}
@After
public void cleanUp() {
if (Build.VERSION.SDK_INT >= 18) {
handler.getLooper().quitSafely();
} else {
handler.getLooper().quit();
}
}
/** Test potential deadlock for calls between controller and session. */
@Test
public void deadlock() throws Exception {
HandlerThread testThread = new HandlerThread("deadlock");
testThread.start();
TestHandler testHandler = new TestHandler(testThread.getLooper());
AtomicReference<MediaSession> sessionRef = new AtomicReference<>();
AtomicReference<MediaController> controllerRef = new AtomicReference<>();
try {
MockPlayer player =
new MockPlayer.Builder().setApplicationLooper(testThread.getLooper()).build();
handler.postAndSync(
() ->
sessionRef.set(new MediaSession.Builder(context, player).setId("deadlock").build()));
controllerRef.set(createController(sessionRef.get().getToken(), testThread.getLooper()));
// This may hang if deadlock happens.
testHandler.postAndSync(
() -> {
int state = STATE_IDLE;
MediaController controller = controllerRef.get();
for (int i = 0; i < 100; i++) {
// triggers call from session to controller.
player.notifyPlaybackStateChanged(state);
// triggers call from controller to session.
controller.play();
// Repeat above
player.notifyPlaybackStateChanged(state);
controller.pause();
player.notifyPlaybackStateChanged(state);
controller.seekTo(0);
player.notifyPlaybackStateChanged(state);
controller.next();
player.notifyPlaybackStateChanged(state);
controller.previous();
}
},
LONG_TIMEOUT_MS);
} finally {
testHandler.postAndSync(
() -> {
if (controllerRef.get() != null) {
controllerRef.get().release();
controllerRef.set(null);
}
});
handler.postAndSync(
() -> {
// Clean up here because sessionHandler will be removed afterwards.
if (sessionRef.get() != null) {
sessionRef.get().release();
}
});
if (Build.VERSION.SDK_INT >= 18) {
testThread.quitSafely();
} else {
testThread.quit();
}
}
}
private MediaController createController(
@NonNull SessionToken token, @NonNull Looper applicationLooper) throws Exception {
CountDownLatch connectedLatch = new CountDownLatch(1);
AtomicReference<MediaController> controller = new AtomicReference<>();
handler.postAndSync(
() -> {
// Create controller on the test handler, for changing MediaBrowserCompat's Handler
// Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
// and commands wouldn't be run if tests codes waits on the test handler.
MediaController.Builder builder =
new MediaController.Builder(context)
.setSessionToken(token)
.setApplicationLooper(applicationLooper)
.setControllerCallback(
new MediaController.ControllerCallback() {
@Override
public void onConnected(MediaController controller) {
connectedLatch.countDown();
}
});
controller.set(builder.build());
});
assertThat(connectedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
return controller.get();
}
}

View File

@ -0,0 +1,349 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.StarRating;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaSession.SessionCallback}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionCallbackTest {
private static final String TAG = "MediaSessionCallbackTest";
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
private Context context;
private MockPlayer player;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
player =
new MockPlayer.Builder()
.setLatchCount(1)
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
}
@Test
public void onPostConnect_afterConnected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaSession.SessionCallback callback =
new MediaSession.SessionCallback() {
@Override
public void onPostConnect(
@NonNull MediaSession session, @NonNull ControllerInfo controller) {
latch.countDown();
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnPostConnect_afterConnected")
.build());
controllerTestRule.createRemoteController(session.getToken());
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onPostConnect_afterConnectionRejected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaSession.SessionCallback callback =
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
return null;
}
@Override
public void onPostConnect(
@NonNull MediaSession session, @NonNull ControllerInfo controller) {
latch.countDown();
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnPostConnect_afterConnectionRejected")
.build());
controllerTestRule.createRemoteController(session.getToken());
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
@Test
public void onCommandRequest() throws Exception {
MockOnCommandCallback callback = new MockOnCommandCallback();
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnCommandRequest")
.build());
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
controller.prepare();
assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
assertThat(player.prepareCalled).isFalse();
assertThat(callback.commands).hasSize(1);
assertThat(callback.commands.get(0)).isEqualTo(Player.COMMAND_PREPARE_STOP);
controller.play();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.playCalled).isTrue();
assertThat(player.prepareCalled).isFalse();
assertThat(callback.commands).hasSize(2);
assertThat(callback.commands.get(1)).isEqualTo(Player.COMMAND_PLAY_PAUSE);
}
@Test
public void onCustomCommand() throws Exception {
// TODO(jaewan): Need to revisit with the permission.
SessionCommand testCommand = new SessionCommand("testCustomCommand", null);
Bundle testArgs = new Bundle();
testArgs.putString("args", "testOnCustomCommand");
CountDownLatch latch = new CountDownLatch(1);
MediaSession.SessionCallback callback =
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
SessionCommands commands =
new SessionCommands.Builder()
.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
.add(testCommand)
.build();
return new MediaSession.ConnectResult(commands, Player.Commands.EMPTY);
}
@Override
@NonNull
public ListenableFuture<SessionResult> onCustomCommand(
@NonNull MediaSession session,
@NonNull MediaSession.ControllerInfo controller,
@NonNull SessionCommand sessionCommand,
Bundle args) {
assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(sessionCommand).isEqualTo(testCommand);
assertThat(TestUtils.equals(testArgs, args)).isTrue();
latch.countDown();
return new SessionResult(RESULT_SUCCESS).asFuture();
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnCustomCommand")
.build());
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onSetMediaUri() throws Exception {
Uri testUri = Uri.parse("foo://boo");
Bundle testExtras = TestUtils.createTestBundle();
CountDownLatch latch = new CountDownLatch(1);
MediaSession.SessionCallback callback =
new MediaSession.SessionCallback() {
@Override
public int onSetMediaUri(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@NonNull Uri uri,
@Nullable Bundle extras) {
assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(uri).isEqualTo(testUri);
assertThat(TestUtils.equals(testExtras, extras)).isTrue();
latch.countDown();
return RESULT_SUCCESS;
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnSetMediaUri")
.build());
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
controller.setMediaUri(testUri, testExtras);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onSetRating() throws Exception {
float ratingValue = 3.5f;
Rating testRating = new StarRating(5, ratingValue);
String testMediaId = "media_id";
CountDownLatch latch = new CountDownLatch(1);
MediaSession.SessionCallback callback =
new MediaSession.SessionCallback() {
@Override
@NonNull
public ListenableFuture<SessionResult> onSetRating(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@NonNull String mediaId,
@NonNull Rating rating) {
assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(mediaId).isEqualTo(testMediaId);
assertThat(rating).isEqualTo(testRating);
latch.countDown();
return new SessionResult(RESULT_SUCCESS).asFuture();
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(callback)
.setId("testOnSetRating")
.build());
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
SessionResult result = controller.setRating(testMediaId, testRating);
assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void onConnect() throws Exception {
AtomicReference<Bundle> connectionHints = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setId("testOnConnect")
.setSessionCallback(
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
// TODO: Get uid of client app's and compare.
if (!SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
return null;
}
connectionHints.set(controller.getConnectionHints());
latch.countDown();
return super.onConnect(session, controller);
}
})
.build());
Bundle testConnectionHints = new Bundle();
testConnectionHints.putString("test_key", "test_value");
controllerTestRule.createRemoteController(
session.getToken(), /* waitForConnection= */ false, testConnectionHints);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
}
@Test
public void onDisconnected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setId("testOnDisconnected")
.setSessionCallback(
new MediaSession.SessionCallback() {
@Override
public void onDisconnected(
@NonNull MediaSession session, @NonNull ControllerInfo controller) {
assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
// TODO: Get uid of client app's and compare.
latch.countDown();
}
})
.build());
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
controller.release();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
static class MockOnCommandCallback extends MediaSession.SessionCallback {
public final ArrayList<Integer> commands = new ArrayList<>();
@Override
public int onPlayerCommandRequest(
@NonNull MediaSession session,
@NonNull ControllerInfo controllerInfo,
@Player.Command int command) {
// TODO: Get uid of client app's and compare.
assertThat(controllerInfo.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(controllerInfo.isTrusted()).isFalse();
commands.add(command);
if (command == Player.COMMAND_PREPARE_STOP) {
return RESULT_ERROR_INVALID_STATE;
}
return RESULT_SUCCESS;
}
}
}

View File

@ -0,0 +1,215 @@
/*
* 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.google.android.exoplayer2.session;
import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.R;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for key event handling of {@link MediaSession}. In order to get the media key events, the
* player state is set to 'Playing' before every test method.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) // For AudioManager#dispatchMediaKeyEvent()
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionKeyEventTest {
private static String expectedControllerPackageName;
static {
if (Build.VERSION.SDK_INT >= 28 || Build.VERSION.SDK_INT < 21) {
expectedControllerPackageName = SUPPORT_APP_PACKAGE_NAME;
} else if (Build.VERSION.SDK_INT >= 24) {
// KeyEvent from system service has the package name "android".
expectedControllerPackageName = "android";
} else {
// In API 21+, MediaSessionCompat#getCurrentControllerInfo always returns fake info.
expectedControllerPackageName = LEGACY_CONTROLLER;
}
}
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaSessionKeyEventTest");
// Intentionally member variable to prevent GC while playback is running.
// Should be only used on the sHandler.
private MediaPlayer mediaPlayer;
private AudioManager audioManager;
private TestHandler handler;
private MediaSession session;
private MockPlayer player;
private TestSessionCallback sessionCallback;
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
handler = threadTestRule.getHandler();
player =
new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
sessionCallback = new TestSessionCallback();
session = new MediaSession.Builder(context, player).setSessionCallback(sessionCallback).build();
// Here's the requirement for an app to receive media key events via MediaSession.
// - SDK < 26: Player should be playing for receiving key events
// - SDK >= 26: Play a media item in the same process of the session for receiving key events.
handler.postAndSync(() -> player.notifyIsPlayingChanged(/* isPlaying= */ true));
if (Build.VERSION.SDK_INT >= 26) {
CountDownLatch latch = new CountDownLatch(1);
handler.postAndSync(
() -> {
// Pick the shortest media to finish within the timeout.
mediaPlayer = MediaPlayer.create(context, R.raw.camera_click);
mediaPlayer.setOnCompletionListener(
player -> {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
latch.countDown();
}
});
mediaPlayer.start();
});
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
}
}
@After
public void cleanUp() throws Exception {
handler.postAndSync(
() -> {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
});
session.release();
}
private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) {
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
if (doubleTap) {
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
}
@Test
public void playKeyEvent() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.playCalled).isTrue();
}
@Test
public void pauseKeyEvent() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.pauseCalled).isTrue();
}
@Test
public void nextKeyEvent() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.nextCalled).isTrue();
}
@Test
public void previousKeyEvent() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.previousCalled).isTrue();
}
@Test
public void stopKeyEvent() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.stopCalled).isTrue();
}
@Test
public void playPauseKeyEvent_play() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.playCalled).isTrue();
}
@Test
public void playPauseKeyEvent_pause() throws Exception {
handler.postAndSync(
() -> {
player.playWhenReady = true;
});
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.pauseCalled).isTrue();
}
@Test
public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Exception {
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.nextCalled).isTrue();
assertThat(player.playCalled).isFalse();
assertThat(player.pauseCalled).isFalse();
}
private static class TestSessionCallback extends MediaSession.SessionCallback {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(MediaSession session, ControllerInfo controller) {
if (expectedControllerPackageName.equals(controller.getPackageName())) {
return super.onConnect(session, controller);
}
return null;
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import android.content.ComponentName;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import java.util.Set;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaSessionManager}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MediaSessionManagerTest {
private static final ComponentName MOCK_BROWSER_SERVICE_COMPAT_NAME =
new ComponentName(
SUPPORT_APP_PACKAGE_NAME, MockMediaBrowserServiceCompat.class.getCanonicalName());
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private Context context;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
}
@Test
public void getSessionServiceTokens() {
boolean hasMockBrowserServiceCompat = false;
boolean hasMockSessionService2 = false;
boolean hasMockLibraryService2 = false;
MediaSessionManager sessionManager = MediaSessionManager.getInstance(context);
Set<SessionToken> serviceTokens = sessionManager.getSessionServiceTokens();
for (SessionToken token : serviceTokens) {
ComponentName componentName = token.getComponentName();
if (MOCK_BROWSER_SERVICE_COMPAT_NAME.equals(componentName)) {
hasMockBrowserServiceCompat = true;
} else if (MOCK_MEDIA2_SESSION_SERVICE.equals(componentName)) {
hasMockSessionService2 = true;
} else if (MOCK_MEDIA2_LIBRARY_SERVICE.equals(componentName)) {
hasMockLibraryService2 = true;
}
}
assertThat(hasMockBrowserServiceCompat).isTrue();
assertThat(hasMockSessionService2).isTrue();
assertThat(hasMockLibraryService2).isTrue();
}
}

View File

@ -0,0 +1,409 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS;
import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWith;
import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWithout;
import static com.google.android.exoplayer2.session.SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI;
import static com.google.android.exoplayer2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.StarRating;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for permission handling of {@link MediaSession}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionPermissionTest {
private static final String SESSION_ID = "MediaSessionTest_permission";
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaSessionPermissionTest");
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
private Context context;
private MockPlayer player;
private MediaSession session;
private MySessionCallback callback;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
}
@After
public void cleanUp() {
if (session != null) {
session.release();
session = null;
}
player = null;
callback = null;
}
private void createSessionWithAvailableCommands(
SessionCommands sessionCommands, Player.Commands playerCommands) {
player =
new MockPlayer.Builder()
.setLatchCount(1)
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
callback =
new MySessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
if (!TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) {
return null;
}
return new MediaSession.ConnectResult(sessionCommands, playerCommands);
}
};
if (this.session != null) {
this.session.release();
}
this.session =
new MediaSession.Builder(context, player)
.setId(SESSION_ID)
.setSessionCallback(callback)
.build();
}
private SessionCommands createSessionCommandsWith(SessionCommand command) {
return new SessionCommands.Builder().add(command).build();
}
private void testOnCommandRequest(int commandCode, PermissionTestTask runnable) throws Exception {
createSessionWithAvailableCommands(
SessionCommands.EMPTY, createPlayerCommandsWith(commandCode));
runnable.run(controllerTestRule.createRemoteController(session.getToken()));
assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callback.onCommandRequestCalled).isTrue();
assertThat(callback.command).isEqualTo(commandCode);
createSessionWithAvailableCommands(
SessionCommands.EMPTY, createPlayerCommandsWithout(commandCode));
runnable.run(controllerTestRule.createRemoteController(session.getToken()));
assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
assertThat(callback.onCommandRequestCalled).isFalse();
}
@Test
public void play() throws Exception {
testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::play);
}
@Test
public void pause() throws Exception {
testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::pause);
}
@Test
public void seekTo() throws Exception {
long position = 10;
testOnCommandRequest(
COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, controller -> controller.seekTo(position));
}
@Test
public void seekToNextMediaItem() throws Exception {
testOnCommandRequest(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, RemoteMediaController::next);
}
@Test
public void seekToPreviousMediaItem() throws Exception {
testOnCommandRequest(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, RemoteMediaController::previous);
}
@Test
public void setPlaylistMetadata() throws Exception {
testOnCommandRequest(
COMMAND_SET_MEDIA_ITEMS_METADATA,
controller -> controller.setPlaylistMetadata(MediaMetadata.EMPTY));
}
@Test
public void setMediaItems() throws Exception {
testOnCommandRequest(
COMMAND_CHANGE_MEDIA_ITEMS,
controller -> controller.setMediaItems(Collections.emptyList()));
}
@Test
public void addMediaItems() throws Exception {
testOnCommandRequest(
COMMAND_CHANGE_MEDIA_ITEMS,
controller -> controller.addMediaItems(/* index= */ 0, Collections.emptyList()));
}
@Test
public void removeMediaItems() throws Exception {
testOnCommandRequest(
COMMAND_CHANGE_MEDIA_ITEMS,
controller -> controller.removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1));
}
@Test
public void setDeviceVolume() throws Exception {
testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceVolume(0));
}
@Test
public void increaseDeviceVolume() throws Exception {
testOnCommandRequest(COMMAND_ADJUST_DEVICE_VOLUME, RemoteMediaController::increaseDeviceVolume);
}
@Test
public void decreaseDeviceVolume() throws Exception {
testOnCommandRequest(COMMAND_ADJUST_DEVICE_VOLUME, RemoteMediaController::decreaseDeviceVolume);
}
@Test
public void setDeviceMuted() throws Exception {
testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceMuted(true));
}
@Test
public void setMediaUri() throws Exception {
Uri uri = Uri.parse("media://uri");
createSessionWithAvailableCommands(
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
Player.Commands.EMPTY);
controllerTestRule
.createRemoteController(session.getToken())
.setMediaUri(uri, /* extras= */ null);
assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callback.onSetMediaUriCalled).isTrue();
assertThat(callback.uri).isEqualTo(uri);
assertThat(callback.extras).isNull();
createSessionWithAvailableCommands(
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
Player.Commands.EMPTY);
controllerTestRule
.createRemoteController(session.getToken())
.setMediaUri(uri, /* extras= */ null);
assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
assertThat(callback.onSetMediaUriCalled).isFalse();
}
@Test
public void setRating() throws Exception {
String mediaId = "testSetRating";
Rating rating = new StarRating(5, 3.5f);
createSessionWithAvailableCommands(
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
Player.Commands.EMPTY);
controllerTestRule.createRemoteController(session.getToken()).setRating(mediaId, rating);
assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callback.onSetRatingCalled).isTrue();
assertThat(callback.mediaId).isEqualTo(mediaId);
assertThat(callback.rating).isEqualTo(rating);
createSessionWithAvailableCommands(
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
Player.Commands.EMPTY);
controllerTestRule.createRemoteController(session.getToken()).setRating(mediaId, rating);
assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
assertThat(callback.onSetRatingCalled).isFalse();
}
@Test
public void changingPermissionForSessionCommandWithSetAvailableCommands() throws Exception {
String mediaId = "testSetRating";
Rating rating = new StarRating(5, 3.5f);
createSessionWithAvailableCommands(
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
Player.Commands.EMPTY);
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
controller.setRating(mediaId, rating);
assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callback.onSetRatingCalled).isTrue();
callback.reset();
// Change allowed commands.
session.setAvailableCommands(
getTestControllerInfo(),
createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
Player.Commands.EMPTY);
controller.setRating(mediaId, rating);
assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
@Test
public void changingPermissionForPlayerCommandWithSetAvailableCommands() throws Exception {
int playPauseCommand = COMMAND_PLAY_PAUSE;
Player.Commands commandsWithPlayPause = createPlayerCommandsWith(playPauseCommand);
Player.Commands commandsWithoutPlayPause = createPlayerCommandsWithout(playPauseCommand);
// Create session with play/pause command.
createSessionWithAvailableCommands(SessionCommands.EMPTY, commandsWithPlayPause);
// Create player with play/pause command.
player.commands = commandsWithPlayPause;
player.notifyAvailableCommandsChanged(commandsWithPlayPause);
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
controller.play();
assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(callback.onCommandRequestCalled).isTrue();
assertThat(callback.command).isEqualTo(playPauseCommand);
callback.reset();
// Change session to not have play/pause command.
session.setAvailableCommands(
getTestControllerInfo(), SessionCommands.EMPTY, commandsWithoutPlayPause);
controller.play();
assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
assertThat(callback.onCommandRequestCalled).isFalse();
}
private ControllerInfo getTestControllerInfo() {
List<ControllerInfo> controllers = session.getConnectedControllers();
assertThat(controllers).isNotNull();
for (int i = 0; i < controllers.size(); i++) {
if (TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controllers.get(i).getPackageName())) {
return controllers.get(i);
}
}
throw new IllegalStateException("Failed to get test controller info");
}
/* @FunctionalInterface */
private interface PermissionTestTask {
void run(@NonNull RemoteMediaController controller) throws Exception;
}
private static class MySessionCallback extends MediaSession.SessionCallback {
public CountDownLatch countDownLatch;
public @Player.Command int command;
public String mediaId;
public Uri uri;
public Bundle extras;
public Rating rating;
public boolean onCommandRequestCalled;
public boolean onSetMediaUriCalled;
public boolean onSetRatingCalled;
public MySessionCallback() {
countDownLatch = new CountDownLatch(1);
}
public void reset() {
countDownLatch = new CountDownLatch(1);
mediaId = null;
onCommandRequestCalled = false;
onSetMediaUriCalled = false;
onSetRatingCalled = false;
}
@Override
public int onPlayerCommandRequest(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@Player.Command int command) {
assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
onCommandRequestCalled = true;
this.command = command;
countDownLatch.countDown();
return super.onPlayerCommandRequest(session, controller, command);
}
@Override
public int onSetMediaUri(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@NonNull Uri uri,
@Nullable Bundle extras) {
assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
onSetMediaUriCalled = true;
this.uri = uri;
this.extras = extras;
countDownLatch.countDown();
return RESULT_SUCCESS;
}
@Override
@NonNull
public ListenableFuture<SessionResult> onSetRating(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@NonNull String mediaId,
@NonNull Rating rating) {
assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
onSetRatingCalled = true;
this.mediaId = mediaId;
this.rating = rating;
countDownLatch.countDown();
return new SessionResult(RESULT_SUCCESS).asFuture();
}
}
}

View File

@ -0,0 +1,410 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for the underlying {@link SessionPlayer} of {@link MediaSession}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionPlayerTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaSessionPlayerTest");
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
private MediaSession session;
private MockPlayer player;
private RemoteMediaController controller;
@Before
public void setUp() throws Exception {
player =
new MockPlayer.Builder()
.setLatchCount(1)
.setApplicationLooper(threadTestRule.getHandler().getLooper())
.build();
session =
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
.setSessionCallback(
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, MediaSession.ControllerInfo controller) {
if (SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
return super.onConnect(session, controller);
}
return null;
}
})
.build();
// Create a default MediaController in client app.
controller = controllerTestRule.createRemoteController(session.getToken());
}
@After
public void cleanUp() {
if (session != null) {
session.release();
}
}
@Test
public void play_isCalledByController() throws Exception {
controller.play();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.playCalled).isTrue();
}
@Test
public void pause_isCalledByController() throws Exception {
controller.pause();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.pauseCalled).isTrue();
}
@Test
public void prepare_isCalledByController() throws Exception {
controller.prepare();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.prepareCalled).isTrue();
}
@Test
public void stop_isCalledByController() throws Exception {
controller.stop();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.stopCalled).isTrue();
}
@Test
public void setPlayWhenReady_isCalledByController() throws Exception {
boolean testPlayWhenReady = true;
controller.setPlayWhenReady(testPlayWhenReady);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setPlayWhenReadyCalled).isTrue();
assertThat(player.playWhenReady).isEqualTo(testPlayWhenReady);
}
@Test
public void seekToDefaultPosition_isCalledByController() throws Exception {
controller.seekToDefaultPosition();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.seekToDefaultPositionCalled).isTrue();
}
@Test
public void seekToDefaultPosition_withWindowIndex_isCalledByController() throws Exception {
int windowIndex = 33;
controller.seekToDefaultPosition(windowIndex);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.seekToDefaultPositionWithWindowIndexCalled).isTrue();
assertThat(player.seekWindowIndex).isEqualTo(windowIndex);
}
@Test
public void seekTo_isCalledByController() throws Exception {
long seekPositionMs = 12125L;
controller.seekTo(seekPositionMs);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.seekToCalled).isTrue();
assertThat(player.seekPositionMs).isEqualTo(seekPositionMs);
}
@Test
public void seekTo_withWindowIndex_isCalledByController() throws Exception {
int windowIndex = 33;
long seekPositionMs = 12125L;
controller.seekTo(windowIndex, seekPositionMs);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.seekToWithWindowIndexCalled).isTrue();
assertThat(player.seekWindowIndex).isEqualTo(windowIndex);
assertThat(player.seekPositionMs).isEqualTo(seekPositionMs);
}
@Test
public void setPlaybackSpeed_isCalledByController() throws Exception {
float testSpeed = 1.5f;
controller.setPlaybackSpeed(testSpeed);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.playbackParameters.speed).isEqualTo(testSpeed);
}
@Test
public void setPlaybackParameters_isCalledByController() throws Exception {
PlaybackParameters testPlaybackParameters =
new PlaybackParameters(/* speed= */ 1.4f, /* pitch= */ 2.3f);
controller.setPlaybackParameters(testPlaybackParameters);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setPlaybackParametersCalled).isTrue();
assertThat(player.playbackParameters).isEqualTo(testPlaybackParameters);
}
@Test
public void setPlaybackParameters_withDefault_isCalledByController() throws Exception {
controller.setPlaybackParameters(PlaybackParameters.DEFAULT);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setPlaybackParametersCalled).isTrue();
assertThat(player.playbackParameters).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
public void setMediaItems_withResetPosition_isCalledByController() throws Exception {
List<MediaItem> items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
controller.setMediaItems(items, /* resetPosition= */ true);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isEqualTo(items);
assertThat(player.resetPosition).isTrue();
}
@Test
public void setMediaItems_withoutResetPosition_isCalledByController() throws Exception {
List<MediaItem> items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
controller.setMediaItems(items, /* resetPosition= */ false);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isEqualTo(items);
assertThat(player.resetPosition).isFalse();
}
@Test
public void setMediaItems_withStartWindowAndPosition_isCalledByController() throws Exception {
List<MediaItem> items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
int startWindowIndex = 1;
long startPositionMs = 1234;
controller.setMediaItems(items, startWindowIndex, startPositionMs);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isEqualTo(items);
assertThat(player.startWindowIndex).isEqualTo(startWindowIndex);
assertThat(player.startPositionMs).isEqualTo(startPositionMs);
}
@Test
public void setMediaItems_withDuplicatedItems_isCalledByController() throws Exception {
int listSize = 4;
List<MediaItem> list = MediaTestUtils.createConvergedMediaItems(listSize);
list.set(2, list.get(1));
controller.setMediaItems(list);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems.size()).isEqualTo(listSize);
for (int i = 0; i < listSize; i++) {
assertThat(player.mediaItems.get(i).mediaId).isEqualTo(list.get(i).mediaId);
}
}
@Test
public void setMediaItems_withLongPlaylist_isCalledByController() throws Exception {
int listSize = 5000;
// Make client app to generate a long list, and call setMediaItems() with it.
controller.createAndSetFakeMediaItems(listSize);
assertThat(player.countDownLatch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isNotNull();
assertThat(player.mediaItems.size()).isEqualTo(listSize);
for (int i = 0; i < listSize; i++) {
// Each item's media ID will be same as its index.
assertThat(player.mediaItems.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
}
}
@Test
public void setPlaylistMetadata_isCalledByController() throws Exception {
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
controller.setPlaylistMetadata(playlistMetadata);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setPlaylistMetadataCalled).isTrue();
assertThat(player.playlistMetadata).isEqualTo(playlistMetadata);
}
@Test
public void addMediaItems_isCalledByController() throws Exception {
int index = 1;
int size = 2;
List<MediaItem> mediaItems = MediaTestUtils.createConvergedMediaItems(size);
controller.addMediaItems(index, mediaItems);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.addMediaItemsCalled).isTrue();
assertThat(player.index).isEqualTo(index);
assertThat(player.mediaItems).isEqualTo(mediaItems);
}
@Test
public void removeMediaItems_isCalledByController() throws Exception {
int fromIndex = 1;
int toIndex = 3;
controller.removeMediaItems(fromIndex, toIndex);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.removeMediaItemsCalled).isTrue();
assertThat(player.fromIndex).isEqualTo(fromIndex);
assertThat(player.toIndex).isEqualTo(toIndex);
}
@Test
public void moveMediaItems_isCalledByController() throws Exception {
int fromIndex = 1;
int toIndex = 2;
int newIndex = 3;
controller.moveMediaItems(fromIndex, toIndex, newIndex);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.moveMediaItemsCalled).isTrue();
assertThat(player.fromIndex).isEqualTo(fromIndex);
assertThat(player.toIndex).isEqualTo(toIndex);
assertThat(player.newIndex).isEqualTo(newIndex);
}
@Test
public void skipToPreviousItem_isCalledByController() throws Exception {
controller.previous();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.previousCalled).isTrue();
}
@Test
public void skipToNextItem_isCalledByController() throws Exception {
controller.next();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.nextCalled).isTrue();
}
@Test
public void setShuffleModeEnabled_isCalledByController() throws Exception {
boolean testShuffleModeEnabled = true;
controller.setShuffleModeEnabled(testShuffleModeEnabled);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setShuffleModeCalled).isTrue();
assertThat(player.shuffleModeEnabled).isEqualTo(testShuffleModeEnabled);
}
@Test
public void setRepeatMode_isCalledByController() throws Exception {
int testRepeatMode = Player.REPEAT_MODE_ALL;
controller.setRepeatMode(testRepeatMode);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setRepeatModeCalled).isTrue();
assertThat(player.repeatMode).isEqualTo(testRepeatMode);
}
@Test
public void setVolume_isCalledByController() throws Exception {
float testVolume = .123f;
controller.setVolume(testVolume);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setVolumeCalled).isTrue();
assertThat(player.volume).isEqualTo(testVolume);
}
@Test
public void setDeviceVolume_isCalledByController() throws Exception {
changePlaybackTypeToRemote();
int testVolume = 12;
controller.setDeviceVolume(testVolume);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setDeviceVolumeCalled).isTrue();
assertThat(player.deviceVolume).isEqualTo(testVolume);
}
@Test
public void increaseDeviceVolume_isCalledByController() throws Exception {
changePlaybackTypeToRemote();
controller.increaseDeviceVolume();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.increaseDeviceVolumeCalled).isTrue();
}
@Test
public void decreaseDeviceVolume_isCalledByController() throws Exception {
changePlaybackTypeToRemote();
controller.decreaseDeviceVolume();
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.decreaseDeviceVolumeCalled).isTrue();
}
@Test
public void setDeviceMuted_isCalledByController() throws Exception {
player.deviceMuted = false;
controller.setDeviceMuted(true);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.setDeviceMutedCalled).isTrue();
assertThat(player.deviceMuted).isTrue();
}
private void changePlaybackTypeToRemote() throws Exception {
threadTestRule
.getHandler()
.postAndSync(
() -> {
player.deviceInfo =
new DeviceInfo(
DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
player.notifyDeviceInfoChanged();
});
}
}

View File

@ -0,0 +1,180 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.STATE_READY;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.R;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Manual test of {@link MediaSessionService} for showing/removing notification when the playback is
* started/ended.
*
* <p>This test is a manual test, which means the one who runs this test should keep looking at the
* device and check whether the notification is shown/removed.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionServiceNotificationTest {
private static final long NOTIFICATION_SHOW_TIME_MS = 15000;
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaSessionServiceNotificationTest");
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
private Context context;
private MockPlayer player;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
}
@Test
@Ignore("Comment out this line and manually run the test.")
public void notification() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaLibrarySessionCallback sessionCallback =
new MediaLibrarySessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
if (SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
player =
new MockPlayer.Builder()
.setApplicationLooper(Looper.myLooper())
.setChangePlayerStateWithTransportControl(true)
.build();
session.setPlayer(player);
latch.countDown();
}
return super.onConnect(session, controller);
}
};
TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
// Create a controller to start the service.
controllerTestRule.createRemoteController(
new SessionToken(context, MOCK_MEDIA2_SESSION_SERVICE),
/* waitForConnection= */ true,
/* connectionHints= */ null);
assertThat(latch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
TestHandler handler = new TestHandler(player.getApplicationLooper());
handler.postAndSync(
() -> {
// Set current media item.
String mediaId = "testMediaId";
String title = "Test Song Name";
Bitmap albumArt =
BitmapFactory.decodeResource(context.getResources(), R.drawable.big_buck_bunny);
// TODO(b/180293668): Set artist, album art, browsable type, playable.
MediaMetadata metadata = new MediaMetadata.Builder().setTitle(title).build();
player.currentMediaItem =
new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(metadata).build();
// Notification should be shown. Clicking play/pause button will change the player state.
// When playing, the notification will not be removed by swiping horizontally.
// When paused, the notification can be swiped away.
player.notifyPlaybackStateChanged(STATE_READY);
});
Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
}
@Test
@Ignore("Comment out this line and manually run the test.")
public void notificationUpdatedWhenCurrentMediaItemChanged() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaLibrarySessionCallback sessionCallback =
new MediaLibrarySessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
if (SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
session.setPlayer(player);
latch.countDown();
}
return super.onConnect(session, controller);
}
};
TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
// Create a controller to start the service.
controllerTestRule.createRemoteController(
new SessionToken(context, MOCK_MEDIA2_SESSION_SERVICE),
/* waitForConnection= */ true,
/* connectionHints= */ null);
// Set current media item.
Bitmap albumArt =
BitmapFactory.decodeResource(context.getResources(), R.drawable.big_buck_bunny);
// TODO(b/180293668): Set artist, album art, browsable type, playable.
MediaMetadata metadata = new MediaMetadata.Builder().setTitle("Test Song Name").build();
player.currentMediaItem =
new MediaItem.Builder().setMediaId("testMediaId").setMediaMetadata(metadata).build();
player.notifyPlaybackStateChanged(STATE_READY);
// At this point, the notification should be shown.
Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
// Set a new media item. (current media item is changed)
// TODO(b/180293668): Set artist, album art, browsable type, playable.
MediaMetadata newMetadata = new MediaMetadata.Builder().setTitle("New Song Name").build();
MediaItem newItem =
new MediaItem.Builder().setMediaId("New media ID").setMediaMetadata(newMetadata).build();
player.currentMediaItem = newItem;
// Calling this should update the notification with the new metadata.
player.notifyMediaItemTransition(newItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
}
}

View File

@ -0,0 +1,335 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaSessionService}. */
@RunWith(AndroidJUnit4.class)
@MediumTest
public class MediaSessionServiceTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("MediaSessionServiceTest");
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
private Context context;
private Looper looper;
private SessionToken token;
@Before
public void setUp() {
TestServiceRegistry.getInstance().cleanUp();
context = ApplicationProvider.getApplicationContext();
looper = threadTestRule.getHandler().getLooper();
token =
new SessionToken(context, new ComponentName(context, LocalMockMediaSessionService.class));
}
@After
public void cleanUp() {
TestServiceRegistry.getInstance().cleanUp();
}
/**
* Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} is called when
* controller tries to connect, with the proper arguments.
*/
@Test
public void onGetSessionIsCalled() throws Exception {
List<ControllerInfo> controllerInfoList = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance()
.setOnGetSessionHandler(
new TestServiceRegistry.OnGetSessionHandler() {
@Override
public MediaSession onGetSession(ControllerInfo controllerInfo) {
controllerInfoList.add(controllerInfo);
latch.countDown();
return null;
}
});
Bundle testHints = new Bundle();
testHints.putString("test_key", "test_value");
controllerTestRule.createRemoteController(token, /* waitForConnection= */ false, testHints);
// onGetSession() should be called.
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(controllerInfoList.get(0).getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints))
.isTrue();
}
/**
* Tests whether the controller is connected to the session which is returned from {@link
* MediaSessionService#onGetSession(ControllerInfo)}. Also checks whether the connection hints are
* properly passed to {@link MediaSession.SessionCallback#onConnect(MediaSession,
* ControllerInfo)}.
*/
@Test
public void onGetSession_returnsSession() throws Exception {
List<ControllerInfo> controllerInfoList = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
MediaSession testSession =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(
context, new MockPlayer.Builder().setApplicationLooper(looper).build())
.setId("testOnGetSession_returnsSession")
.setSessionCallback(
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
controllerInfoList.add(controller);
latch.countDown();
return new MediaSession.ConnectResult();
}
})
.build());
TestServiceRegistry.getInstance()
.setOnGetSessionHandler(
new TestServiceRegistry.OnGetSessionHandler() {
@Override
public MediaSession onGetSession(ControllerInfo controllerInfo) {
return testSession;
}
});
Bundle testHints = new Bundle();
testHints.putString("test_key", "test_value");
RemoteMediaController controller =
controllerTestRule.createRemoteController(token, true, testHints);
// MediaSession.SessionCallback#onConnect() should be called.
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(controllerInfoList.get(0).getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints))
.isTrue();
// The controller should be connected to the right session.
assertThat(controller.getConnectedSessionToken()).isNotEqualTo(token);
assertThat(controller.getConnectedSessionToken()).isEqualTo(testSession.getToken());
}
/**
* Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} can return different
* sessions for different controllers.
*/
@Test
public void onGetSession_returnsDifferentSessions() throws Exception {
List<SessionToken> tokens = new ArrayList<>();
TestServiceRegistry.getInstance()
.setOnGetSessionHandler(
new TestServiceRegistry.OnGetSessionHandler() {
@Override
public MediaSession onGetSession(ControllerInfo controllerInfo) {
MediaSession session =
createMediaSession(
"testOnGetSession_returnsDifferentSessions" + System.currentTimeMillis());
tokens.add(session.getToken());
return session;
}
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
assertThat(controller2.getConnectedSessionToken())
.isNotEqualTo(controller1.getConnectedSessionToken());
assertThat(controller1.getConnectedSessionToken()).isEqualTo(tokens.get(0));
assertThat(controller2.getConnectedSessionToken()).isEqualTo(tokens.get(1));
}
/**
* Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} can reject incoming
* connection by returning null.
*/
@Test
public void onGetSession_rejectsConnection() throws Exception {
TestServiceRegistry.getInstance()
.setOnGetSessionHandler(
new TestServiceRegistry.OnGetSessionHandler() {
@Override
public MediaSession onGetSession(ControllerInfo controllerInfo) {
return null;
}
});
CountDownLatch latch = new CountDownLatch(1);
MediaController controller =
new MediaController.Builder(context)
.setSessionToken(token)
.setControllerCallback(
new MediaController.ControllerCallback() {
@Override
public void onDisconnected(@NonNull MediaController controller) {
latch.countDown();
}
})
.setApplicationLooper(looper)
.build();
// MediaController2.ControllerCallback#onDisconnected() should be called.
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(controller.getConnectedToken()).isNull();
threadTestRule.getHandler().postAndSync(controller::release);
}
@Test
public void allControllersDisconnected_oneSession() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance()
.setSessionServiceCallback(
new TestServiceRegistry.SessionServiceCallback() {
@Override
public void onCreated() {
// no-op
}
@Override
public void onDestroyed() {
latch.countDown();
}
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
controller1.release();
controller2.release();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void allControllersDisconnected_multipleSessions() throws Exception {
TestServiceRegistry.getInstance()
.setOnGetSessionHandler(
new TestServiceRegistry.OnGetSessionHandler() {
@Override
public MediaSession onGetSession(ControllerInfo controllerInfo) {
return createMediaSession(
"testAllControllersDisconnected" + System.currentTimeMillis());
}
});
CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance()
.setSessionServiceCallback(
new TestServiceRegistry.SessionServiceCallback() {
@Override
public void onCreated() {
// no-op
}
@Override
public void onDestroyed() {
latch.countDown();
}
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
controller1.release();
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
// Service should be closed only when all controllers are closed.
controller2.release();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@Test
public void getSessions() throws Exception {
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
MediaSession session = createMediaSession("testGetSessions");
service.addSession(session);
List<MediaSession> sessions = service.getSessions();
assertThat(sessions.contains(session)).isTrue();
assertThat(sessions.size()).isEqualTo(2);
service.removeSession(session);
sessions = service.getSessions();
assertThat(sessions.contains(session)).isFalse();
}
@Test
public void addSessions_removedWhenClose() throws Exception {
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
MediaSession session = createMediaSession("testAddSessions_removedWhenClose");
service.addSession(session);
List<MediaSession> sessions = service.getSessions();
assertThat(sessions.contains(session)).isTrue();
assertThat(sessions.size()).isEqualTo(2);
session.release();
sessions = service.getSessions();
assertThat(sessions.contains(session)).isFalse();
}
private MediaSession createMediaSession(String id) {
return sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(
context, new MockPlayer.Builder().setApplicationLooper(looper).build())
.setId(id)
.build());
}
}

View File

@ -0,0 +1,378 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.Player.STATE_IDLE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.os.Build;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.MediaSessionManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.android.exoplayer2.session.MediaSession.SessionCallback;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.util.Log;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaSession}. */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaSessionTest {
private static final String TAG = "MediaSessionTest";
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
@Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
@Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
private Context context;
private TestHandler handler;
private MediaSession session;
private MockPlayer player;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
handler = threadTestRule.getHandler();
player =
new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setId(TAG)
.setSessionCallback(
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, MediaSession.ControllerInfo controller) {
if (TextUtils.equals(
context.getPackageName(), controller.getPackageName())) {
return super.onConnect(session, controller);
}
return null;
}
})
.build());
}
@Test
public void builder() {
MediaSession.Builder builder;
try {
builder = new MediaSession.Builder(context, null);
assertWithMessage("null player shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaSession.Builder(context, player);
builder.setId(null);
assertWithMessage("null id shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
try {
builder = new MediaSession.Builder(context, player);
builder.setExtras(null);
assertWithMessage("null extras shouldn't be allowed").fail();
} catch (NullPointerException e) {
// expected. pass-through
}
// Empty string as ID is allowed.
new MediaSession.Builder(context, player).setId("").build().release();
}
@Test
public void getPlayer() throws Exception {
assertThat(session.getPlayer()).isEqualTo(player);
}
@Test
public void setPlayer() throws Exception {
MockPlayer player =
new MockPlayer.Builder().setApplicationLooper(this.player.getApplicationLooper()).build();
session.setPlayer(player);
assertThat(session.getPlayer()).isEqualTo(player);
}
@Test
public void setPlayer_withSamePlayerInstance() throws Exception {
session.setPlayer(player);
assertThat(session.getPlayer()).isEqualTo(player);
}
@Test
public void setPlayer_withDifferentLooper_throwsIAE() throws Exception {
MockPlayer player =
new MockPlayer.Builder().setApplicationLooper(Looper.getMainLooper()).build();
try {
session.setPlayer(player);
assertWithMessage("IAE is expected").fail();
} catch (IllegalArgumentException unused) {
// expected
}
}
/** Test potential deadlock for calls between controller and session. */
@Test
@LargeTest
public void deadlock() throws Exception {
handler.postAndSync(
() -> {
session.release();
session = null;
});
HandlerThread testThread = new HandlerThread("deadlock");
testThread.start();
TestHandler testHandler = new TestHandler(testThread.getLooper());
try {
MockPlayer player =
new MockPlayer.Builder().setApplicationLooper(testThread.getLooper()).build();
handler.postAndSync(
() -> {
session = new MediaSession.Builder(context, player).setId("testDeadlock").build();
});
RemoteMediaController controller =
controllerTestRule.createRemoteController(session.getToken());
// This may hang if deadlock happens.
testHandler.postAndSync(
() -> {
int state = STATE_IDLE;
for (int i = 0; i < 100; i++) {
Log.d(TAG, "testDeadlock for-loop started: index=" + i);
long startTime = SystemClock.elapsedRealtime();
// triggers call from session to controller.
player.notifyPlaybackStateChanged(state);
long endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "1) Time spent on API call(ms): " + (endTime - startTime));
// triggers call from controller to session.
startTime = endTime;
controller.play();
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "2) Time spent on API call(ms): " + (endTime - startTime));
// Repeat above
startTime = endTime;
player.notifyPlaybackStateChanged(state);
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "3) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
controller.pause();
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "4) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
player.notifyPlaybackStateChanged(state);
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "5) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
controller.seekTo(0);
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "6) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
player.notifyPlaybackStateChanged(state);
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "7) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
controller.next();
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "8) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
player.notifyPlaybackStateChanged(state);
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "9) Time spent on API call(ms): " + (endTime - startTime));
startTime = endTime;
controller.previous();
endTime = SystemClock.elapsedRealtime();
Log.d(TAG, "10) Time spent on API call(ms): " + (endTime - startTime));
}
},
LONG_TIMEOUT_MS);
} finally {
if (session != null) {
handler.postAndSync(
() -> {
// Clean up here because sessionHandler will be removed afterwards.
session.release();
session = null;
});
}
if (Build.VERSION.SDK_INT >= 18) {
testThread.quitSafely();
} else {
testThread.quit();
}
}
}
@Test
public void creatingTwoSessionWithSameId() {
String sessionId = "testSessionId";
MediaSession session =
new MediaSession.Builder(
context, new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build())
.setId(sessionId)
.build();
MediaSession.Builder builderWithSameId =
new MediaSession.Builder(
context, new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build());
try {
builderWithSameId.setId(sessionId).build();
assertWithMessage(
"Creating a new session with the same ID in a process should not be allowed")
.fail();
} catch (IllegalStateException e) {
// expected. pass-through
}
session.release();
// Creating a new session with ID of the closed session is okay.
MediaSession sessionWithSameId = builderWithSameId.build();
sessionWithSameId.release();
}
@Test
public void sendCustomCommand_onConnect() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
SessionCommand testCommand = new SessionCommand("test", null);
SessionCallback testSessionCallback =
new SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, MediaSession.ControllerInfo controller) {
Future<SessionResult> result = session.sendCustomCommand(controller, testCommand, null);
try {
// The controller is not connected yet.
assertThat(result.get(TIMEOUT_MS, MILLISECONDS).resultCode)
.isEqualTo(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
assertWithMessage("Fail to get result of the returned future.").fail();
}
return super.onConnect(session, controller);
}
@Override
public void onPostConnect(
@NonNull MediaSession session, @NonNull MediaSession.ControllerInfo controller) {
Future<SessionResult> result = session.sendCustomCommand(controller, testCommand, null);
try {
// The controller is connected but doesn't implement onCustomCommand.
assertThat(result.get(TIMEOUT_MS, MILLISECONDS).resultCode)
.isEqualTo(SessionResult.RESULT_ERROR_NOT_SUPPORTED);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
assertWithMessage("Fail to get result of the returned future.").fail();
}
latch.countDown();
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setSessionCallback(testSessionCallback)
.build());
controllerTestRule.createRemoteController(session.getToken());
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
/** Test {@link MediaSession#getSessionCompatToken()}. */
@Test
public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat() throws Exception {
String expectedControllerCompatPackageName =
(21 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 24)
? MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER
: context.getPackageName();
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
.setSessionCallback(
new SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, MediaSession.ControllerInfo controller) {
if (TextUtils.equals(
expectedControllerCompatPackageName, controller.getPackageName())) {
return super.onConnect(session, controller);
}
return null;
}
})
.build());
MediaSessionCompat.Token token = session.getSessionCompatToken();
MediaControllerCompat controllerCompat = new MediaControllerCompat(context, token);
CountDownLatch sessionReadyLatch = new CountDownLatch(1);
controllerCompat.registerCallback(
new MediaControllerCompat.Callback() {
@Override
public void onSessionReady() {
sessionReadyLatch.countDown();
}
},
handler);
assertThat(sessionReadyLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
long testSeekPositionMs = 1234;
controllerCompat.getTransportControls().seekTo(testSeekPositionMs);
assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(player.seekToCalled).isTrue();
assertThat(player.seekPositionMs).isEqualTo(testSeekPositionMs);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/** TestRule for releasing {@link MediaSession} instances after use. */
public class MediaSessionTestRule implements TestRule {
private final List<MediaSession> sessions;
MediaSessionTestRule() {
sessions = new CopyOnWriteArrayList<>();
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} finally {
cleanUpSessions();
}
}
};
}
/** Ensures that release() is called after the test. */
public <T extends MediaSession> T ensureReleaseAfterTest(T session) {
sessions.add(session);
return session;
}
private void cleanUpSessions() {
for (int i = 0; i < sessions.size(); i++) {
sessions.get(i).release();
}
sessions.clear();
}
}

View File

@ -0,0 +1,433 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.service.media.MediaBrowserService;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import androidx.media.AudioAttributesCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.HeartRating;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PercentageRating;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.StarRating;
import com.google.android.exoplayer2.ThumbRating;
import com.google.android.exoplayer2.audio.AudioAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MediaUtils}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class MediaUtilsTest {
private Context context;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
}
@Test
public void convertToBrowserItem() {
String mediaId = "testId";
CharSequence trackTitle = "testTitle";
MediaItem mediaItem =
new MediaItem.Builder()
.setMediaId(mediaId)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(trackTitle).build())
.build();
MediaBrowserCompat.MediaItem browserItem = MediaUtils.convertToBrowserItem(mediaItem);
assertThat(browserItem.getDescription()).isNotNull();
assertThat(browserItem.getDescription().getMediaId()).isEqualTo(mediaId);
assertThat(TextUtils.equals(browserItem.getDescription().getTitle(), trackTitle)).isTrue();
}
@Test
public void convertToMediaItem_browserItemToMediaItem() {
String mediaId = "testId";
String title = "testTitle";
MediaDescriptionCompat descriptionCompat =
new MediaDescriptionCompat.Builder().setMediaId(mediaId).setTitle(title).build();
MediaBrowserCompat.MediaItem browserItem =
new MediaBrowserCompat.MediaItem(descriptionCompat, /* flags= */ 0);
MediaItem mediaItem = MediaUtils.convertToMediaItem(browserItem);
assertThat(mediaItem.mediaId).isEqualTo(mediaId);
assertThat(mediaItem.mediaMetadata.title).isEqualTo(title);
}
@Test
public void convertToMediaItem_queueItemToMediaItem() {
String mediaId = "testMediaId";
String title = "testTitle";
MediaDescriptionCompat descriptionCompat =
new MediaDescriptionCompat.Builder().setMediaId(mediaId).setTitle(title).build();
MediaSessionCompat.QueueItem queueItem =
new MediaSessionCompat.QueueItem(descriptionCompat, /* id= */ 1);
MediaItem mediaItem = MediaUtils.convertToMediaItem(queueItem);
assertThat(mediaItem.mediaId).isEqualTo(mediaId);
assertThat(mediaItem.mediaMetadata.title.toString()).isEqualTo(title);
}
@Test
public void convertToMediaItem_metadataCompatToMediaItem() {
MediaMetadataCompat metadataCompat = null;
LegacyMediaItem mediaItem =
MediaUtils.convertToLegacyMediaItem(metadataCompat, RatingCompat.RATING_3_STARS);
assertThat(mediaItem).isNull();
String mediaId = "testId";
String displaySubtitle = "testDisplaySubtitle";
String displayDescription = "testDisplayDescription";
String title = "testTitle";
String iconUri = "testIconUri";
String mediaUri = "testMediaUri";
metadataCompat =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, displayDescription)
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, iconUri)
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, mediaUri)
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, displaySubtitle)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.build();
mediaItem = MediaUtils.convertToLegacyMediaItem(metadataCompat, RatingCompat.RATING_3_STARS);
assertThat(mediaItem).isNotNull();
assertThat(mediaItem.getMediaId()).isEqualTo(mediaId);
LegacyMediaMetadata metadata = mediaItem.getMetadata();
assertThat(metadata).isNotNull();
assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION))
.isEqualTo(displayDescription);
assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_ICON_URI))
.isEqualTo(iconUri);
assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_MEDIA_URI)).isEqualTo(mediaUri);
assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE))
.isEqualTo(displaySubtitle);
assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_TITLE)).isEqualTo(title);
}
@Test
public void convertToBrowserItemList() {
int size = 3;
List<MediaItem> mediaItems = MediaTestUtils.createConvergedMediaItems(size);
List<MediaBrowserCompat.MediaItem> browserItems =
MediaUtils.convertToBrowserItemList(mediaItems);
assertThat(browserItems).hasSize(size);
for (int i = 0; i < size; ++i) {
assertThat(browserItems.get(i).getMediaId()).isEqualTo(mediaItems.get(i).mediaId);
}
}
@Test
public void convertBrowserItemListToMediaItemList() {
int size = 3;
List<MediaBrowserCompat.MediaItem> browserItems = MediaTestUtils.createBrowserItems(size);
List<MediaItem> mediaItems = MediaUtils.convertBrowserItemListToMediaItemList(browserItems);
assertThat(mediaItems).hasSize(size);
for (int i = 0; i < size; ++i) {
assertThat(mediaItems.get(i).mediaId).isEqualTo(browserItems.get(i).getMediaId());
}
}
@Test
public void convertToMediaItemList_queueItemToMediaItem() {
int size = 3;
List<MediaSessionCompat.QueueItem> queueItems = MediaTestUtils.createQueueItems(size);
List<MediaItem> mediaItems = MediaUtils.convertQueueItemListToMediaItemList(queueItems);
assertThat(mediaItems).hasSize(queueItems.size());
for (int i = 0; i < size; ++i) {
assertThat(mediaItems.get(i).mediaId)
.isEqualTo(queueItems.get(i).getDescription().getMediaId());
}
}
@Test
public void convertToQueueItemList() {
int size = 3;
List<MediaItem> mediaItems = MediaTestUtils.createConvergedMediaItems(size);
List<MediaSessionCompat.QueueItem> queueItems = MediaUtils.convertToQueueItemList(mediaItems);
assertThat(queueItems).hasSize(mediaItems.size());
for (int i = 0; i < size; ++i) {
assertThat(queueItems.get(i).getDescription().getMediaId())
.isEqualTo(mediaItems.get(i).mediaId);
}
}
@Test
public void createMediaDescriptionCompat() {
String mediaId = "testId";
MediaDescriptionCompat descriptionCompat = MediaUtils.createMediaDescriptionCompat(null);
assertThat(descriptionCompat).isNull();
descriptionCompat = MediaUtils.createMediaDescriptionCompat(mediaId);
assertThat(descriptionCompat.getMediaId()).isEqualTo(mediaId);
}
@Test
public void convertToQueueItemId() {
assertThat(MediaUtils.convertToQueueItemId(C.INDEX_UNSET))
.isEqualTo(MediaSessionCompat.QueueItem.UNKNOWN_ID);
assertThat(MediaUtils.convertToQueueItemId(100)).isEqualTo(100);
}
@Test
public void truncateListBySize() {
List<Bundle> bundleList = new ArrayList<>();
Bundle testBundle = new Bundle();
testBundle.putString("key", "value");
Parcel p = Parcel.obtain();
p.writeParcelable(testBundle, 0);
int bundleSize = p.dataSize();
p.recycle();
bundleList.addAll(Collections.nCopies(10, testBundle));
assertThat(MediaUtils.truncateListBySize(null, 1)).isNull();
for (int i = 0; i < 5; i++) {
assertThat(MediaUtils.truncateListBySize(bundleList, bundleSize * i + 1)).hasSize(i);
}
}
@Test
public void convertToMediaMetadata_withoutTitle() {
assertThat(MediaUtils.convertToMediaMetadata(null)).isEqualTo(MediaMetadata.EMPTY);
}
@Test
public void convertToMediaMetadata_withTitle() {
CharSequence title = "title";
assertThat(MediaUtils.convertToMediaMetadata(title).title).isEqualTo(title);
}
@Test
public void convertBetweenRatingAndRatingCompat() {
assertRatingEquals(MediaUtils.convertToRating(null), MediaUtils.convertToRatingCompat(null));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newUnratedRating(RatingCompat.RATING_NONE)),
MediaUtils.convertToRatingCompat(null));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newUnratedRating(RatingCompat.RATING_HEART)),
MediaUtils.convertToRatingCompat(new HeartRating()));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newHeartRating(true)),
MediaUtils.convertToRatingCompat(new HeartRating(true)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newThumbRating(false)),
MediaUtils.convertToRatingCompat(new ThumbRating(false)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newThumbRating(false)),
MediaUtils.convertToRatingCompat(new ThumbRating(false)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_3_STARS, 1f)),
MediaUtils.convertToRatingCompat(new StarRating(3, 1f)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_4_STARS, 0f)),
MediaUtils.convertToRatingCompat(new StarRating(4, 0f)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 5f)),
MediaUtils.convertToRatingCompat(new StarRating(5, 5f)));
assertRatingEquals(
MediaUtils.convertToRating(RatingCompat.newPercentageRating(80f)),
MediaUtils.convertToRatingCompat(new PercentageRating(80f)));
}
void assertRatingEquals(Rating rating, RatingCompat ratingCompat) {
if (rating == null && ratingCompat == null) {
return;
}
assertThat(rating.isRated()).isEqualTo(ratingCompat.isRated());
if (rating instanceof HeartRating) {
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_HEART);
assertThat(((HeartRating) rating).isHeart()).isEqualTo(ratingCompat.hasHeart());
} else if (rating instanceof ThumbRating) {
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_THUMB_UP_DOWN);
assertThat(((ThumbRating) rating).isThumbsUp()).isEqualTo(ratingCompat.isThumbUp());
} else if (rating instanceof StarRating) {
StarRating starRating = (StarRating) rating;
switch (starRating.getMaxStars()) {
case 3:
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_3_STARS);
break;
case 4:
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_4_STARS);
break;
case 5:
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_5_STARS);
break;
default: // fall out
}
assertThat(starRating.getStarRating()).isEqualTo(ratingCompat.getStarRating());
} else if (rating instanceof PercentageRating) {
assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_PERCENTAGE);
assertThat(((PercentageRating) rating).getPercent())
.isEqualTo(ratingCompat.getPercentRating());
}
}
@Test
public void convertToLibraryParams() {
assertThat(MediaUtils.convertToLibraryParams(context, null)).isNull();
Bundle rootHints = new Bundle();
rootHints.putString("key", "value");
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE, true);
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED, true);
MediaLibraryService.LibraryParams params =
MediaUtils.convertToLibraryParams(context, rootHints);
assertThat(params.offline).isTrue();
assertThat(params.recent).isTrue();
assertThat(params.suggested).isTrue();
assertThat(params.extras.getString("key")).isEqualTo("value");
}
@Test
public void convertToRootHints() {
assertThat(MediaUtils.convertToRootHints(null)).isNull();
Bundle extras = new Bundle();
extras.putString("key", "value");
MediaLibraryService.LibraryParams param =
new MediaLibraryService.LibraryParams.Builder()
.setOffline(true)
.setRecent(true)
.setSuggested(true)
.setExtras(extras)
.build();
Bundle rootHints = MediaUtils.convertToRootHints(param);
assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE)).isTrue();
assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT)).isTrue();
assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED)).isTrue();
assertThat(rootHints.getString("key")).isEqualTo("value");
}
@Test
public void removeNullElements() {
List<String> strings = new ArrayList<>();
strings.add("str1");
strings.add(null);
strings.add("str2");
strings.add(null);
assertThat(MediaUtils.removeNullElements(strings)).containsExactly("str1", "str2");
}
@Test
public void convertToSessionCommands() {
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.addCustomAction("action", "name", /* icon= */ 100)
.build();
SessionCommands sessionCommands = MediaUtils.convertToSessionCommands(playbackState);
assertThat(sessionCommands.contains(new SessionCommand("action", /* extras= */ null))).isTrue();
}
@Test
public void convertToPlayerCommands() {
long sessionFlags = FLAG_HANDLES_QUEUE_COMMANDS;
Player.Commands playerCommands = MediaUtils.convertToPlayerCommands(sessionFlags);
assertThat(playerCommands.contains(Player.COMMAND_GET_MEDIA_ITEMS)).isTrue();
}
@Test
public void convertToCustomLayout() {
assertThat(MediaUtils.convertToCustomLayout(null)).isEmpty();
String extraKey = "key";
String extraValue = "value";
String actionStr = "action";
String displayName = "display_name";
int iconRes = 21;
Bundle extras = new Bundle();
extras.putString(extraKey, extraValue);
PlaybackStateCompat.CustomAction action =
new PlaybackStateCompat.CustomAction.Builder(actionStr, displayName, iconRes)
.setExtras(extras)
.build();
PlaybackStateCompat state =
new PlaybackStateCompat.Builder()
.setState(
PlaybackStateCompat.STATE_NONE,
/* position= */ 0,
/* playbackSpeed= */ 1,
/* updateTime= */ 100)
.addCustomAction(action)
.build();
List<CommandButton> buttons = MediaUtils.convertToCustomLayout(state);
assertThat(buttons).hasSize(1);
CommandButton button = buttons.get(0);
assertThat(button.displayName.toString()).isEqualTo(displayName);
assertThat(button.enabled).isTrue();
assertThat(button.iconResId).isEqualTo(iconRes);
assertThat(button.sessionCommand.customAction).isEqualTo(actionStr);
assertThat(button.sessionCommand.customExtras.getString(extraKey)).isEqualTo(extraValue);
}
@Test
public void convertToAudioAttributes() {
assertThat(MediaUtils.convertToAudioAttributes((AudioAttributesCompat) null))
.isSameInstanceAs(AudioAttributes.DEFAULT);
assertThat(MediaUtils.convertToAudioAttributes((MediaControllerCompat.PlaybackInfo) null))
.isSameInstanceAs(AudioAttributes.DEFAULT);
int contentType = AudioAttributesCompat.CONTENT_TYPE_MUSIC;
int flags = AudioAttributesCompat.FLAG_AUDIBILITY_ENFORCED;
int usage = AudioAttributesCompat.USAGE_MEDIA;
AudioAttributesCompat aaCompat =
new AudioAttributesCompat.Builder()
.setContentType(contentType)
.setFlags(flags)
.setUsage(usage)
.build();
AudioAttributes aa =
new AudioAttributes.Builder()
.setContentType(contentType)
.setFlags(flags)
.setUsage(usage)
.build();
assertThat(MediaUtils.convertToAudioAttributes(aaCompat)).isEqualTo(aa);
assertThat(MediaUtils.convertToAudioAttributesCompat(aa)).isEqualTo(aaCompat);
}
}

View File

@ -0,0 +1,233 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MockPlayer}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MockPlayerTest {
private MockPlayer player;
@Before
public void setUp() {
player = new MockPlayer.Builder().build();
}
@Test
public void play() {
player.play();
assertThat(player.playCalled).isTrue();
}
@Test
public void pause() {
player.pause();
assertThat(player.pauseCalled).isTrue();
}
@Test
public void prepare() {
player.prepare();
assertThat(player.prepareCalled).isTrue();
}
@Test
public void stop() {
player.stop();
assertThat(player.stopCalled).isTrue();
}
@Test
public void release() {
player.release();
assertThat(player.releaseCalled).isTrue();
}
@Test
public void setPlayWhenReady() {
boolean testPlayWhenReady = false;
player.setPlayWhenReady(testPlayWhenReady);
assertThat(player.setPlayWhenReadyCalled).isTrue();
}
@Test
public void seekTo() {
long pos = 1004L;
player.seekTo(pos);
assertThat(player.seekToCalled).isTrue();
assertThat(player.seekPositionMs).isEqualTo(pos);
}
@Test
public void setPlaybackParameters() {
PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
player.setPlaybackParameters(playbackParameters);
assertThat(player.setPlaybackParametersCalled).isTrue();
assertThat(player.playbackParameters).isEqualTo(playbackParameters);
}
@Test
public void setPlaybackSpeed() {
float speed = 1.5f;
player.setPlaybackSpeed(speed);
assertThat(player.setPlaybackSpeedCalled).isTrue();
assertThat(player.playbackParameters.speed).isEqualTo(speed);
}
@Test
public void setMediaItems() {
List<MediaItem> list = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
player.setMediaItems(list);
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isEqualTo(list);
}
@Test
public void setMediaItems_withDuplicatedItems() {
List<MediaItem> list = MediaTestUtils.createConvergedMediaItems(/* size= */ 4);
list.set(2, list.get(1));
player.setMediaItems(list);
assertThat(player.setMediaItemsCalled).isTrue();
assertThat(player.mediaItems).isEqualTo(list);
}
@Test
public void setPlaylistMetadata() {
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
player.setPlaylistMetadata(playlistMetadata);
assertThat(player.setPlaylistMetadataCalled).isTrue();
assertThat(player.playlistMetadata).isSameInstanceAs(playlistMetadata);
}
@Test
public void addMediaItems() {
int index = 1;
int size = 2;
List<MediaItem> mediaItems = MediaTestUtils.createConvergedMediaItems(size);
player.addMediaItems(index, mediaItems);
assertThat(player.addMediaItemsCalled).isTrue();
assertThat(player.index).isEqualTo(index);
assertThat(player.mediaItems).isSameInstanceAs(mediaItems);
}
@Test
public void removeMediaItems() {
int fromIndex = 1;
int toIndex = 3;
player.removeMediaItems(fromIndex, toIndex);
assertThat(player.removeMediaItemsCalled).isTrue();
assertThat(player.fromIndex).isEqualTo(fromIndex);
assertThat(player.toIndex).isEqualTo(toIndex);
}
@Test
public void moveMediaItems() {
int fromIndex = 1;
int toIndex = 2;
int newIndex = 3;
player.moveMediaItems(fromIndex, toIndex, newIndex);
assertThat(player.moveMediaItemsCalled).isTrue();
assertThat(player.fromIndex).isEqualTo(fromIndex);
assertThat(player.toIndex).isEqualTo(toIndex);
assertThat(player.newIndex).isEqualTo(newIndex);
}
@Test
public void skipToPreviousItem() {
player.previous();
assertThat(player.previousCalled).isTrue();
}
@Test
public void skipToNextItem() {
player.next();
assertThat(player.nextCalled).isTrue();
}
@Test
public void setShuffleModeEnabled() {
boolean testShuffleModeEnabled = true;
player.setShuffleModeEnabled(testShuffleModeEnabled);
assertThat(player.setShuffleModeCalled).isTrue();
assertThat(player.shuffleModeEnabled).isEqualTo(testShuffleModeEnabled);
}
@Test
public void setRepeatMode() {
int testRepeatMode = Player.REPEAT_MODE_ALL;
player.setRepeatMode(testRepeatMode);
assertThat(player.setRepeatModeCalled).isTrue();
assertThat(player.repeatMode).isEqualTo(testRepeatMode);
}
@Test
public void setVolume() {
float testVolume = .123f;
player.setVolume(testVolume);
assertThat(player.setVolumeCalled).isTrue();
assertThat(player.volume).isEqualTo(testVolume);
}
@Test
public void setDeviceVolume() {
int testVolume = 12;
player.setDeviceVolume(testVolume);
assertThat(player.setDeviceVolumeCalled).isTrue();
assertThat(player.deviceVolume).isEqualTo(testVolume);
}
@Test
public void increaseDeviceVolume() {
player.increaseDeviceVolume();
assertThat(player.increaseDeviceVolumeCalled).isTrue();
}
@Test
public void decreaseDeviceVolume() {
player.decreaseDeviceVolume();
assertThat(player.decreaseDeviceVolumeCalled).isTrue();
}
@Test
public void setDeviceMuted() {
player.deviceMuted = false;
player.setDeviceMuted(true);
assertThat(player.setDeviceMutedCalled).isTrue();
assertThat(player.deviceMuted).isTrue();
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.exoplayer2.util.Log;
import java.util.ArrayList;
import java.util.List;
import org.junit.rules.ExternalResource;
/** TestRule for managing {@link RemoteMediaController} instances. */
public final class RemoteControllerTestRule extends ExternalResource {
private static final String TAG = "RemoteControllerTestRule";
private Context context;
private final List<RemoteMediaController> controllers = new ArrayList<>();
@Override
protected void before() {
context = ApplicationProvider.getApplicationContext();
}
@Override
protected void after() {
Exception exception = null;
for (RemoteMediaController controller : controllers) {
try {
controller.cleanUp();
} catch (Exception e) {
exception = e;
Log.e(TAG, "Exception thrown while cleanUp()", e);
}
}
if (exception != null) {
assertWithMessage("An exception thrown: " + exception).fail();
}
}
/**
* Creates {@link RemoteMediaController} from {@link SessionToken} with default options waiting
* for connection.
*/
@NonNull
public RemoteMediaController createRemoteController(@NonNull SessionToken token)
throws RemoteException {
return createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
}
/** Creates {@link RemoteMediaController} from {@link SessionToken}. */
@NonNull
public RemoteMediaController createRemoteController(
@NonNull SessionToken token, boolean waitForConnection, Bundle connectionHints)
throws RemoteException {
RemoteMediaController controller =
new RemoteMediaController(context, token, connectionHints, waitForConnection);
controllers.add(controller);
return controller;
}
/**
* Creates {@link RemoteMediaBrowser} from {@link SessionToken} with default options waiting for
* connection.
*/
@NonNull
public RemoteMediaBrowser createRemoteBrowser(@NonNull SessionToken token)
throws RemoteException {
RemoteMediaBrowser browser =
new RemoteMediaBrowser(
context, token, /* waitForConnection= */ true, /* connectionHints= */ null);
controllers.add(browser);
return browser;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link RemoteMediaBrowserCompat}. */
@RunWith(AndroidJUnit4.class)
public class RemoteMediaBrowserCompatTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private RemoteMediaBrowserCompat remoteBrowserCompat;
@Before
public void setUp() throws Exception {
remoteBrowserCompat =
new RemoteMediaBrowserCompat(
ApplicationProvider.getApplicationContext(), MOCK_MEDIA2_LIBRARY_SERVICE);
}
@After
public void cleanUp() throws Exception {
if (remoteBrowserCompat != null) {
remoteBrowserCompat.cleanUp();
}
}
@Test
@SmallTest
public void connect() throws Exception {
remoteBrowserCompat.connect(/* waitForConnection= */ true);
assertThat(remoteBrowserCompat.isConnected()).isTrue();
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link RemoteMediaControllerCompat}. */
@RunWith(AndroidJUnit4.class)
public class RemoteMediaControllerCompatTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule
public final HandlerThreadTestRule threadTestRule =
new HandlerThreadTestRule("RemoteMediaControllerCompatTest");
private TestHandler handler;
private MediaSessionCompat sessionCompat;
private RemoteMediaControllerCompat remoteControllerCompat;
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
handler = threadTestRule.getHandler();
handler.postAndSync(
() -> {
sessionCompat = new MediaSessionCompat(context, DEFAULT_TEST_NAME);
sessionCompat.setActive(true);
});
remoteControllerCompat =
new RemoteMediaControllerCompat(
context, sessionCompat.getSessionToken(), /* waitForConnection= */ true);
}
@After
public void cleanUp() {
sessionCompat.release();
remoteControllerCompat.cleanUp();
}
@Test
@SmallTest
public void play() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
sessionCompat.setCallback(
new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
latch.countDown();
}
},
handler);
remoteControllerCompat.getTransportControls().play();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link RemoteMediaSessionCompat}. */
@RunWith(AndroidJUnit4.class)
public class RemoteMediaSessionCompatTest {
private Context context;
private RemoteMediaSessionCompat remoteSessionCompat;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
remoteSessionCompat = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
}
@After
public void cleanUp() throws Exception {
remoteSessionCompat.cleanUp();
}
@Test
@SmallTest
public void gettingToken() throws Exception {
MediaSessionCompat.Token token = remoteSessionCompat.getSessionToken();
assertThat(token).isNotNull();
}
@Test
@SmallTest
public void creatingControllerCompat() throws Exception {
MediaSessionCompat.Token token = remoteSessionCompat.getSessionToken();
assertThat(token).isNotNull();
MediaControllerCompat controller = new MediaControllerCompat(context, token);
assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Bundle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link RemoteMediaSession}. */
@RunWith(AndroidJUnit4.class)
public class RemoteMediaSessionTest {
private Context context;
private RemoteMediaSession remoteSession;
private Bundle tokenExtras;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
tokenExtras = TestUtils.createTestBundle();
remoteSession = new RemoteMediaSession(DEFAULT_TEST_NAME, context, tokenExtras);
}
@After
public void cleanUp() throws Exception {
if (remoteSession != null) {
remoteSession.cleanUp();
}
}
@Test
@SmallTest
public void gettingToken() throws Exception {
SessionToken token = remoteSession.getToken();
assertThat(token).isNotNull();
assertThat(token.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
assertThat(TestUtils.equals(tokenExtras, token.getExtras())).isTrue();
}
@Test
@SmallTest
public void creatingController() throws Exception {
SessionToken token = remoteSession.getToken();
assertThat(token).isNotNull();
MediaController controller =
new MediaController.Builder(context)
.setSessionToken(token)
.setControllerCallback(new MediaController.ControllerCallback() {})
.build();
assertThat(controller).isNotNull();
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link SessionCommand} and {@link SessionCommands}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SessionCommandTest {
// Prefix for all command codes
private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
private static final List<String> PREFIX_COMMAND_CODES = new ArrayList<>();
static {
PREFIX_COMMAND_CODES.add("COMMAND_CODE_PLAYER_");
PREFIX_COMMAND_CODES.add("COMMAND_CODE_VOLUME_");
PREFIX_COMMAND_CODES.add("COMMAND_CODE_SESSION_");
PREFIX_COMMAND_CODES.add("COMMAND_CODE_LIBRARY_");
}
/** Test possible typos in naming */
@Test
public void codes_name() {
List<Field> fields = getSessionCommandsFields("");
for (int i = 0; i < fields.size(); i++) {
String name = fields.get(i).getName();
boolean matches = false;
if (name.startsWith("COMMAND_VERSION_") || name.equals("COMMAND_CODE_CUSTOM")) {
matches = true;
}
if (!matches) {
for (int j = 0; j < PREFIX_COMMAND_CODES.size(); j++) {
if (name.startsWith(PREFIX_COMMAND_CODES.get(j))) {
matches = true;
break;
}
}
}
assertWithMessage("Unexpected constant " + name).that(matches).isTrue();
}
}
/** Tests possible code duplications in values */
@Test
public void codes_valueDuplication() throws IllegalAccessException {
List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODE);
Set<Integer> values = new HashSet<>();
for (int i = 0; i < fields.size(); i++) {
Integer value = fields.get(i).getInt(null);
assertThat(values.add(value)).isTrue();
}
}
/** Tests whether codes are continuous */
@Test
@Ignore
public void codes_valueContinuous() throws IllegalAccessException {
for (int i = 0; i < PREFIX_COMMAND_CODES.size(); i++) {
List<Field> fields = getSessionCommandsFields(PREFIX_COMMAND_CODES.get(i));
List<Integer> values = new ArrayList<>();
for (int j = 0; j < fields.size(); j++) {
values.add(fields.get(j).getInt(null));
}
Collections.sort(values);
for (int j = 1; j < values.size(); j++) {
assertWithMessage(
"Command code isn't continuous. Missing "
+ (values.get(j - 1) + 1)
+ " in "
+ PREFIX_COMMAND_CODES.get(i))
.that((int) values.get(j))
.isEqualTo(((int) values.get(j - 1)) + 1);
}
}
}
@Test
public void addAllPredefinedCommands_withVersion1_notHaveVersion2Commands() {
SessionCommands commands =
new SessionCommands.Builder()
.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
.build();
assertThat(commands.contains(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI)).isFalse();
}
@Test
public void addAllPredefinedCommands_withVersion2_hasVersion2Commands() {
SessionCommands commands =
new SessionCommands.Builder()
.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_2)
.build();
assertThat(commands.contains(SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI)).isTrue();
}
private static List<Field> getSessionCommandsFields(String prefix) {
List<Field> list = new ArrayList<>();
Field[] fields = SessionCommand.class.getFields();
if (fields != null) {
for (int i = 0; i < fields.length; i++) {
if (isPublicStaticFinalInt(fields[i]) && fields[i].getName().startsWith(prefix)) {
list.add(fields[i]);
}
}
}
return list;
}
private static boolean isPublicStaticFinalInt(Field field) {
if (field.getType() != int.class) {
return false;
}
int modifier = field.getModifiers();
return Modifier.isPublic(modifier) && Modifier.isStatic(modifier) && Modifier.isFinal(modifier);
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertThat;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link SessionToken}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SessionTokenTest {
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
private Context context;
private List<MediaSession> sessions = new ArrayList<>();
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
}
@After
public void cleanUp() throws Exception {
for (MediaSession session : sessions) {
if (session != null) {
session.release();
}
}
}
@Test
public void constructor_sessionService() {
SessionToken token =
new SessionToken(
context,
new ComponentName(
context.getPackageName(), MockMediaSessionService.class.getCanonicalName()));
assertThat(token.getPackageName()).isEqualTo(context.getPackageName());
assertThat(token.getUid()).isEqualTo(Process.myUid());
assertThat(token.getType()).isEqualTo(SessionToken.TYPE_SESSION_SERVICE);
}
@Test
public void constructor_libraryService() {
ComponentName testComponentName =
new ComponentName(
context.getPackageName(), MockMediaLibraryService.class.getCanonicalName());
SessionToken token = new SessionToken(context, testComponentName);
assertThat(token.getPackageName()).isEqualTo(context.getPackageName());
assertThat(token.getUid()).isEqualTo(Process.myUid());
assertThat(token.getType()).isEqualTo(SessionToken.TYPE_LIBRARY_SERVICE);
assertThat(token.getServiceName()).isEqualTo(testComponentName.getClassName());
}
@Test
public void getters_whenCreatedBySession() {
Bundle testTokenExtras = TestUtils.createTestBundle();
MediaSession session =
new MediaSession.Builder(context, new MockPlayer.Builder().build())
.setId("testGetters_whenCreatedBySession")
.setExtras(testTokenExtras)
.build();
sessions.add(session);
SessionToken token = session.getToken();
assertThat(token.getPackageName()).isEqualTo(context.getPackageName());
assertThat(token.getUid()).isEqualTo(Process.myUid());
assertThat(token.getType()).isEqualTo(SessionToken.TYPE_SESSION);
assertThat(TestUtils.equals(testTokenExtras, token.getExtras())).isTrue();
assertThat(token.getServiceName()).isNull();
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link TestBrowserCallback}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestBrowserCallbackTest {
/**
* Test if the {@link TestBrowserCallback} wraps the callback proxy without missing any method.
*/
@Test
public void methods_overridden() {
Method[] methods = TestBrowserCallback.class.getMethods();
assertThat(methods).isNotNull();
for (Method method : methods) {
// For any methods in the controller callback, TestBrowserCallback should have
// overridden the method and call matching API in the callback proxy.
assertWithMessage(
"TestBrowserCallback should override " + method + " and call callback proxy")
.that(method.getDeclaringClass())
.isNotEqualTo(MediaBrowser.BrowserCallback.class);
assertWithMessage(
"TestBrowserCallback should override " + method + " and call callback proxy")
.that(method.getDeclaringClass())
.isNotEqualTo(MediaController.ControllerCallback.class);
}
}
}

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 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.google.android.exoplayer2.session.vct">
<uses-sdk android:minSdkVersion="16"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries>
<package android:name="com.google.android.exoplayer2.session.vct.test" />
</queries>
<application android:allowBackup="false">
<activity android:name="com.google.android.exoplayer2.session.vct.common.SurfaceActivity"
android:exported="false" />
<receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<service android:name="com.google.android.exoplayer2.session.MediaControllerProviderService"
android:exported="true"
android:process=":remote">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
<action android:name="com.google.android.exoplayer2.session.vct.action.MEDIA2_CONTROLLER" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MediaControllerCompatProviderService"
android:exported="true"
android:process=":remote">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
<action android:name="com.google.android.exoplayer2.session.vct.action.MEDIA_CONTROLLER_COMPAT" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MediaBrowserCompatProviderService"
android:exported="true"
android:process=":remote">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
<action android:name="com.google.android.exoplayer2.session.vct.action.MEDIA_BROWSER_COMPAT" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MediaSessionProviderService"
android:exported="true"
android:process=":remote">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
<action android:name="com.google.android.exoplayer2.session.vct.action.MEDIA2_SESSION" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MediaSessionCompatProviderService"
android:exported="true"
android:process=":remote">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
<action android:name="com.google.android.exoplayer2.session.vct.action.MEDIA_SESSION_COMPAT" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MockMediaSessionService"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.google.android.exoplayer2.session.MediaSessionService" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.LocalMockMediaSessionService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.exoplayer2.session.MediaSessionService" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MockMediaLibraryService"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.google.android.exoplayer2.session.MediaLibraryService" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.MockMediaBrowserServiceCompat"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.session.LocalMockMediaBrowserServiceCompat"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,19 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
/** {@link MockMediaBrowserServiceCompat} running on a local process. */
public class LocalMockMediaBrowserServiceCompat extends MockMediaBrowserServiceCompat {}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
/** {@link MockMediaSessionService} running on a local process. */
public class LocalMockMediaSessionService extends MockMediaSessionService {}

View File

@ -0,0 +1,200 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserCompat.ConnectionCallback;
import android.support.v4.media.MediaBrowserCompat.ItemCallback;
import android.support.v4.media.MediaBrowserCompat.SearchCallback;
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaBrowserCompat;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
* A Service that creates {@link MediaBrowserCompat} and calls its methods according to the service
* app's requests.
*/
public class MediaBrowserCompatProviderService extends Service {
private static final String TAG = "MediaBrowserCompatProviderService";
Map<String, MediaBrowserCompat> mediaBrowserCompatMap = new HashMap<>();
Map<String, TestBrowserConnectionCallback> connectionCallbackMap = new HashMap<>();
RemoteMediaBrowserCompatStub binder;
TestHandler handler;
Executor executor;
@Override
public void onCreate() {
super.onCreate();
binder = new RemoteMediaBrowserCompatStub();
handler = new TestHandler(getMainLooper());
executor = handler::post;
}
@Override
public IBinder onBind(Intent intent) {
if (ACTION_MEDIA_BROWSER_COMPAT.equals(intent.getAction())) {
return binder;
}
return null;
}
private class RemoteMediaBrowserCompatStub extends IRemoteMediaBrowserCompat.Stub {
@Override
public void create(String browserId, ComponentName componentName) throws RemoteException {
try {
TestBrowserConnectionCallback callback = new TestBrowserConnectionCallback();
handler.postAndSync(
() -> {
MediaBrowserCompat browser =
new MediaBrowserCompat(
MediaBrowserCompatProviderService.this,
componentName,
callback,
new Bundle(/* rootHints= */ ));
mediaBrowserCompatMap.put(browserId, browser);
connectionCallbackMap.put(browserId, callback);
});
} catch (Exception e) {
Log.e(TAG, "Exception occurred while creating MediaMediaBrowserCompat", e);
}
}
////////////////////////////////////////////////////////////////////////////////
// MediaBrowserCompat methods
////////////////////////////////////////////////////////////////////////////////
@Override
public void connect(String browserId, boolean waitForConnection) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.connect();
if (waitForConnection) {
TestBrowserConnectionCallback callback = connectionCallbackMap.get(browserId);
boolean connected = false;
try {
connected = callback.connectionLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException occurred while waiting for connection", e);
}
if (!connected) {
Log.e(TAG, "Could not connect to the given browser service.");
}
}
}
@Override
public void disconnect(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.disconnect();
}
@Override
public boolean isConnected(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
return browser.isConnected();
}
@Override
public ComponentName getServiceComponent(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
return browser.getServiceComponent();
}
@Override
public String getRoot(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
return browser.getRoot();
}
@Override
public Bundle getExtras(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
return browser.getExtras();
}
@Override
public Bundle getConnectedSessionToken(String browserId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
Bundle tokenBundle = new Bundle();
tokenBundle.putParcelable(KEY_SESSION_COMPAT_TOKEN, browser.getSessionToken());
return tokenBundle;
}
@Override
public void subscribe(String browserId, String parentId, Bundle options)
throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.subscribe(parentId, options, new SubscriptionCallback() {});
}
@Override
public void unsubscribe(String browserId, String parentId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.unsubscribe(parentId);
}
@Override
public void getItem(String browserId, String mediaId) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.getItem(mediaId, new ItemCallback() {});
}
@Override
public void search(String browserId, String query, Bundle extras) throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.search(query, extras, new SearchCallback() {});
}
@Override
public void sendCustomAction(String browserId, String action, Bundle extras)
throws RemoteException {
MediaBrowserCompat browser = mediaBrowserCompatMap.get(browserId);
browser.sendCustomAction(action, extras, /* customActionCallback= */ null);
}
}
private class TestBrowserConnectionCallback extends ConnectionCallback {
private CountDownLatch connectionLatch = new CountDownLatch(1);
@Override
public void onConnected() {
connectionLatch.countDown();
}
}
}

View File

@ -0,0 +1,312 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_ARGUMENTS;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaControllerCompat;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
* A Service that creates {@link MediaControllerCompat} and calls its methods according to the
* service app's requests.
*/
public class MediaControllerCompatProviderService extends Service {
private static final String TAG = "MediaControllerCompatProviderService";
Map<String, MediaControllerCompat> mediaControllerCompatMap = new HashMap<>();
RemoteMediaControllerCompatStub binder;
TestHandler handler;
Executor executor;
@Override
public void onCreate() {
super.onCreate();
binder = new RemoteMediaControllerCompatStub();
handler = new TestHandler(getMainLooper());
executor = handler::post;
}
@Override
public IBinder onBind(Intent intent) {
if (ACTION_MEDIA_CONTROLLER_COMPAT.equals(intent.getAction())) {
return binder;
}
return null;
}
private class RemoteMediaControllerCompatStub extends IRemoteMediaControllerCompat.Stub {
@Override
public void create(String controllerId, Bundle tokenBundle, boolean waitForConnection) {
MediaSessionCompat.Token token = (MediaSessionCompat.Token) getParcelable(tokenBundle);
MediaControllerCompat controller =
new MediaControllerCompat(MediaControllerCompatProviderService.this, token);
TestControllerCallback callback = new TestControllerCallback();
controller.registerCallback(callback, handler);
mediaControllerCompatMap.put(controllerId, controller);
if (!waitForConnection) {
return;
}
boolean connected = false;
try {
connected = callback.connectionLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException occurred while waiting for connection", e);
}
if (!connected) {
Log.e(TAG, "Could not connect to the given session.");
}
}
////////////////////////////////////////////////////////////////////////////////
// MediaControllerCompat methods
////////////////////////////////////////////////////////////////////////////////
@Override
public void addQueueItem(String controllerId, Bundle descriptionBundle) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
controller.addQueueItem(desc);
}
@Override
public void addQueueItemWithIndex(String controllerId, Bundle descriptionBundle, int index)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
controller.addQueueItem(desc, index);
}
@Override
public void removeQueueItem(String controllerId, Bundle descriptionBundle)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
controller.removeQueueItem(desc);
}
@Override
public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.setVolumeTo(value, flags);
}
@Override
public void adjustVolume(String controllerId, int direction, int flags) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.adjustVolume(direction, flags);
}
@Override
public void sendCommand(String controllerId, String command, Bundle params, ResultReceiver cb)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.sendCommand(command, params, cb);
}
////////////////////////////////////////////////////////////////////////////////
// MediaControllerCompat.TransportControls methods
////////////////////////////////////////////////////////////////////////////////
@Override
public void prepare(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().prepare();
}
@Override
public void prepareFromMediaId(String controllerId, String mediaId, Bundle extras)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().prepareFromMediaId(mediaId, extras);
}
@Override
public void prepareFromSearch(String controllerId, String query, Bundle extras)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().prepareFromSearch(query, extras);
}
@Override
public void prepareFromUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().prepareFromUri(uri, extras);
}
@Override
public void play(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().play();
}
@Override
public void playFromMediaId(String controllerId, String mediaId, Bundle extras)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().playFromMediaId(mediaId, extras);
}
@Override
public void playFromSearch(String controllerId, String query, Bundle extras)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().playFromSearch(query, extras);
}
@Override
public void playFromUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().playFromUri(uri, extras);
}
@Override
public void skipToQueueItem(String controllerId, long id) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().skipToQueueItem(id);
}
@Override
public void pause(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().pause();
}
@Override
public void stop(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().stop();
}
@Override
public void seekTo(String controllerId, long pos) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().seekTo(pos);
}
@Override
public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().setPlaybackSpeed(speed);
}
@Override
public void skipToNext(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().skipToNext();
}
@Override
public void skipToPrevious(String controllerId) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().skipToPrevious();
}
@Override
public void setRating(String controllerId, Bundle ratingBundle) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
controller.getTransportControls().setRating(rating);
}
@Override
public void setRatingWithExtras(String controllerId, Bundle ratingBundle, Bundle extras)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
controller.getTransportControls().setRating(rating, extras);
}
@Override
public void setCaptioningEnabled(String controllerId, boolean enabled) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().setCaptioningEnabled(enabled);
}
@Override
public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().setRepeatMode(repeatMode);
}
@Override
public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().setShuffleMode(shuffleMode);
}
@Override
public void sendCustomAction(String controllerId, Bundle customActionBundle, Bundle args)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
PlaybackStateCompat.CustomAction customAction =
(PlaybackStateCompat.CustomAction) getParcelable(customActionBundle);
controller.getTransportControls().sendCustomAction(customAction, args);
}
@Override
public void sendCustomActionWithName(String controllerId, String action, Bundle args)
throws RemoteException {
MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
controller.getTransportControls().sendCustomAction(action, args);
}
private Parcelable getParcelable(Bundle bundle) {
bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
return bundle.getParcelable(KEY_ARGUMENTS);
}
}
private static class TestControllerCallback extends MediaControllerCompat.Callback {
private final CountDownLatch connectionLatch = new CountDownLatch(1);
@Override
public void onSessionReady() {
super.onSessionReady();
connectionLatch.countDown();
}
}
}

View File

@ -0,0 +1,630 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaController;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
/**
* A Service that creates {@link MediaController} and calls its methods according to the service
* app's requests.
*/
public class MediaControllerProviderService extends Service {
private static final String TAG = "MediaControllerProviderService";
Map<String, MediaController> mediaControllerMap = new HashMap<>();
RemoteMediaControllerStub binder;
TestHandler handler;
@Override
public void onCreate() {
super.onCreate();
binder = new RemoteMediaControllerStub();
handler = new TestHandler(getMainLooper());
}
@Override
public IBinder onBind(Intent intent) {
if (ACTION_MEDIA2_CONTROLLER.equals(intent.getAction())) {
return binder;
}
return null;
}
@Override
public void onDestroy() {
for (MediaController controller : mediaControllerMap.values()) {
try {
handler.postAndSync(controller::release);
} catch (Exception e) {
Log.e(TAG, "Exception in releasing controller", e);
}
}
}
private class RemoteMediaControllerStub extends IRemoteMediaController.Stub {
private void runOnHandler(@NonNull TestHandler.TestRunnable runnable) throws RemoteException {
try {
handler.postAndSync(runnable);
} catch (Exception e) {
Log.e(TAG, "Exception thrown while waiting for handler", e);
throw new RemoteException("Unexpected exception");
}
}
private <V> V runOnHandler(@NonNull Callable<V> callable) throws RemoteException {
try {
return handler.postAndSync(callable);
} catch (Exception e) {
Log.e(TAG, "Exception thrown while waiting for handler", e);
throw new RemoteException("Unexpected exception");
}
}
@Override
public void create(
boolean isBrowser,
String controllerId,
Bundle tokenBundle,
Bundle connectionHints,
boolean waitForConnection)
throws RemoteException {
SessionToken token = SessionToken.CREATOR.fromBundle(tokenBundle);
TestControllerCallback callback = new TestControllerCallback();
runOnHandler(
() -> {
Context context = MediaControllerProviderService.this;
MediaController controller;
if (isBrowser) {
MediaBrowser.Builder builder =
new MediaBrowser.Builder(context)
.setSessionToken(token)
.setControllerCallback(callback);
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
controller = builder.build();
} else {
MediaController.Builder builder =
new MediaController.Builder(context)
.setSessionToken(token)
.setControllerCallback(callback);
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
controller = builder.build();
}
mediaControllerMap.put(controllerId, controller);
});
if (!waitForConnection) {
return;
}
boolean connected = false;
try {
connected = callback.connectionLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException occurred while waiting for connection", e);
}
if (!connected) {
Log.e(TAG, "Could not connect to the given session.");
}
}
////////////////////////////////////////////////////////////////////////////////
// MediaController methods
////////////////////////////////////////////////////////////////////////////////
@Override
public Bundle getConnectedSessionToken(String controllerId) throws RemoteException {
return runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
return BundleableUtils.toNullableBundle(controller.getConnectedToken());
});
}
@Override
public void play(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.play();
});
}
@Override
public void pause(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.pause();
});
}
@Override
public void setPlayWhenReady(String controllerId, boolean playWhenReady)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setPlayWhenReady(playWhenReady);
});
}
@Override
public void prepare(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.prepare();
});
}
@Override
public void seekToDefaultPosition(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.seekToDefaultPosition();
});
}
@Override
public void seekToDefaultPositionWithWindowIndex(String controllerId, int windowIndex)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.seekToDefaultPosition(windowIndex);
});
}
@Override
public void seekTo(String controllerId, long positionMs) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.seekTo(positionMs);
});
}
@Override
public void seekToWithWindowIndex(String controllerId, int windowIndex, long positionMs)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.seekTo(windowIndex, positionMs);
});
}
@Override
public void setPlaybackParameters(String controllerId, Bundle playbackParametersBundle)
throws RemoteException {
PlaybackParameters playbackParameters =
PlaybackParameters.CREATOR.fromBundle(playbackParametersBundle);
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setPlaybackParameters(playbackParameters);
});
}
@Override
public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setPlaybackSpeed(speed);
});
}
@Override
public void setMediaItems1(
String controllerId, List<Bundle> mediaItemBundles, boolean resetPosition)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setMediaItems(
BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles), resetPosition);
});
}
@Override
public void setMediaItems2(
String controllerId,
List<Bundle> mediaItemBundles,
int startWindowIndex,
long startPositionMs)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setMediaItems(
BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles),
startWindowIndex,
startPositionMs);
});
}
@Override
public void createAndSetFakeMediaItems(String controllerId, int size) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
List<MediaItem> itemList = new ArrayList<>();
for (int i = 0; i < size; i++) {
// Make media ID of each item same with its index.
String mediaId = TestUtils.getMediaIdInFakeTimeline(i);
itemList.add(MediaTestUtils.createConvergedMediaItem(mediaId));
}
controller.setMediaItems(itemList);
});
}
@Override
@SuppressWarnings("FutureReturnValueIgnored")
public void setMediaUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setMediaUri(uri, extras);
});
}
@Override
public void setPlaylistMetadata(String controllerId, Bundle playlistMetadataBundle)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setPlaylistMetadata(
MediaMetadata.CREATOR.fromBundle(playlistMetadataBundle));
});
}
@Override
public void addMediaItems(String controllerId, int index, List<Bundle> mediaItemBundles)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.addMediaItems(
index, BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles));
});
}
@Override
public void removeMediaItems(String controllerId, int fromIndex, int toIndex)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.removeMediaItems(fromIndex, toIndex);
});
}
@Override
public void moveMediaItems(String controllerId, int fromIndex, int toIndex, int newIndex)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.moveMediaItems(fromIndex, toIndex, newIndex);
});
}
@Override
public void previous(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.previous();
});
}
@Override
public void next(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.next();
});
}
@Override
public void setShuffleModeEnabled(String controllerId, boolean shuffleModeEnabled)
throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setShuffleModeEnabled(shuffleModeEnabled);
});
}
@Override
public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setRepeatMode(repeatMode);
});
}
@Override
public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setDeviceVolume(value);
});
}
@Override
public void adjustVolume(String controllerId, int direction, int flags) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
switch (direction) {
case AudioManager.ADJUST_RAISE:
controller.increaseDeviceVolume();
break;
case AudioManager.ADJUST_LOWER:
controller.decreaseDeviceVolume();
break;
case AudioManager.ADJUST_MUTE:
controller.setDeviceMuted(true);
break;
case AudioManager.ADJUST_UNMUTE:
controller.setDeviceMuted(false);
break;
case AudioManager.ADJUST_TOGGLE_MUTE:
controller.setDeviceMuted(controller.isDeviceMuted());
break;
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
});
}
@Override
public Bundle sendCustomCommand(String controllerId, Bundle command, Bundle args)
throws RemoteException {
MediaController controller = mediaControllerMap.get(controllerId);
Future<SessionResult> future =
controller.sendCustomCommand(SessionCommand.CREATOR.fromBundle(command), args);
SessionResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle setRating(String controllerId, String mediaId, Bundle rating)
throws RemoteException {
MediaController controller = mediaControllerMap.get(controllerId);
Future<SessionResult> future =
controller.setRating(mediaId, Rating.CREATOR.fromBundle(rating));
SessionResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public void setVolume(String controllerId, float volume) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setVolume(volume);
});
}
@Override
public void setDeviceVolume(String controllerId, int volume) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setDeviceVolume(volume);
});
}
@Override
public void increaseDeviceVolume(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.increaseDeviceVolume();
});
}
@Override
public void decreaseDeviceVolume(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.decreaseDeviceVolume();
});
}
@Override
public void setDeviceMuted(String controllerId, boolean muted) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.setDeviceMuted(muted);
});
}
@Override
public void release(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.release();
});
}
@Override
public void stop(String controllerId) throws RemoteException {
runOnHandler(
() -> {
MediaController controller = mediaControllerMap.get(controllerId);
controller.stop();
});
}
////////////////////////////////////////////////////////////////////////////////
// MediaBrowser methods
////////////////////////////////////////////////////////////////////////////////
@Override
public Bundle getLibraryRoot(String controllerId, Bundle libraryParams) throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future =
browser.getLibraryRoot(
BundleableUtils.fromNullableBundle(
MediaLibraryService.LibraryParams.CREATOR, libraryParams));
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle subscribe(String controllerId, String parentId, Bundle libraryParams)
throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future =
browser.subscribe(
parentId,
BundleableUtils.fromNullableBundle(
MediaLibraryService.LibraryParams.CREATOR, libraryParams));
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle unsubscribe(String controllerId, String parentId) throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future = browser.unsubscribe(parentId);
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle getChildren(
String controllerId, String parentId, int page, int pageSize, Bundle libraryParams)
throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future =
browser.getChildren(
parentId,
page,
pageSize,
BundleableUtils.fromNullableBundle(
MediaLibraryService.LibraryParams.CREATOR, libraryParams));
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle getItem(String controllerId, String mediaId) throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future = browser.getItem(mediaId);
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle search(String controllerId, String query, Bundle libraryParams)
throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future =
browser.search(
query,
BundleableUtils.fromNullableBundle(
MediaLibraryService.LibraryParams.CREATOR, libraryParams));
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
@Override
public Bundle getSearchResult(
String controllerId, String query, int page, int pageSize, Bundle libraryParams)
throws RemoteException {
MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
Future<LibraryResult> future =
browser.getSearchResult(
query,
page,
pageSize,
BundleableUtils.fromNullableBundle(
MediaLibraryService.LibraryParams.CREATOR, libraryParams));
LibraryResult result = getFutureResult(future);
return result.toBundle();
}
private final class TestControllerCallback implements MediaBrowser.BrowserCallback {
private final CountDownLatch connectionLatch = new CountDownLatch(1);
@Override
public void onConnected(MediaController controller) {
connectionLatch.countDown();
}
}
}
private static <T> T getFutureResult(Future<T> future) throws RemoteException {
try {
return future.get(TestUtils.TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RemoteException("Exception thrown when getting result. " + e.getMessage());
}
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_METADATA_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_QUEUE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.annotation.Nullable;
import androidx.media.VolumeProviderCompat;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaSessionCompat;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.util.Log;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* A Service that creates {@link MediaSessionCompat} and calls its methods according to the client
* app's requests.
*/
public class MediaSessionCompatProviderService extends Service {
private static final String TAG = "MediaSessionCompatProviderService";
Map<String, MediaSessionCompat> sessionMap = new HashMap<>();
RemoteMediaSessionCompatStub sessionBinder;
TestHandler handler;
Executor executor;
@Override
public void onCreate() {
super.onCreate();
sessionBinder = new RemoteMediaSessionCompatStub();
handler = new TestHandler(getMainLooper());
executor = handler::post;
}
@Override
public IBinder onBind(Intent intent) {
if (ACTION_MEDIA_SESSION_COMPAT.equals(intent.getAction())) {
return sessionBinder;
}
return null;
}
@Override
public void onDestroy() {
for (MediaSessionCompat session : sessionMap.values()) {
session.release();
}
}
private class RemoteMediaSessionCompatStub extends IRemoteMediaSessionCompat.Stub {
@Override
public void create(String sessionTag) throws RemoteException {
try {
handler.postAndSync(
() -> {
MediaSessionCompat session =
new MediaSessionCompat(MediaSessionCompatProviderService.this, sessionTag);
sessionMap.put(sessionTag, session);
});
} catch (Exception e) {
Log.e(TAG, "Exception occurred while creating MediaSessionCompat", e);
}
}
////////////////////////////////////////////////////////////////////////////////
// MediaSessionCompat methods
////////////////////////////////////////////////////////////////////////////////
@Override
public Bundle getSessionToken(String sessionTag) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
Bundle result = new Bundle();
result.putParcelable(KEY_SESSION_COMPAT_TOKEN, session.getSessionToken());
return result;
}
@Override
public void setPlaybackToLocal(String sessionTag, int stream) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setPlaybackToLocal(stream);
}
@Override
public void setPlaybackToRemote(
String sessionTag, int volumeControl, int maxVolume, int currentVolume)
throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setPlaybackToRemote(
new VolumeProviderCompat(volumeControl, maxVolume, currentVolume) {
@Override
public void onSetVolumeTo(int volume) {
setCurrentVolume(volume);
}
@Override
public void onAdjustVolume(int direction) {
setCurrentVolume(getCurrentVolume() + direction);
}
});
}
@Override
public void release(String sessionTag) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.release();
}
@Override
public void setPlaybackState(String sessionTag, Bundle stateBundle) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
stateBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
PlaybackStateCompat state = stateBundle.getParcelable(KEY_PLAYBACK_STATE_COMPAT);
session.setPlaybackState(state);
}
@Override
public void setMetadata(String sessionTag, Bundle metadataBundle) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
metadataBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
MediaMetadataCompat metadata = metadataBundle.getParcelable(KEY_METADATA_COMPAT);
session.setMetadata(metadata);
}
@Override
public void setQueue(String sessionTag, @Nullable Bundle queueBundle) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
if (queueBundle == null) {
session.setQueue(null);
} else {
queueBundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
List<QueueItem> queue = queueBundle.getParcelableArrayList(KEY_QUEUE);
session.setQueue(queue);
}
}
@Override
public void setQueueTitle(String sessionTag, CharSequence title) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setQueueTitle(title);
}
@Override
public void setRepeatMode(String sessionTag, @PlaybackStateCompat.RepeatMode int repeatMode)
throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setRepeatMode(repeatMode);
}
@Override
public void setShuffleMode(String sessionTag, @PlaybackStateCompat.ShuffleMode int shuffleMode)
throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setShuffleMode(shuffleMode);
}
@Override
public void setSessionActivity(String sessionTag, PendingIntent pi) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setSessionActivity(pi);
}
@Override
public void setFlags(String sessionTag, int flags) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setFlags(flags);
}
@Override
public void setRatingType(String sessionTag, int type) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setRatingType(type);
}
@Override
public void sendSessionEvent(String sessionTag, String event, Bundle extras)
throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.sendSessionEvent(event, extras);
}
@Override
public void setCaptioningEnabled(String sessionTag, boolean enabled) throws RemoteException {
MediaSessionCompat session = sessionMap.get(sessionTag);
session.setCaptioningEnabled(enabled);
}
}
}

View File

@ -0,0 +1,822 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA2_SESSION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_BUFFERED_PERCENTAGE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_BUFFERED_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_BUFFERED_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_AD_GROUP_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_AD_INDEX_IN_AD_GROUP;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_LIVE_OFFSET;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_PERIOD_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_WINDOW_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_INFO;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_MUTED;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_VOLUME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_LOADING;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_PLAYING;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_PLAYING_AD;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_MEDIA_ITEM;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_PARAMETERS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_STATE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_SUPPRESSION_REASON;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYER_ERROR;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYLIST_METADATA;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAY_WHEN_READY;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_REPEAT_MODE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SHUFFLE_MODE_ENABLED;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_TIMELINE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_TOTAL_BUFFERED_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_VIDEO_SIZE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_VOLUME;
import static com.google.android.exoplayer2.session.vct.common.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
import static com.google.android.exoplayer2.session.vct.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Player.PositionInfo;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaSession;
import com.google.android.exoplayer2.session.vct.common.MockActivity;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.session.vct.common.TestHandler.TestRunnable;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* A Service that creates {@link MediaSession} and calls its methods according to the client app's
* requests.
*/
public class MediaSessionProviderService extends Service {
private static final String TAG = "MediaSessionProviderService";
private Map<String, MediaSession> sessionMap = new HashMap<>();
private RemoteMediaSessionStub sessionBinder;
private TestHandler handler;
@Override
public void onCreate() {
super.onCreate();
sessionBinder = new RemoteMediaSessionStub();
handler = new TestHandler(getMainLooper());
}
@Override
public IBinder onBind(Intent intent) {
if (ACTION_MEDIA2_SESSION.equals(intent.getAction())) {
return sessionBinder;
}
return null;
}
@Override
public void onDestroy() {
for (MediaSession session : sessionMap.values()) {
session.release();
}
}
private class RemoteMediaSessionStub extends IRemoteMediaSession.Stub {
private void runOnHandler(@NonNull TestRunnable runnable) throws RemoteException {
try {
handler.postAndSync(runnable);
} catch (Exception e) {
Log.e(TAG, "Exception thrown while waiting for handler", e);
throw new RemoteException("Unexpected exception");
}
}
private <V> V runOnHandler(@NonNull Callable<V> callable) throws RemoteException {
try {
return handler.postAndSync(callable);
} catch (Exception e) {
Log.e(TAG, "Exception thrown while waiting for handler", e);
throw new RemoteException("Unexpected exception");
}
}
@Override
public void create(String sessionId, Bundle tokenExtras) throws RemoteException {
MediaSession.Builder builder =
new MediaSession.Builder(
MediaSessionProviderService.this,
new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build())
.setId(sessionId);
if (tokenExtras != null) {
builder.setExtras(tokenExtras);
}
switch (sessionId) {
case TEST_GET_SESSION_ACTIVITY:
{
Intent sessionActivity =
new Intent(MediaSessionProviderService.this, MockActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(
MediaSessionProviderService.this,
/* requestCode= */ 0,
sessionActivity,
/* flags= */ 0);
builder.setSessionActivity(pendingIntent);
break;
}
case TEST_CONTROLLER_CALLBACK_SESSION_REJECTS:
{
builder.setSessionCallback(
new MediaSession.SessionCallback() {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(
MediaSession session, ControllerInfo controller) {
return null;
}
});
break;
}
}
runOnHandler(
() -> {
MediaSession session = builder.build();
session.setSessionPositionUpdateDelayMs(0L);
sessionMap.put(sessionId, session);
});
}
////////////////////////////////////////////////////////////////////////////////
// MediaSession methods
////////////////////////////////////////////////////////////////////////////////
@Override
public Bundle getToken(String sessionId) throws RemoteException {
return runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
return session.getToken().toBundle();
});
}
@Override
public Bundle getCompatToken(String sessionId) throws RemoteException {
return runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
return session.getSessionCompat().getSessionToken().toBundle();
});
}
@Override
public void setSessionPositionUpdateDelayMs(String sessionId, long updateDelayMs)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
session.setSessionPositionUpdateDelayMs(updateDelayMs);
});
}
@Override
public void setPlayer(String sessionId, @NonNull Bundle config) throws RemoteException {
runOnHandler(
() -> {
config.setClassLoader(MediaSession.class.getClassLoader());
MediaSession session = sessionMap.get(sessionId);
session.setPlayer(createMockPlayer(config));
});
}
private SessionPlayer createMockPlayer(Bundle config) {
MockPlayer player = new MockPlayer.Builder().build();
player.playerError =
BundleableUtils.fromNullableBundle(
ExoPlaybackException.CREATOR, config.getBundle(KEY_PLAYER_ERROR), player.playerError);
player.currentPosition = config.getLong(KEY_CURRENT_POSITION, player.currentPosition);
player.bufferedPosition = config.getLong(KEY_BUFFERED_POSITION, player.bufferedPosition);
player.bufferedPercentage = config.getInt(KEY_BUFFERED_PERCENTAGE, player.bufferedPercentage);
player.duration = config.getLong(KEY_DURATION, player.duration);
player.totalBufferedDuration =
config.getLong(KEY_TOTAL_BUFFERED_DURATION, player.totalBufferedDuration);
player.currentLiveOffset = config.getLong(KEY_CURRENT_LIVE_OFFSET, player.currentLiveOffset);
player.contentDuration = config.getLong(KEY_CONTENT_DURATION, player.contentDuration);
player.contentPosition = config.getLong(KEY_CONTENT_POSITION, player.contentPosition);
player.contentBufferedPosition =
config.getLong(KEY_CONTENT_BUFFERED_POSITION, player.contentBufferedPosition);
player.isPlayingAd = config.getBoolean(KEY_IS_PLAYING_AD, player.isPlayingAd);
player.currentAdGroupIndex =
config.getInt(KEY_CURRENT_AD_GROUP_INDEX, player.currentAdGroupIndex);
player.currentAdIndexInAdGroup =
config.getInt(KEY_CURRENT_AD_INDEX_IN_AD_GROUP, player.currentAdIndexInAdGroup);
player.playbackParameters =
BundleableUtils.fromNullableBundle(
PlaybackParameters.CREATOR,
config.getBundle(KEY_PLAYBACK_PARAMETERS),
player.playbackParameters);
player.timeline =
BundleableUtils.fromNullableBundle(
Timeline.CREATOR, config.getBundle(KEY_TIMELINE), player.timeline);
player.currentWindowIndex =
config.getInt(KEY_CURRENT_WINDOW_INDEX, player.currentWindowIndex);
player.currentPeriodIndex =
config.getInt(KEY_CURRENT_PERIOD_INDEX, player.currentPeriodIndex);
player.currentMediaItem =
BundleableUtils.fromNullableBundle(
MediaItem.CREATOR, config.getBundle(KEY_MEDIA_ITEM), player.currentMediaItem);
player.playlistMetadata =
BundleableUtils.fromNullableBundle(
MediaMetadata.CREATOR,
config.getBundle(KEY_PLAYLIST_METADATA),
player.playlistMetadata);
player.videoSize =
BundleableUtils.fromNullableBundle(
VideoSize.CREATOR, config.getBundle(KEY_VIDEO_SIZE), player.videoSize);
player.volume = config.getFloat(KEY_VOLUME, player.volume);
player.audioAttributes =
BundleableUtils.fromNullableBundle(
AudioAttributes.CREATOR,
config.getBundle(KEY_AUDIO_ATTRIBUTES),
player.audioAttributes);
player.deviceInfo =
BundleableUtils.fromNullableBundle(
DeviceInfo.CREATOR, config.getBundle(KEY_DEVICE_INFO), player.deviceInfo);
player.deviceVolume = config.getInt(KEY_DEVICE_VOLUME, player.deviceVolume);
player.deviceMuted = config.getBoolean(KEY_DEVICE_MUTED, player.deviceMuted);
player.playWhenReady = config.getBoolean(KEY_PLAY_WHEN_READY, player.playWhenReady);
player.playbackSuppressionReason =
config.getInt(KEY_PLAYBACK_SUPPRESSION_REASON, player.playbackSuppressionReason);
player.playbackState = config.getInt(KEY_PLAYBACK_STATE, player.playbackState);
player.isPlaying = config.getBoolean(KEY_IS_PLAYING, player.isPlaying);
player.isLoading = config.getBoolean(KEY_IS_LOADING, player.isLoading);
player.repeatMode = config.getInt(KEY_REPEAT_MODE, player.repeatMode);
player.shuffleModeEnabled =
config.getBoolean(KEY_SHUFFLE_MODE_ENABLED, player.shuffleModeEnabled);
return player;
}
@Override
public void broadcastCustomCommand(String sessionId, Bundle command, Bundle args)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
session.broadcastCustomCommand(SessionCommand.CREATOR.fromBundle(command), args);
});
}
@Override
@SuppressWarnings("FutureReturnValueIgnored")
public void sendCustomCommand(String sessionId, Bundle controller, Bundle command, Bundle args)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
session.sendCustomCommand(info, SessionCommand.CREATOR.fromBundle(command), args);
});
}
@Override
public void release(String sessionId) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
session.release();
});
}
@Override
public void setAvailableCommands(
String sessionId, Bundle controller, Bundle sessionCommands, Bundle playerCommands)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
session.setAvailableCommands(
info,
SessionCommands.CREATOR.fromBundle(sessionCommands),
Player.Commands.CREATOR.fromBundle(playerCommands));
});
}
@Override
@SuppressWarnings("FutureReturnValueIgnored")
public void setCustomLayout(String sessionId, Bundle controller, List<Bundle> layout)
throws RemoteException {
if (layout == null) {
return;
}
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
ControllerInfo info = MediaTestUtils.getTestControllerInfo(session);
List<CommandButton> buttons = new ArrayList<>();
for (Bundle bundle : layout) {
buttons.add(CommandButton.CREATOR.fromBundle(bundle));
}
session.setCustomLayout(info, buttons);
});
}
////////////////////////////////////////////////////////////////////////////////
// MockPlayer methods
////////////////////////////////////////////////////////////////////////////////
@Override
public void notifyPlayerError(String sessionId, @Nullable Bundle playerErrorBundle)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
@Nullable
ExoPlaybackException playerError =
BundleableUtils.fromNullableBundle(
ExoPlaybackException.CREATOR, playerErrorBundle, player.playerError);
player.notifyPlayerError(playerError);
});
}
@Override
public void setPlayWhenReady(String sessionId, boolean playWhenReady, int reason)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.playWhenReady = playWhenReady;
player.playbackSuppressionReason = reason;
});
}
@Override
public void setPlaybackState(String sessionId, int state) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.playbackState = state;
});
}
@Override
public void setCurrentPosition(String sessionId, long pos) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.currentPosition = pos;
});
}
@Override
public void setBufferedPosition(String sessionId, long pos) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.bufferedPosition = pos;
});
}
@Override
public void setDuration(String sessionId, long duration) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.duration = duration;
});
}
@Override
public void setBufferedPercentage(String sessionId, int bufferedPercentage)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.bufferedPercentage = bufferedPercentage;
});
}
@Override
public void setTotalBufferedDuration(String sessionId, long totalBufferedDuration)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.totalBufferedDuration = totalBufferedDuration;
});
}
@Override
public void setCurrentLiveOffset(String sessionId, long currentLiveOffset)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.currentLiveOffset = currentLiveOffset;
});
}
@Override
public void setContentDuration(String sessionId, long contentDuration) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.contentDuration = contentDuration;
});
}
@Override
public void setContentPosition(String sessionId, long contentPosition) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.contentPosition = contentPosition;
});
}
@Override
public void setContentBufferedPosition(String sessionId, long contentBufferedPosition)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.contentBufferedPosition = contentBufferedPosition;
});
}
@Override
public void setPlaybackParameters(String sessionId, Bundle playbackParametersBundle)
throws RemoteException {
PlaybackParameters playbackParameters =
PlaybackParameters.CREATOR.fromBundle(playbackParametersBundle);
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.setPlaybackParameters(playbackParameters);
});
}
@Override
public void setIsPlayingAd(String sessionId, boolean isPlayingAd) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.isPlayingAd = isPlayingAd;
});
}
@Override
public void setCurrentAdGroupIndex(String sessionId, int currentAdGroupIndex)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.currentAdGroupIndex = currentAdGroupIndex;
});
}
@Override
public void setCurrentAdIndexInAdGroup(String sessionId, int currentAdIndexInAdGroup)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.currentAdIndexInAdGroup = currentAdIndexInAdGroup;
});
}
@Override
public void notifyPlayWhenReadyChanged(
String sessionId, boolean playWhenReady, @Player.PlaybackSuppressionReason int reason)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPlayWhenReadyChanged(playWhenReady, reason);
});
}
@Override
public void notifyPlaybackStateChanged(String sessionId, @Player.State int state)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPlaybackStateChanged(state);
});
}
@Override
public void notifyIsPlayingChanged(String sessionId, boolean isPlaying) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyIsPlayingChanged(isPlaying);
});
}
@Override
public void notifyIsLoadingChanged(String sessionId, boolean isLoading) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyIsLoadingChanged(isLoading);
});
}
@Override
public void notifyPositionDiscontinuity(
String sessionId,
Bundle oldPositionBundle,
Bundle newPositionBundle,
@DiscontinuityReason int reason)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPositionDiscontinuity(
PositionInfo.CREATOR.fromBundle(oldPositionBundle),
PositionInfo.CREATOR.fromBundle(newPositionBundle),
reason);
});
}
@Override
public void notifyPlaybackParametersChanged(String sessionId, Bundle playbackParametersBundle)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPlaybackParametersChanged(
PlaybackParameters.CREATOR.fromBundle(playbackParametersBundle));
});
}
@Override
public void notifyMediaItemTransition(
String sessionId, int index, @Player.MediaItemTransitionReason int reason)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
Timeline.Window window = new Timeline.Window();
@Nullable
MediaItem mediaItem =
index == C.INDEX_UNSET ? null : player.timeline.getWindow(index, window).mediaItem;
player.currentMediaItem = mediaItem;
player.notifyMediaItemTransition(mediaItem, reason);
});
}
@Override
public void notifyAudioAttributesChanged(
@NonNull String sessionId, @NonNull Bundle audioAttributesBundle) throws RemoteException {
AudioAttributes audioAttributes = AudioAttributes.CREATOR.fromBundle(audioAttributesBundle);
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.audioAttributes = audioAttributes;
player.notifyAudioAttributesChanged(audioAttributes);
});
}
////////////////////////////////////////////////////////////////////////////////
// MockPlaylistAgent methods
////////////////////////////////////////////////////////////////////////////////
@Override
public void setTimeline(String sessionId, Bundle timelineBundle) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.timeline = Timeline.CREATOR.fromBundle(timelineBundle);
});
}
@Override
public void createAndSetFakeTimeline(String sessionId, int windowCount) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
List<MediaItem> mediaItems = new ArrayList<>();
for (int windowIndex = 0; windowIndex < windowCount; windowIndex++) {
mediaItems.add(
MediaTestUtils.createConvergedMediaItem(
TestUtils.getMediaIdInFakeTimeline(windowIndex)));
}
player.timeline = new PlaylistTimeline(mediaItems);
});
}
@Override
public void setPlaylistMetadata(String sessionId, Bundle playlistMetadataBundle)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.playlistMetadata = MediaMetadata.CREATOR.fromBundle(playlistMetadataBundle);
});
}
@Override
public void setShuffleModeEnabled(String sessionId, boolean shuffleModeEnabled)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.shuffleModeEnabled = shuffleModeEnabled;
});
}
@Override
public void setRepeatMode(String sessionId, int repeatMode) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.repeatMode = repeatMode;
});
}
@Override
public void setCurrentWindowIndex(String sessionId, int index) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.currentWindowIndex = index;
});
}
@Override
public void notifyAvailableCommandsChanged(String sessionId, Bundle commandsBundle)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyAvailableCommandsChanged(
BundleableUtils.fromNullableBundle(
Player.Commands.CREATOR, commandsBundle, Player.Commands.EMPTY));
});
}
@Override
public void notifyTimelineChanged(String sessionId, @Player.TimelineChangeReason int reason)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyTimelineChanged(reason);
});
}
@Override
public void notifyPlaylistMetadataChanged(String sessionId) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPlaylistMetadataChanged();
});
}
@Override
public void notifyShuffleModeEnabledChanged(String sessionId) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyShuffleModeEnabledChanged();
});
}
@Override
public void notifyRepeatModeChanged(String sessionId) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyRepeatModeChanged();
});
}
@Override
public void notifyVideoSizeChanged(String sessionId, Bundle videoSize) throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
VideoSize videoSizeObj = VideoSize.CREATOR.fromBundle(videoSize);
player.notifyVideoSizeChanged(videoSizeObj);
});
}
@Override
public boolean surfaceExists(String sessionId) throws RemoteException {
return runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
return player.surfaceExists();
});
}
@Override
public void notifyDeviceVolumeChanged(String sessionId, int volume, boolean muted)
throws RemoteException {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.deviceVolume = volume;
player.deviceMuted = muted;
player.notifyDeviceVolumeChanged();
});
}
@Override
public void notifyDeviceInfoChanged(String sessionId, @NonNull Bundle deviceInfoBundle)
throws RemoteException {
DeviceInfo deviceInfo = DeviceInfo.CREATOR.fromBundle(deviceInfoBundle);
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.deviceInfo = deviceInfo;
player.notifyDeviceInfoChanged();
});
}
}
}

View File

@ -0,0 +1,160 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import java.util.ArrayList;
import java.util.List;
/** Utilities for tests. */
public final class MediaTestUtils {
private static final String TAG = "MediaTestUtils";
/** Create a media item with the mediaId for testing purpose. */
public static MediaItem createConvergedMediaItem(String mediaId) {
return new MediaItem.Builder().setMediaId(mediaId).build();
}
public static List<MediaItem> createConvergedMediaItems(int size) {
List<MediaItem> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(createConvergedMediaItem("mediaItem_" + (i + 1)));
}
return list;
}
public static ControllerInfo getTestControllerInfo(MediaSession session) {
if (session == null) {
return null;
}
for (ControllerInfo info : session.getConnectedControllers()) {
if (SUPPORT_APP_PACKAGE_NAME.equals(info.getPackageName())) {
return info;
}
}
Log.e(TAG, "Test controller was not found in connected controllers. session=" + session);
return null;
}
/**
* Create a list of {@link MediaBrowserCompat.MediaItem} for testing purpose.
*
* @param size list size
* @return the newly created playlist
*/
public static List<MediaBrowserCompat.MediaItem> createBrowserItems(int size) {
List<MediaBrowserCompat.MediaItem> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(
new MediaBrowserCompat.MediaItem(
new MediaDescriptionCompat.Builder().setMediaId("browserItem_" + (i + 1)).build(),
/* flags= */ 0));
}
return list;
}
/**
* Create a list of {@link MediaSessionCompat.QueueItem} for testing purpose.
*
* @param size list size
* @return the newly created playlist
*/
public static List<MediaSessionCompat.QueueItem> createQueueItems(int size) {
List<MediaSessionCompat.QueueItem> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(
new MediaSessionCompat.QueueItem(
new MediaDescriptionCompat.Builder().setMediaId("queueItem_" + (i + 1)).build(), i));
}
return list;
}
public static Timeline createTimeline(int windowCount) {
return new PlaylistTimeline(createConvergedMediaItems(/* size= */ windowCount));
}
public static LibraryParams createLibraryParams() {
Bundle extras = new Bundle();
extras.putString("key", "value");
return new LibraryParams.Builder().setExtras(extras).build();
}
public static void assertLibraryParamsEquals(
@Nullable LibraryParams a, @Nullable LibraryParams b) {
if (a == null || b == null) {
assertThat(b).isEqualTo(a);
} else {
assertThat(b.recent).isEqualTo(a.recent);
assertThat(b.offline).isEqualTo(a.offline);
assertThat(b.suggested).isEqualTo(a.suggested);
assertThat(TestUtils.equals(a.extras, b.extras)).isTrue();
}
}
public static void assertLibraryParamsEquals(
@Nullable LibraryParams params, @Nullable Bundle rootExtras) {
if (params == null || rootExtras == null) {
assertThat(params).isNull();
assertThat(rootExtras).isNull();
} else {
assertThat(rootExtras.getBoolean(BrowserRoot.EXTRA_RECENT)).isEqualTo(params.recent);
assertThat(rootExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE)).isEqualTo(params.offline);
assertThat(rootExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED)).isEqualTo(params.suggested);
assertThat(TestUtils.contains(rootExtras, params.extras)).isTrue();
}
}
public static void assertPaginatedListHasIds(
List<MediaItem> paginatedList, List<String> fullIdList, int page, int pageSize) {
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, fullIdList.size());
// Compare the given results with originals.
for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
int relativeIndex = originalIndex - fromIndex;
assertThat(paginatedList.get(relativeIndex).mediaId).isEqualTo(fullIdList.get(originalIndex));
}
}
public static void assertMediaIdEquals(MediaItem expected, MediaItem actual) {
assertThat(actual.mediaId).isEqualTo(expected.mediaId);
}
public static void assertMediaIdEquals(Timeline expected, Timeline actual) {
assertThat(actual.getWindowCount()).isEqualTo(expected.getWindowCount());
Timeline.Window expectedWindow = new Timeline.Window();
Timeline.Window actualWindow = new Timeline.Window();
for (int i = 0; i < expected.getWindowCount(); i++) {
assertMediaIdEquals(
expected.getWindow(i, expectedWindow).mediaItem,
actual.getWindow(i, actualWindow).mediaItem);
}
}
}

View File

@ -0,0 +1,259 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.Callback;
import androidx.annotation.GuardedBy;
import androidx.media.MediaBrowserServiceCompat;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaBrowserServiceCompat;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/** Mock implementation of the media browser service. */
public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
private static final String TAG = "MockMediaBrowserServiceCompat";
private static final Object lock = new Object();
@GuardedBy("lock")
private static volatile MockMediaBrowserServiceCompat instance;
@GuardedBy("lock")
private static volatile Proxy serviceProxy;
private MediaSessionCompat sessionCompat;
private RemoteMediaBrowserServiceCompatStub testBinder;
@Override
public void onCreate() {
super.onCreate();
synchronized (lock) {
instance = this;
}
sessionCompat = new MediaSessionCompat(this, TAG);
sessionCompat.setCallback(new Callback() {});
sessionCompat.setActive(true);
setSessionToken(sessionCompat.getSessionToken());
testBinder = new RemoteMediaBrowserServiceCompatStub();
}
@Override
public void onDestroy() {
super.onDestroy();
sessionCompat.release();
synchronized (lock) {
instance = null;
// Note: Don't reset serviceProxy.
// When a test is finished and its next test is running, this service will be
// destroyed and re-created for the next test. When it happens, onDestroy() may be
// called after the next test's proxy has set because onDestroy() and tests run on
// the different threads.
// So keep serviceProxy for the next test.
}
}
@Override
public IBinder onBind(Intent intent) {
String action = intent.getAction();
if (SERVICE_INTERFACE.equals(action)) {
// for MediaBrowser
return super.onBind(intent);
}
// for RemoteMediaBrowserServiceCompat
return testBinder;
}
public static MockMediaBrowserServiceCompat getInstance() {
synchronized (lock) {
return instance;
}
}
public static void setMediaBrowserServiceProxy(Proxy proxy) {
synchronized (lock) {
serviceProxy = proxy;
}
}
private static boolean isProxyOverridesMethod(String methodName) {
return isProxyOverridesMethod(methodName, -1);
}
private static boolean isProxyOverridesMethod(String methodName, int paramCount) {
synchronized (lock) {
if (serviceProxy == null) {
return false;
}
Method[] methods = serviceProxy.getClass().getMethods();
if (methods == null) {
return false;
}
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName)) {
if (paramCount < 0
|| (methods[i].getParameterTypes() != null
&& methods[i].getParameterTypes().length == paramCount)) {
// Found method. Check if it overrides
return methods[i].getDeclaringClass() != Proxy.class;
}
}
}
return false;
}
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
if (!SUPPORT_APP_PACKAGE_NAME.equals(clientPackageName)) {
// Test only -- reject any other request.
return null;
}
synchronized (lock) {
if (isProxyOverridesMethod("onGetRoot")) {
return serviceProxy.onGetRoot(clientPackageName, clientUid, rootHints);
}
}
return new BrowserRoot("stub", null);
}
@Override
public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
synchronized (lock) {
if (isProxyOverridesMethod("onLoadChildren", 2)) {
serviceProxy.onLoadChildren(parentId, result);
return;
}
}
}
@Override
public void onLoadChildren(String parentId, Result<List<MediaItem>> result, Bundle options) {
synchronized (lock) {
if (isProxyOverridesMethod("onLoadChildren", 3)) {
serviceProxy.onLoadChildren(parentId, result, options);
return;
}
}
super.onLoadChildren(parentId, result, options);
}
@Override
public void onLoadItem(String itemId, Result<MediaItem> result) {
synchronized (lock) {
if (isProxyOverridesMethod("onLoadItem")) {
serviceProxy.onLoadItem(itemId, result);
return;
}
}
super.onLoadItem(itemId, result);
}
@Override
public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
synchronized (lock) {
if (isProxyOverridesMethod("onSearch")) {
serviceProxy.onSearch(query, extras, result);
return;
}
}
super.onSearch(query, extras, result);
}
@Override
public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
synchronized (lock) {
if (isProxyOverridesMethod("onCustomAction")) {
serviceProxy.onCustomAction(action, extras, result);
return;
}
}
super.onCustomAction(action, extras, result);
}
/** Proxy for MediaBrowserServiceCompat callbacks */
public static class Proxy {
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
return new BrowserRoot("stub", null);
}
public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {}
public void onLoadChildren(String parentId, Result<List<MediaItem>> result, Bundle options) {}
public void onLoadItem(String itemId, Result<MediaItem> result) {}
public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {}
public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {}
}
private static class RemoteMediaBrowserServiceCompatStub
extends IRemoteMediaBrowserServiceCompat.Stub {
@Override
public void setProxyForTest(String testName) throws RemoteException {
switch (testName) {
case TEST_CONNECT_REJECTED:
setProxyForTestConnectRejected();
break;
case TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE:
setProxyForTestOnChildrenChanged_subscribeAndUnsubscribe();
break;
default:
throw new IllegalArgumentException("Unknown testName: " + testName);
}
}
@Override
public void notifyChildrenChanged(String parentId) throws RemoteException {
getInstance().notifyChildrenChanged(parentId);
}
private void setProxyForTestConnectRejected() {
setMediaBrowserServiceProxy(
new MockMediaBrowserServiceCompat.Proxy() {
@Override
public BrowserRoot onGetRoot(
String clientPackageName, int clientUid, Bundle rootHints) {
return null;
}
});
}
private void setProxyForTestOnChildrenChanged_subscribeAndUnsubscribe() {
setMediaBrowserServiceProxy(
new MockMediaBrowserServiceCompat.Proxy() {
@Override
public void onLoadChildren(
String parentId, Result<List<MediaItem>> result, Bundle options) {
result.sendResult(Collections.emptyList());
}
});
}
}
}

View File

@ -0,0 +1,405 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
import static com.google.android.exoplayer2.session.MediaTestUtils.assertLibraryParamsEquals;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.LONG_LIST_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_ERROR;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_EXTRAS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_TIME_IN_MS;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.Service;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import com.google.android.exoplayer2.session.vct.common.TestHandler;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
/** A mock MediaLibraryService */
public class MockMediaLibraryService extends MediaLibraryService {
/** ID of the session that this service will create. */
public static final String ID = "TestLibrary";
// TODO(b/180293668): Set browsable property to the media metadata.
public static final MediaItem ROOT_ITEM =
new MediaItem.Builder()
.setMediaId(ROOT_ID)
.setMediaMetadata(new MediaMetadata.Builder().build())
.build();
public static final LibraryParams ROOT_PARAMS =
new LibraryParams.Builder().setExtras(ROOT_EXTRAS).build();
private static final LibraryParams NOTIFY_CHILDREN_CHANGED_PARAMS =
new LibraryParams.Builder().setExtras(NOTIFY_CHILDREN_CHANGED_EXTRAS).build();
private static final String TAG = "MockMediaLibrarySvc2";
@GuardedBy("MockMediaLibraryService.class")
private static boolean assertLibraryParams;
@GuardedBy("MockMediaLibraryService.class")
private static LibraryParams expectedParams;
MediaLibrarySession session;
TestHandler handler;
HandlerThread handlerThread;
@Override
public void onCreate() {
TestServiceRegistry.getInstance().setServiceInstance(this);
super.onCreate();
handlerThread = new HandlerThread(TAG);
handlerThread.start();
handler = new TestHandler(handlerThread.getLooper());
}
@Override
public void onDestroy() {
super.onDestroy();
synchronized (MockMediaLibraryService.class) {
assertLibraryParams = false;
expectedParams = null;
}
TestServiceRegistry.getInstance().cleanUp();
if (Build.VERSION.SDK_INT >= 18) {
handler.getLooper().quitSafely();
} else {
handler.getLooper().quit();
}
}
@Override
public MediaLibrarySession onGetSession(@NonNull ControllerInfo controllerInfo) {
TestServiceRegistry registry = TestServiceRegistry.getInstance();
TestServiceRegistry.OnGetSessionHandler onGetSessionHandler = registry.getOnGetSessionHandler();
if (onGetSessionHandler != null) {
return (MediaLibrarySession) onGetSessionHandler.onGetSession(controllerInfo);
}
MockPlayer player =
new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
MediaLibrarySessionCallback callback = registry.getSessionCallback();
session =
new MediaLibrarySession.Builder(
MockMediaLibraryService.this,
player,
callback != null ? callback : new TestLibrarySessionCallback())
.setId(ID)
.build();
return session;
}
/**
* This changes the visibility of {@link Service#attachBaseContext(Context)} to public. This is a
* workaround for creating {@link MediaLibrarySession} without starting a service.
*/
@Override
public void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
public static void setAssertLibraryParams(LibraryParams expectedParams) {
synchronized (MockMediaLibraryService.class) {
assertLibraryParams = true;
MockMediaLibraryService.expectedParams = expectedParams;
}
}
private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
@Override
@Nullable
public MediaSession.ConnectResult onConnect(MediaSession session, ControllerInfo controller) {
if (!SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
return null;
}
MediaSession.ConnectResult connectResult = super.onConnect(session, controller);
SessionCommands.Builder builder =
new SessionCommands.Builder(connectResult.availableSessionCommands);
builder.add(new SessionCommand(CUSTOM_ACTION, null));
builder.add(new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null));
return new MediaSession.ConnectResult(builder.build(), connectResult.availablePlayerCommands);
}
@Override
@NonNull
public ListenableFuture<LibraryResult> onGetLibraryRoot(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
LibraryParams params) {
assertLibraryParams(params);
return new LibraryResult(RESULT_SUCCESS, ROOT_ITEM, ROOT_PARAMS).asFuture();
}
@Override
@NonNull
public ListenableFuture<LibraryResult> onGetItem(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
@NonNull String mediaId) {
switch (mediaId) {
case MEDIA_ID_GET_ITEM:
return new LibraryResult(
RESULT_SUCCESS, createPlayableMediaItem(mediaId), /* params= */ null)
.asFuture();
case MEDIA_ID_GET_NULL_ITEM:
return new LibraryResult(RESULT_SUCCESS).asFuture();
}
return new LibraryResult(RESULT_ERROR_BAD_VALUE).asFuture();
}
@Override
@NonNull
public ListenableFuture<LibraryResult> onGetChildren(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
@NonNull String parentId,
int page,
int pageSize,
LibraryParams params) {
assertLibraryParams(params);
if (PARENT_ID.equals(parentId)) {
return new LibraryResult(
RESULT_SUCCESS,
getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize),
/* params= */ null)
.asFuture();
} else if (PARENT_ID_LONG_LIST.equals(parentId)) {
List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
for (int i = 0; i < LONG_LIST_COUNT; i++) {
list.add(createPlayableMediaItem(TestUtils.getMediaIdInFakeTimeline(i)));
}
return new LibraryResult(RESULT_SUCCESS, list, /* params= */ null).asFuture();
} else if (PARENT_ID_ERROR.equals(parentId)) {
return new LibraryResult(RESULT_ERROR_BAD_VALUE).asFuture();
}
// Includes the case of PARENT_ID_NO_CHILDREN.
return new LibraryResult(RESULT_SUCCESS, Collections.emptyList(), /* params= */ null)
.asFuture();
}
@Override
@SuppressWarnings("FutureReturnValueIgnored")
@NonNull
public ListenableFuture<LibraryResult> onSearch(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
@NonNull String query,
LibraryParams params) {
assertLibraryParams(params);
if (SEARCH_QUERY.equals(query)) {
MockMediaLibraryService.this.session.notifySearchResultChanged(
browser, query, SEARCH_RESULT_COUNT, params);
} else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
MockMediaLibraryService.this.session.notifySearchResultChanged(
browser, query, LONG_LIST_COUNT, params);
} else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
// Searching takes some time. Notify after 5 seconds.
Executors.newSingleThreadScheduledExecutor()
.schedule(
new Runnable() {
@Override
public void run() {
MockMediaLibraryService.this.session.notifySearchResultChanged(
browser, query, SEARCH_RESULT_COUNT, params);
}
},
SEARCH_TIME_IN_MS,
MILLISECONDS);
} else {
// SEARCH_QUERY_EMPTY_RESULT and SEARCH_QUERY_ERROR will be handled here.
MockMediaLibraryService.this.session.notifySearchResultChanged(browser, query, 0, params);
}
return new LibraryResult(RESULT_SUCCESS).asFuture();
}
@Override
@NonNull
public ListenableFuture<LibraryResult> onGetSearchResult(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
@NonNull String query,
int page,
int pageSize,
LibraryParams params) {
assertLibraryParams(params);
if (SEARCH_QUERY.equals(query)) {
return new LibraryResult(
RESULT_SUCCESS,
getPaginatedResult(SEARCH_RESULT, page, pageSize),
/* params= */ null)
.asFuture();
} else if (SEARCH_QUERY_LONG_LIST.equals(query)) {
List<MediaItem> list = new ArrayList<>(LONG_LIST_COUNT);
for (int i = 0; i < LONG_LIST_COUNT; i++) {
list.add(createPlayableMediaItem(TestUtils.getMediaIdInFakeTimeline(i)));
}
return new LibraryResult(RESULT_SUCCESS, list, /* params= */ null).asFuture();
} else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
return new LibraryResult(RESULT_SUCCESS, Collections.emptyList(), /* params= */ null)
.asFuture();
} else {
// SEARCH_QUERY_ERROR will be handled here.
return new LibraryResult(RESULT_ERROR_BAD_VALUE).asFuture();
}
}
@Override
@NonNull
public ListenableFuture<LibraryResult> onSubscribe(
@NonNull MediaLibrarySession session,
@NonNull ControllerInfo browser,
@NonNull String parentId,
LibraryParams params) {
assertLibraryParams(params);
String unsubscribedId = "unsubscribedId";
switch (parentId) {
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL:
MockMediaLibraryService.this.session.notifyChildrenChanged(
parentId, NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, NOTIFY_CHILDREN_CHANGED_PARAMS);
return new LibraryResult(RESULT_SUCCESS).asFuture();
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE:
MockMediaLibraryService.this.session.notifyChildrenChanged(
MediaTestUtils.getTestControllerInfo(MockMediaLibraryService.this.session),
parentId,
NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
NOTIFY_CHILDREN_CHANGED_PARAMS);
return new LibraryResult(RESULT_SUCCESS).asFuture();
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID:
MockMediaLibraryService.this.session.notifyChildrenChanged(
unsubscribedId, NOTIFY_CHILDREN_CHANGED_ITEM_COUNT, NOTIFY_CHILDREN_CHANGED_PARAMS);
return new LibraryResult(RESULT_SUCCESS).asFuture();
case SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID:
MockMediaLibraryService.this.session.notifyChildrenChanged(
MediaTestUtils.getTestControllerInfo(MockMediaLibraryService.this.session),
unsubscribedId,
NOTIFY_CHILDREN_CHANGED_ITEM_COUNT,
NOTIFY_CHILDREN_CHANGED_PARAMS);
return new LibraryResult(RESULT_SUCCESS).asFuture();
}
return new LibraryResult(RESULT_ERROR_BAD_VALUE).asFuture();
}
@Override
@NonNull
public ListenableFuture<SessionResult> onCustomCommand(
@NonNull MediaSession session,
@NonNull ControllerInfo controller,
@NonNull SessionCommand sessionCommand,
Bundle args) {
switch (sessionCommand.customAction) {
case CUSTOM_ACTION:
return new SessionResult(RESULT_SUCCESS, CUSTOM_ACTION_EXTRAS).asFuture();
case CUSTOM_ACTION_ASSERT_PARAMS:
LibraryParams params =
BundleableUtils.fromNullableBundle(
LibraryParams.CREATOR, args.getBundle(CUSTOM_ACTION_ASSERT_PARAMS));
setAssertLibraryParams(params);
return new SessionResult(RESULT_SUCCESS).asFuture();
}
return new SessionResult(RESULT_ERROR_BAD_VALUE).asFuture();
}
private void assertLibraryParams(LibraryParams params) {
synchronized (MockMediaLibraryService.class) {
if (assertLibraryParams) {
assertLibraryParamsEquals(expectedParams, params);
}
}
}
}
private List<MediaItem> getPaginatedResult(List<String> items, int page, int pageSize) {
if (items == null) {
return null;
} else if (items.size() == 0) {
return new ArrayList<>();
}
int totalItemCount = items.size();
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, totalItemCount);
List<String> paginatedMediaIdList = new ArrayList<>();
try {
// The case of (fromIndex >= totalItemCount) will throw exception below.
paginatedMediaIdList = items.subList(fromIndex, toIndex);
} catch (IndexOutOfBoundsException | IllegalArgumentException e) {
Log.d(
TAG,
"Result is empty for given pagination arguments: totalItemCount="
+ totalItemCount
+ ", page="
+ page
+ ", pageSize="
+ pageSize,
e);
}
// Create a list of MediaItem from the list of media IDs.
List<MediaItem> result = new ArrayList<>();
for (int i = 0; i < paginatedMediaIdList.size(); i++) {
result.add(createPlayableMediaItem(paginatedMediaIdList.get(i)));
}
return result;
}
private static MediaItem createPlayableMediaItem(String mediaId) {
// TODO(b/180293668): Set playable property to the media metadata.
MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build();
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import android.os.Build;
import android.os.HandlerThread;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
/** A mock MediaSessionService */
public class MockMediaSessionService extends MediaSessionService {
/** ID of the session that this service will create. */
public static final String ID = "TestSession";
public MediaSession session;
private HandlerThread handlerThread;
@Override
public void onCreate() {
TestServiceRegistry.getInstance().setServiceInstance(this);
super.onCreate();
handlerThread = new HandlerThread("MockMediaSessionService");
handlerThread.start();
}
@Override
public void onDestroy() {
super.onDestroy();
TestServiceRegistry.getInstance().cleanUp();
if (Build.VERSION.SDK_INT >= 18) {
handlerThread.quitSafely();
} else {
handlerThread.quit();
}
}
@Override
public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
TestServiceRegistry registry = TestServiceRegistry.getInstance();
TestServiceRegistry.OnGetSessionHandler onGetSessionHandler = registry.getOnGetSessionHandler();
if (onGetSessionHandler != null) {
return onGetSessionHandler.onGetSession(controllerInfo);
}
if (session == null) {
MediaSession.SessionCallback callback = registry.getSessionCallback();
MockPlayer player =
new MockPlayer.Builder().setApplicationLooper(handlerThread.getLooper()).build();
session =
new MediaSession.Builder(MockMediaSessionService.this, player)
.setId(ID)
.setSessionCallback(callback != null ? callback : new TestSessionCallback())
.build();
}
return session;
}
private static class TestSessionCallback extends MediaSession.SessionCallback {
@Nullable
@Override
public MediaSession.ConnectResult onConnect(MediaSession session, ControllerInfo controller) {
if (TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) {
return super.onConnect(session, controller);
}
return null;
}
}
}

View File

@ -0,0 +1,870 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWithAllCommands;
import android.os.Looper;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArraySet;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/** A mock implementation of {@link SessionPlayer} for testing. */
public class MockPlayer implements SessionPlayer {
@NonNull public final CountDownLatch countDownLatch;
private final boolean changePlayerStateWithTransportControl;
@NonNull private final Looper applicationLooper;
private final ArraySet<PlayerCallback> callbacks = new ArraySet<>();
@Nullable ExoPlaybackException playerError;
@NonNull public AudioAttributes audioAttributes;
public long seekPositionMs;
public int seekWindowIndex;
public long currentPosition;
public long bufferedPosition;
public long duration;
public int bufferedPercentage;
public long totalBufferedDuration;
public long currentLiveOffset;
public long contentPosition;
public long contentDuration;
public long contentBufferedPosition;
public boolean isPlayingAd;
public int currentAdGroupIndex;
public int currentAdIndexInAdGroup;
@Nullable public PlaybackParameters playbackParameters;
public Timeline timeline;
public List<MediaItem> mediaItems;
public boolean resetPosition;
public int startWindowIndex;
public long startPositionMs;
public MediaMetadata playlistMetadata;
@Nullable public MediaItem currentMediaItem;
public int index;
public int fromIndex;
public int toIndex;
public int newIndex;
public int currentPeriodIndex;
public int currentWindowIndex;
@RepeatMode public int repeatMode;
public boolean shuffleModeEnabled;
public VideoSize videoSize;
@Nullable public Surface surface;
public float volume;
public DeviceInfo deviceInfo;
public int deviceVolume;
public boolean deviceMuted;
public boolean playWhenReady;
@PlaybackSuppressionReason public int playbackSuppressionReason;
@State public int playbackState;
public boolean isPlaying;
public boolean isLoading;
public Commands commands;
public boolean playCalled;
public boolean pauseCalled;
public boolean prepareCalled;
public boolean stopCalled;
public boolean releaseCalled;
public boolean seekToDefaultPositionCalled;
public boolean seekToDefaultPositionWithWindowIndexCalled;
public boolean seekToCalled;
public boolean seekToWithWindowIndexCalled;
public boolean setPlaybackSpeedCalled;
public boolean setPlaybackParametersCalled;
public boolean setMediaItemsCalled;
public boolean setPlaylistMetadataCalled;
public boolean addMediaItemsCalled;
public boolean removeMediaItemsCalled;
public boolean moveMediaItemsCalled;
public boolean previousCalled;
public boolean nextCalled;
public boolean setRepeatModeCalled;
public boolean setShuffleModeCalled;
public boolean setVolumeCalled;
public boolean setDeviceVolumeCalled;
public boolean increaseDeviceVolumeCalled;
public boolean decreaseDeviceVolumeCalled;
public boolean setDeviceMutedCalled;
public boolean setPlayWhenReadyCalled;
private MockPlayer(Builder builder) {
countDownLatch = new CountDownLatch(builder.latchCount);
changePlayerStateWithTransportControl = builder.changePlayerStateWithTransportControl;
applicationLooper = builder.applicationLooper;
playbackParameters = PlaybackParameters.DEFAULT;
// Sets default audio attributes to prevent setVolume() from being called with the play().
audioAttributes = AudioAttributes.DEFAULT;
timeline = Timeline.EMPTY;
playlistMetadata = MediaMetadata.EMPTY;
index = C.INDEX_UNSET;
fromIndex = C.INDEX_UNSET;
toIndex = C.INDEX_UNSET;
currentPeriodIndex = C.INDEX_UNSET;
currentWindowIndex = C.INDEX_UNSET;
repeatMode = Player.REPEAT_MODE_OFF;
videoSize = VideoSize.UNKNOWN;
volume = 1.0f;
deviceInfo = DeviceInfo.UNKNOWN;
seekPositionMs = C.TIME_UNSET;
seekWindowIndex = C.INDEX_UNSET;
currentPosition = C.TIME_UNSET;
duration = C.TIME_UNSET;
currentLiveOffset = C.TIME_UNSET;
contentDuration = C.TIME_UNSET;
contentPosition = C.TIME_UNSET;
contentBufferedPosition = C.TIME_UNSET;
currentAdGroupIndex = C.INDEX_UNSET;
currentAdIndexInAdGroup = C.INDEX_UNSET;
// Invalid playbackState throws assertion error.
playbackState = Player.STATE_IDLE;
commands = createPlayerCommandsWithAllCommands();
}
@Override
public void release() {
releaseCalled = true;
}
@Override
public void stop() {
stopCalled = true;
countDownLatch.countDown();
}
@Deprecated
@Override
public void stop(boolean reset) {
throw new UnsupportedOperationException();
}
@Override
public void addListener(@NonNull PlayerCallback callback) {
callbacks.add(callback);
}
@Override
public void removeListener(@NonNull PlayerCallback callback) {
callbacks.remove(callback);
}
@Override
@Nullable
public ExoPlaybackException getPlayerError() {
return playerError;
}
@Override
public void play() {
playCalled = true;
countDownLatch.countDown();
if (changePlayerStateWithTransportControl) {
notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}
}
@Override
public void pause() {
pauseCalled = true;
countDownLatch.countDown();
if (changePlayerStateWithTransportControl) {
notifyPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}
}
@Override
public void prepare() {
prepareCalled = true;
countDownLatch.countDown();
if (changePlayerStateWithTransportControl) {
notifyPlaybackStateChanged(Player.STATE_READY);
}
}
@Override
public void seekToDefaultPosition() {
seekToDefaultPositionCalled = true;
countDownLatch.countDown();
}
@Override
public void seekToDefaultPosition(int windowIndex) {
seekToDefaultPositionWithWindowIndexCalled = true;
seekWindowIndex = windowIndex;
countDownLatch.countDown();
}
@Override
public void seekTo(long positionMs) {
seekToCalled = true;
seekPositionMs = positionMs;
countDownLatch.countDown();
}
@Override
public void seekTo(int windowIndex, long positionMs) {
seekToWithWindowIndexCalled = true;
seekWindowIndex = windowIndex;
seekPositionMs = positionMs;
countDownLatch.countDown();
}
@Override
public long getCurrentPosition() {
return currentPosition;
}
@Override
public long getBufferedPosition() {
return bufferedPosition;
}
@Override
public boolean isPlayingAd() {
return isPlayingAd;
}
@Override
public int getCurrentAdGroupIndex() {
return currentAdGroupIndex;
}
@Override
public int getCurrentAdIndexInAdGroup() {
return currentAdIndexInAdGroup;
}
@Override
public PlaybackParameters getPlaybackParameters() {
return playbackParameters == null ? PlaybackParameters.DEFAULT : playbackParameters;
}
@Override
public long getDuration() {
return duration;
}
@Override
public int getBufferedPercentage() {
return bufferedPercentage;
}
@Override
public long getTotalBufferedDuration() {
return totalBufferedDuration;
}
@Override
public long getCurrentLiveOffset() {
return currentLiveOffset;
}
@Override
public long getContentDuration() {
return contentDuration;
}
@Override
public long getContentPosition() {
return contentPosition;
}
@Override
public long getContentBufferedPosition() {
return contentBufferedPosition;
}
@Override
public boolean isCommandAvailable(int command) {
return commands.contains(command);
}
@Override
public Commands getAvailableCommands() {
return commands;
}
public void notifyPlayerError(@Nullable ExoPlaybackException playerError) {
if (this.playerError == playerError) {
return;
}
this.playerError = playerError;
// TODO(b/184262323): Remove this check when migrating to onPlayerErrorChanged() that takes
// nullable error.
if (playerError == null) {
return;
}
for (PlayerCallback callback : callbacks) {
callback.onPlayerError(playerError);
}
}
public void notifyAvailableCommandsChanged(Commands commands) {
if (Util.areEqual(this.commands, commands)) {
return;
}
this.commands = commands;
for (PlayerCallback callback : callbacks) {
callback.onAvailableCommandsChanged(commands);
}
}
public void notifyPlayWhenReadyChanged(
boolean playWhenReady, @PlaybackSuppressionReason int reason) {
boolean playWhenReadyChanged = (this.playWhenReady != playWhenReady);
boolean playbackSuppressionReasonChanged = (this.playbackSuppressionReason != reason);
if (!playWhenReadyChanged && !playbackSuppressionReasonChanged) {
return;
}
this.playWhenReady = playWhenReady;
this.playbackSuppressionReason = reason;
for (PlayerCallback callback : callbacks) {
if (playWhenReadyChanged) {
callback.onPlayWhenReadyChanged(
playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
if (playbackSuppressionReasonChanged) {
callback.onPlaybackSuppressionReasonChanged(reason);
}
}
}
public void notifyPlaybackStateChanged(@State int playbackState) {
if (this.playbackState == playbackState) {
return;
}
this.playbackState = playbackState;
for (PlayerCallback callback : callbacks) {
callback.onPlaybackStateChanged(playbackState);
}
}
public void notifyIsPlayingChanged(boolean isPlaying) {
if (this.isPlaying == isPlaying) {
return;
}
this.isPlaying = isPlaying;
for (PlayerCallback callback : callbacks) {
callback.onIsPlayingChanged(isPlaying);
}
}
public void notifyIsLoadingChanged(boolean isLoading) {
if (this.isLoading == isLoading) {
return;
}
this.isLoading = isLoading;
for (PlayerCallback callback : callbacks) {
callback.onIsLoadingChanged(isLoading);
}
}
public void notifyPositionDiscontinuity(
PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
for (PlayerCallback callback : callbacks) {
callback.onPositionDiscontinuity(oldPosition, newPosition, reason);
}
}
public void notifyMediaItemTransition(
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
for (PlayerCallback callback : callbacks) {
callback.onMediaItemTransition(mediaItem, reason);
}
}
public void notifyPlaybackParametersChanged(PlaybackParameters playbackParameters) {
if (Util.areEqual(this.playbackParameters, playbackParameters)) {
return;
}
this.playbackParameters = playbackParameters;
for (PlayerCallback callback : callbacks) {
callback.onPlaybackParametersChanged(
playbackParameters == null ? PlaybackParameters.DEFAULT : playbackParameters);
}
}
public void notifyAudioAttributesChanged(@NonNull AudioAttributes attrs) {
for (PlayerCallback callback : callbacks) {
callback.onAudioAttributesChanged(attrs);
}
}
@Override
@NonNull
public AudioAttributes getAudioAttributes() {
return audioAttributes;
}
@Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
setPlaybackParametersCalled = true;
this.playbackParameters = playbackParameters;
countDownLatch.countDown();
}
@Override
public void setPlaybackSpeed(float speed) {
setPlaybackSpeedCalled = true;
playbackParameters = new PlaybackParameters(speed);
countDownLatch.countDown();
}
@Override
public float getVolume() {
return volume;
}
@Override
public void setVolume(float volume) {
setVolumeCalled = true;
this.volume = volume;
countDownLatch.countDown();
}
@Override
@NonNull
public DeviceInfo getDeviceInfo() {
return deviceInfo;
}
@Override
public int getDeviceVolume() {
return deviceVolume;
}
@Override
public boolean isDeviceMuted() {
return deviceMuted;
}
@Override
public void setDeviceVolume(int volume) {
setDeviceVolumeCalled = true;
deviceVolume = volume;
countDownLatch.countDown();
}
@Override
public void increaseDeviceVolume() {
increaseDeviceVolumeCalled = true;
countDownLatch.countDown();
}
@Override
public void decreaseDeviceVolume() {
decreaseDeviceVolumeCalled = true;
countDownLatch.countDown();
}
@Override
public void setDeviceMuted(boolean muted) {
setDeviceMutedCalled = true;
deviceMuted = muted;
countDownLatch.countDown();
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
this.setPlayWhenReadyCalled = true;
this.playWhenReady = playWhenReady;
countDownLatch.countDown();
}
@Override
public boolean getPlayWhenReady() {
return playWhenReady;
}
@Override
@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
return playbackSuppressionReason;
}
@Override
@State
public int getPlaybackState() {
return playbackState;
}
@Override
public boolean isPlaying() {
return isPlaying;
}
@Override
public boolean isLoading() {
return isLoading;
}
/////////////////////////////////////////////////////////////////////////////////
// Playlist APIs
/////////////////////////////////////////////////////////////////////////////////
@Override
public Object getCurrentManifest() {
throw new UnsupportedOperationException();
}
@Override
public Timeline getCurrentTimeline() {
return timeline;
}
@Override
public void setMediaItem(MediaItem mediaItem) {
setMediaItems(Collections.singletonList(mediaItem));
}
@Override
public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
setMediaItems(Collections.singletonList(mediaItem), /* startWindowIndex= */ 0, startPositionMs);
}
@Override
public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
setMediaItems(Collections.singletonList(mediaItem), resetPosition);
}
@Override
public void setMediaItems(List<MediaItem> mediaItems) {
setMediaItems(mediaItems, /* resetPosition= */ true);
}
@Override
public void setMediaItems(@NonNull List<MediaItem> mediaItems, boolean resetPosition) {
setMediaItemsCalled = true;
this.mediaItems = mediaItems;
this.resetPosition = resetPosition;
countDownLatch.countDown();
}
@Override
public void setMediaItems(
List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs) {
setMediaItemsCalled = true;
this.mediaItems = mediaItems;
this.startWindowIndex = startWindowIndex;
this.startPositionMs = startPositionMs;
countDownLatch.countDown();
}
@Override
public MediaMetadata getPlaylistMetadata() {
return playlistMetadata;
}
@Override
public void setPlaylistMetadata(MediaMetadata playlistMetadata) {
setPlaylistMetadataCalled = true;
this.playlistMetadata = playlistMetadata;
countDownLatch.countDown();
}
@Override
public boolean isCurrentWindowDynamic() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCurrentWindowLive() {
throw new UnsupportedOperationException();
}
@Override
public boolean isCurrentWindowSeekable() {
throw new UnsupportedOperationException();
}
@Override
@Nullable
public MediaItem getCurrentMediaItem() {
return currentMediaItem;
}
@Override
public int getMediaItemCount() {
throw new UnsupportedOperationException();
}
@Override
public MediaItem getMediaItemAt(int index) {
throw new UnsupportedOperationException();
}
@Override
public int getCurrentPeriodIndex() {
return currentPeriodIndex;
}
@Override
public int getCurrentWindowIndex() {
return currentWindowIndex;
}
@Override
public int getPreviousWindowIndex() {
throw new UnsupportedOperationException();
}
@Override
public int getNextWindowIndex() {
throw new UnsupportedOperationException();
}
@Override
public void addMediaItem(MediaItem mediaItem) {
addMediaItems(Collections.singletonList(mediaItem));
}
@Override
public void addMediaItem(int index, MediaItem mediaItem) {
addMediaItems(index, Collections.singletonList(mediaItem));
}
@Override
public void addMediaItems(List<MediaItem> mediaItems) {
addMediaItems(/* index= */ Integer.MAX_VALUE, mediaItems);
}
@Override
public void addMediaItems(int index, List<MediaItem> mediaItems) {
addMediaItemsCalled = true;
this.index = index;
this.mediaItems = mediaItems;
countDownLatch.countDown();
}
@Override
public void removeMediaItem(int index) {
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
}
@Override
public void removeMediaItems(int fromIndex, int toIndex) {
removeMediaItemsCalled = true;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
countDownLatch.countDown();
}
@Override
public void clearMediaItems() {
removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ Integer.MAX_VALUE);
}
@Override
public void moveMediaItem(int currentIndex, int newIndex) {
if (currentIndex != newIndex) {
moveMediaItems(/* fromIndex= */ currentIndex, /* toIndex= */ currentIndex + 1, newIndex);
}
}
@Override
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
moveMediaItemsCalled = true;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
this.newIndex = newIndex;
countDownLatch.countDown();
}
@Override
public boolean hasPrevious() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasNext() {
throw new UnsupportedOperationException();
}
@Override
public void previous() {
previousCalled = true;
countDownLatch.countDown();
}
@Override
public void next() {
nextCalled = true;
countDownLatch.countDown();
}
@Override
public int getRepeatMode() {
return repeatMode;
}
@Override
public void setRepeatMode(int repeatMode) {
setRepeatModeCalled = true;
this.repeatMode = repeatMode;
countDownLatch.countDown();
}
@Override
public boolean getShuffleModeEnabled() {
return shuffleModeEnabled;
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
setShuffleModeCalled = true;
this.shuffleModeEnabled = shuffleModeEnabled;
countDownLatch.countDown();
}
public void notifyShuffleModeEnabledChanged() {
boolean shuffleModeEnabled = this.shuffleModeEnabled;
for (PlayerCallback callback : callbacks) {
callback.onShuffleModeEnabledChanged(shuffleModeEnabled);
}
}
public void notifyRepeatModeChanged() {
int repeatMode = this.repeatMode;
for (PlayerCallback callback : callbacks) {
callback.onRepeatModeChanged(repeatMode);
}
}
public void notifyTimelineChanged(@TimelineChangeReason int reason) {
Timeline timeline = this.timeline;
for (PlayerCallback callback : callbacks) {
callback.onTimelineChanged(timeline, reason);
}
}
public void notifyPlaylistMetadataChanged() {
MediaMetadata metadata = playlistMetadata;
for (PlayerCallback callback : callbacks) {
callback.onPlaylistMetadataChanged(metadata);
}
}
@Override
@NonNull
public VideoSize getVideoSize() {
if (videoSize == null) {
videoSize = VideoSize.UNKNOWN;
}
return videoSize;
}
public void notifyVideoSizeChanged(@NonNull VideoSize videoSize) {
for (PlayerCallback callback : callbacks) {
callback.onVideoSizeChanged(videoSize);
}
}
@Override
public void clearVideoSurface() {
surface = null;
}
@Override
public void clearVideoSurface(@Nullable Surface surface) {
if (surface != null && surface == this.surface) {
this.surface = null;
}
}
@Override
public void setVideoSurface(@Nullable Surface surface) {
this.surface = surface;
}
public boolean surfaceExists() {
return surface != null;
}
public void notifyDeviceVolumeChanged() {
for (PlayerCallback callback : callbacks) {
callback.onDeviceVolumeChanged(deviceVolume, deviceMuted);
}
}
public void notifyDeviceInfoChanged() {
for (PlayerCallback callback : callbacks) {
callback.onDeviceInfoChanged(deviceInfo);
}
}
@Override
@NonNull
public Looper getApplicationLooper() {
return applicationLooper;
}
/** Builder for {@link MockPlayer}. */
public static final class Builder {
private int latchCount;
private boolean changePlayerStateWithTransportControl;
private Looper applicationLooper;
public Builder() {
applicationLooper = Util.getCurrentOrMainLooper();
}
public Builder setLatchCount(int latchCount) {
this.latchCount = latchCount;
return this;
}
public Builder setChangePlayerStateWithTransportControl(
boolean changePlayerStateWithTransportControl) {
this.changePlayerStateWithTransportControl = changePlayerStateWithTransportControl;
return this;
}
public Builder setApplicationLooper(Looper applicationLooper) {
this.applicationLooper = applicationLooper;
return this;
}
public MockPlayer build() {
return new MockPlayer(this);
}
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2021 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
/** A {@link Timeline} implementation for testing session/controller(s). */
public class PlaylistTimeline extends Timeline {
private static final long DEFAULT_DURATION_MS = 100;
private final ImmutableList<MediaItem> mediaItems;
private final int[] shuffledIndices;
private final int[] indicesInShuffled;
public PlaylistTimeline(List<MediaItem> mediaItems) {
this(mediaItems, createUnshuffledIndices(mediaItems.size()));
}
public PlaylistTimeline(List<MediaItem> mediaItems, int[] shuffledIndices) {
checkState(mediaItems.size() == shuffledIndices.length);
this.mediaItems = ImmutableList.copyOf(mediaItems);
this.shuffledIndices = Arrays.copyOf(shuffledIndices, shuffledIndices.length);
indicesInShuffled = new int[shuffledIndices.length];
for (int i = 0; i < shuffledIndices.length; i++) {
indicesInShuffled[shuffledIndices[i]] = i;
}
}
@Override
public int getWindowCount() {
return mediaItems.size();
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
window.set(
/* uid= */ 0,
mediaItems.get(windowIndex),
/* manifest= */ null,
/* presentationStartTimeMs= */ 0,
/* windowStartTimeMs= */ 0,
/* elapsedRealtimeEpochOffsetMs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* liveConfiguration= */ null,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.msToUs(DEFAULT_DURATION_MS),
/* firstPeriodIndex= */ windowIndex,
/* lastPeriodIndex= */ windowIndex,
/* positionInFirstPeriodUs= */ 0);
window.isPlaceholder = false;
return window;
}
@Override
public int getNextWindowIndex(
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
if (repeatMode == Player.REPEAT_MODE_ONE) {
return windowIndex;
}
if (windowIndex == getLastWindowIndex(shuffleModeEnabled)) {
return repeatMode == Player.REPEAT_MODE_ALL
? getFirstWindowIndex(shuffleModeEnabled)
: C.INDEX_UNSET;
}
return shuffleModeEnabled
? shuffledIndices[indicesInShuffled[windowIndex] + 1]
: windowIndex + 1;
}
@Override
public int getPreviousWindowIndex(
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
if (repeatMode == Player.REPEAT_MODE_ONE) {
return windowIndex;
}
if (windowIndex == getFirstWindowIndex(shuffleModeEnabled)) {
return repeatMode == Player.REPEAT_MODE_ALL
? getLastWindowIndex(shuffleModeEnabled)
: C.INDEX_UNSET;
}
return shuffleModeEnabled
? shuffledIndices[indicesInShuffled[windowIndex] - 1]
: windowIndex - 1;
}
@Override
public int getLastWindowIndex(boolean shuffleModeEnabled) {
if (isEmpty()) {
return C.INDEX_UNSET;
}
return shuffleModeEnabled ? shuffledIndices[getWindowCount() - 1] : getWindowCount() - 1;
}
@Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) {
if (isEmpty()) {
return C.INDEX_UNSET;
}
return shuffleModeEnabled ? shuffledIndices[0] : 0;
}
@Override
public int getPeriodCount() {
return getWindowCount();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
period.set(
/* id= */ null,
/* uid= */ null,
periodIndex,
C.msToUs(DEFAULT_DURATION_MS),
/* positionInWindowUs= */ 0);
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
throw new UnsupportedOperationException();
}
@Override
public Object getUidOfPeriod(int periodIndex) {
throw new UnsupportedOperationException();
}
private static int[] createUnshuffledIndices(int length) {
int[] indices = new int[length];
for (int i = 0; i < length; i++) {
indices[i] = i;
}
return indices;
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.google.android.exoplayer2.session;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
/**
* Represents remote {@link MediaBrowser} the client app's MediaControllerService. Users can run
* {@link MediaBrowser} methods remotely with this object.
*/
public class RemoteMediaBrowser extends RemoteMediaController {
/**
* Create a {@link MediaBrowser} in the client app. Should NOT be called main thread.
*
* @param waitForConnection true if the remote browser needs to wait for the connection, false
* otherwise.
* @param connectionHints connection hints
*/
public RemoteMediaBrowser(
Context context, SessionToken token, boolean waitForConnection, Bundle connectionHints)
throws RemoteException {
super(context, token, connectionHints, waitForConnection);
}
/** {@link MediaBrowser} methods. */
public LibraryResult getLibraryRoot(@Nullable LibraryParams params) throws RemoteException {
Bundle result = binder.getLibraryRoot(controllerId, BundleableUtils.toNullableBundle(params));
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult subscribe(@NonNull String parentId, @Nullable LibraryParams params)
throws RemoteException {
Bundle result =
binder.subscribe(controllerId, parentId, BundleableUtils.toNullableBundle(params));
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult unsubscribe(@NonNull String parentId) throws RemoteException {
Bundle result = binder.unsubscribe(controllerId, parentId);
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult getChildren(
@NonNull String parentId, int page, int pageSize, @Nullable LibraryParams params)
throws RemoteException {
Bundle result =
binder.getChildren(
controllerId, parentId, page, pageSize, BundleableUtils.toNullableBundle(params));
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult getItem(@NonNull String mediaId) throws RemoteException {
Bundle result = binder.getItem(controllerId, mediaId);
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult search(@NonNull String query, @Nullable LibraryParams params)
throws RemoteException {
Bundle result = binder.search(controllerId, query, BundleableUtils.toNullableBundle(params));
return LibraryResult.CREATOR.fromBundle(result);
}
public LibraryResult getSearchResult(
@NonNull String query, int page, int pageSize, @Nullable LibraryParams params)
throws RemoteException {
Bundle result =
binder.getSearchResult(
controllerId, query, page, pageSize, BundleableUtils.toNullableBundle(params));
return LibraryResult.CREATOR.fromBundle(result);
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Create a {@link MediaBrowser} in the client app. Should be used after successful connection
* through {@link #connect()}.
*
* @param connectionHints connection hints
* @param waitForConnection true if this method needs to wait for the connection,
*/
void create(SessionToken token, Bundle connectionHints, boolean waitForConnection)
throws RemoteException {
binder.create(
/* isBrowser= */ true, controllerId, token.toBundle(), connectionHints, waitForConnection);
}
}

View File

@ -0,0 +1,187 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.media.MediaBrowserCompat;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaBrowserCompat;
import com.google.android.exoplayer2.util.Log;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
/**
* Represents remote {@link MediaBrowserCompat} the client app's MediaBrowserCompatProviderService.
* Users can run {@link MediaBrowserCompat} methods remotely with this object.
*/
public class RemoteMediaBrowserCompat {
private static final String TAG = "RemoteMediaBrowserCompat";
private final String browserId;
private final Context context;
private final CountDownLatch countDownLatch;
private ServiceConnection serviceConnection;
private IRemoteMediaBrowserCompat binder;
/** Create a {@link MediaBrowserCompat} in the client app. Should NOT be called main thread. */
public RemoteMediaBrowserCompat(Context context, ComponentName serviceComponent)
throws RemoteException {
this.context = context;
browserId = UUID.randomUUID().toString();
countDownLatch = new CountDownLatch(1);
serviceConnection = new MyServiceConnection();
if (!connectToService()) {
assertWithMessage("Failed to connect to the MediaBrowserCompatProviderService.").fail();
}
create(serviceComponent);
}
public void cleanUp() throws RemoteException {
disconnect();
disconnectFromService();
}
////////////////////////////////////////////////////////////////////////////////
// MediaBrowserCompat methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connect to the given media browser service.
*
* @param waitForConnection true if the remote browser needs to wait for the connection, false
* otherwise.
*/
public void connect(boolean waitForConnection) throws RemoteException {
binder.connect(browserId, waitForConnection);
}
public void disconnect() throws RemoteException {
binder.disconnect(browserId);
}
public boolean isConnected() throws RemoteException {
return binder.isConnected(browserId);
}
public ComponentName getServiceComponent() throws RemoteException {
return binder.getServiceComponent(browserId);
}
public String getRoot() throws RemoteException {
return binder.getRoot(browserId);
}
public Bundle getExtras() throws RemoteException {
return binder.getExtras(browserId);
}
public Bundle getConnectedSessionToken() throws RemoteException {
return binder.getConnectedSessionToken(browserId);
}
public void subscribe(String parentId, Bundle options) throws RemoteException {
binder.subscribe(browserId, parentId, options);
}
public void unsubscribe(String parentId) throws RemoteException {
binder.unsubscribe(browserId, parentId);
}
public void getItem(String mediaId) throws RemoteException {
binder.getItem(browserId, mediaId);
}
public void search(String query, Bundle extras) throws RemoteException {
binder.search(browserId, query, extras);
}
public void sendCustomAction(String action, Bundle extras) throws RemoteException {
binder.sendCustomAction(browserId, action, extras);
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connects to client app's MediaBrowserCompatProviderService. Should NOT be called main thread.
*
* @return true if connected successfully, false if failed to connect.
*/
private boolean connectToService() {
Intent intent = new Intent(ACTION_MEDIA_BROWSER_COMPAT);
intent.setComponent(MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE);
boolean bound = false;
try {
bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to the MediaBrowserCompatProviderService.", e);
}
if (bound) {
try {
countDownLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", e);
}
}
return binder != null;
}
/** Disconnects from client app's MediaBrowserCompatProviderService. */
private void disconnectFromService() {
if (serviceConnection != null) {
context.unbindService(serviceConnection);
serviceConnection = null;
}
}
/**
* Create a {@link MediaBrowserCompat} in the client app. Should be used after successful
* connection through {@link #connectToService()}.
*/
private void create(ComponentName serviceComponent) throws RemoteException {
binder.create(browserId, serviceComponent);
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Connected to client app's MediaBrowserCompatProviderService.");
binder = IRemoteMediaBrowserCompat.Stub.asInterface(service);
countDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Disconnected from client app's MediaBrowserCompatProviderService.");
}
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2020 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA_BROWSER_SERVICE_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaBrowserServiceCompat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
/** A client to control service app's MockMediaBrowserServiceCompat remotely. */
public class RemoteMediaBrowserServiceCompat {
private final Context context;
private final ServiceConnection serviceConnection;
private final CountDownLatch connectedLatch;
private AtomicReference<IRemoteMediaBrowserServiceCompat> binderRef = new AtomicReference<>();
public RemoteMediaBrowserServiceCompat(@NonNull Context context) {
this.context = context;
serviceConnection = new MyServiceConnection();
connectedLatch = new CountDownLatch(1);
Intent intent = new Intent();
intent.setComponent(MOCK_MEDIA_BROWSER_SERVICE_COMPAT);
boolean bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
throw new IllegalArgumentException("Could not bind to the service");
}
boolean connected;
try {
connected = connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while waiting for connection", e);
}
if (!connected) {
throw new IllegalStateException("ServiceConnection was not made in time");
}
}
public void release() {
context.unbindService(serviceConnection);
}
/** Calls MockMediaBrowserServiceCompat#setMediaBrowserServiceProxy for a specific test case. */
public void setProxyForTest(@NonNull String testName) throws RemoteException {
getBinderOrThrow().setProxyForTest(testName);
}
/** Calls MockMediaBrowserServiceCompat#notifyChildrenChanged. */
public void notifyChildrenChanged(String parentId) throws RemoteException {
getBinderOrThrow().notifyChildrenChanged(parentId);
}
@NonNull
private IRemoteMediaBrowserServiceCompat getBinderOrThrow() {
IRemoteMediaBrowserServiceCompat binder = binderRef.get();
if (binder == null) {
throw new IllegalStateException("service is not connected");
}
return binder;
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binderRef.set(IRemoteMediaBrowserServiceCompat.Stub.asInterface(service));
connectedLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
binderRef.set(null);
}
}
}

View File

@ -0,0 +1,310 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MEDIA2_CONTROLLER_PROVIDER_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.Rating;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaController;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
/**
* Represents remote {@link MediaController} the client app's MediaControllerProviderService. Users
* can run {@link MediaController} methods remotely with this object.
*/
public class RemoteMediaController {
static final String TAG = "RemoteMediaController";
final String controllerId;
final Context context;
final CountDownLatch countDownLatch;
ServiceConnection serviceConnection;
IRemoteMediaController binder;
/**
* Create a {@link MediaController} in the client app. Should NOT be called main thread.
*
* @param connectionHints connection hints
* @param waitForConnection true if the remote controller needs to wait for the connection,
*/
public RemoteMediaController(
Context context, SessionToken token, Bundle connectionHints, boolean waitForConnection)
throws RemoteException {
this.context = context;
controllerId = UUID.randomUUID().toString();
countDownLatch = new CountDownLatch(1);
serviceConnection = new MyServiceConnection();
if (!connect()) {
assertWithMessage("Failed to connect to the MediaControllerProviderService.").fail();
}
create(token, connectionHints, waitForConnection);
}
public void cleanUp() throws RemoteException {
release();
disconnect();
}
////////////////////////////////////////////////////////////////////////////////
// MediaController methods
////////////////////////////////////////////////////////////////////////////////
public SessionToken getConnectedSessionToken() throws RemoteException {
return BundleableUtils.fromNullableBundle(
SessionToken.CREATOR, binder.getConnectedSessionToken(controllerId));
}
public void play() throws RemoteException {
binder.play(controllerId);
}
public void pause() throws RemoteException {
binder.pause(controllerId);
}
public void prepare() throws RemoteException {
binder.prepare(controllerId);
}
public void setPlayWhenReady(boolean playWhenReady) throws RemoteException {
binder.setPlayWhenReady(controllerId, playWhenReady);
}
public void seekToDefaultPosition() throws RemoteException {
binder.seekToDefaultPosition(controllerId);
}
public void seekToDefaultPosition(int windowIndex) throws RemoteException {
binder.seekToDefaultPositionWithWindowIndex(controllerId, windowIndex);
}
public void seekTo(long positionMs) throws RemoteException {
binder.seekTo(controllerId, positionMs);
}
public void seekTo(int windowIndex, long positionMs) throws RemoteException {
binder.seekToWithWindowIndex(controllerId, windowIndex, positionMs);
}
public void setPlaybackParameters(PlaybackParameters playbackParameters) throws RemoteException {
binder.setPlaybackParameters(
controllerId, BundleableUtils.toNullableBundle(playbackParameters));
}
public void setPlaybackSpeed(float speed) throws RemoteException {
binder.setPlaybackSpeed(controllerId, speed);
}
public void setMediaItems(@NonNull List<MediaItem> mediaItems) throws RemoteException {
setMediaItems(mediaItems, /* resetPosition= */ true);
}
public void setMediaItems(@NonNull List<MediaItem> mediaItems, boolean resetPosition)
throws RemoteException {
binder.setMediaItems1(controllerId, BundleableUtils.toBundleList(mediaItems), resetPosition);
}
public void setMediaItems(
@NonNull List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs)
throws RemoteException {
binder.setMediaItems2(
controllerId, BundleableUtils.toBundleList(mediaItems), startWindowIndex, startPositionMs);
}
/**
* Client app will automatically create a playlist of size {@param size}, and call
* MediaController#setMediaItems() with the list.
*
* <p>Each item's media ID will be {@link TestUtils#getMediaIdInFakeTimeline(int)}.
*/
public void createAndSetFakeMediaItems(int size) throws RemoteException {
binder.createAndSetFakeMediaItems(controllerId, size);
}
public void setMediaUri(@NonNull Uri uri, @Nullable Bundle extras) throws RemoteException {
binder.setMediaUri(controllerId, uri, extras);
}
public void setPlaylistMetadata(MediaMetadata playlistMetadata) throws RemoteException {
binder.setPlaylistMetadata(controllerId, playlistMetadata.toBundle());
}
public void addMediaItems(int index, @NonNull MediaItem mediaItem) throws RemoteException {
addMediaItems(index, Collections.singletonList(mediaItem));
}
public void addMediaItems(int index, @NonNull List<MediaItem> mediaItems) throws RemoteException {
binder.addMediaItems(controllerId, index, BundleableUtils.toBundleList(mediaItems));
}
public void removeMediaItem(int index) throws RemoteException {
removeMediaItems(index, index + 1);
}
public void removeMediaItems(int fromIndex, int toIndex) throws RemoteException {
binder.removeMediaItems(controllerId, fromIndex, toIndex);
}
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) throws RemoteException {
binder.moveMediaItems(controllerId, fromIndex, toIndex, newIndex);
}
public void previous() throws RemoteException {
binder.previous(controllerId);
}
public void next() throws RemoteException {
binder.next(controllerId);
}
public void setShuffleModeEnabled(boolean shuffleModeEnabled) throws RemoteException {
binder.setShuffleModeEnabled(controllerId, shuffleModeEnabled);
}
public void setRepeatMode(@RepeatMode int repeatMode) throws RemoteException {
binder.setRepeatMode(controllerId, repeatMode);
}
public void setVolume(float volume) throws RemoteException {
binder.setVolume(controllerId, volume);
}
public void setDeviceVolume(int volume) throws RemoteException {
binder.setDeviceVolume(controllerId, volume);
}
public void increaseDeviceVolume() throws RemoteException {
binder.increaseDeviceVolume(controllerId);
}
public void decreaseDeviceVolume() throws RemoteException {
binder.decreaseDeviceVolume(controllerId);
}
public void setDeviceMuted(boolean muted) throws RemoteException {
binder.setDeviceMuted(controllerId, muted);
}
public SessionResult sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args)
throws RemoteException {
Bundle result = binder.sendCustomCommand(controllerId, command.toBundle(), args);
return SessionResult.CREATOR.fromBundle(result);
}
public SessionResult setRating(@NonNull String mediaId, @NonNull Rating rating)
throws RemoteException {
Bundle result = binder.setRating(controllerId, mediaId, rating.toBundle());
return SessionResult.CREATOR.fromBundle(result);
}
public void release() throws RemoteException {
binder.release(controllerId);
}
public void stop() throws RemoteException {
binder.stop(controllerId);
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connects to client app's MediaControllerProviderService. Should NOT be called main thread.
*
* @return true if connected successfully, false if failed to connect.
*/
private boolean connect() {
Intent intent = new Intent(ACTION_MEDIA2_CONTROLLER);
intent.setComponent(MEDIA2_CONTROLLER_PROVIDER_SERVICE);
boolean bound = false;
try {
bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to the MediaControllerProviderService.", e);
}
if (bound) {
try {
countDownLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", e);
}
}
return binder != null;
}
/** Disconnects from client app's MediaControllerProviderService. */
private void disconnect() {
if (serviceConnection != null) {
context.unbindService(serviceConnection);
serviceConnection = null;
}
}
/**
* Create a {@link MediaController} in the client app. Should be used after successful connection
* through {@link #connect()}.
*
* @param connectionHints connection hints
* @param waitForConnection true if this method needs to wait for the connection,
*/
void create(SessionToken token, Bundle connectionHints, boolean waitForConnection)
throws RemoteException {
binder.create(
/* isBrowser= */ false, controllerId, token.toBundle(), connectionHints, waitForConnection);
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Connected to client app's MediaControllerProviderService.");
binder = IRemoteMediaController.Stub.asInterface(service);
countDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Disconnected from client app's MediaControllerProviderService.");
}
}
}

View File

@ -0,0 +1,289 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_ARGUMENTS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaControllerCompat;
import com.google.android.exoplayer2.util.Log;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
/**
* Represents remote {@link MediaControllerCompat} the client app's
* MediaControllerCompatProviderService.
*
* <p>Users can run {@link MediaControllerCompat} methods remotely with this object.
*/
public class RemoteMediaControllerCompat {
static final String TAG = "RemoteMediaControllerCompat";
final String controllerId;
final Context context;
final CountDownLatch countDownLatch;
ServiceConnection serviceConnection;
IRemoteMediaControllerCompat binder;
TransportControls transportControls;
/**
* Create a {@link MediaControllerCompat} in the client app. Should NOT be called main thread.
*
* @param waitForConnection true if the remote controller needs to wait for the connection, false
* otherwise.
*/
public RemoteMediaControllerCompat(
Context context, MediaSessionCompat.Token token, boolean waitForConnection)
throws RemoteException {
this.context = context;
controllerId = UUID.randomUUID().toString();
countDownLatch = new CountDownLatch(1);
serviceConnection = new MyServiceConnection();
if (!connect()) {
assertWithMessage("Failed to connect to the MediaControllerCompatProviderService.").fail();
}
create(token, waitForConnection);
}
public void cleanUp() {
disconnect();
}
/**
* Gets {@link TransportControls} for interact with the remote MockPlayer. Users can run
* MockPlayer methods remotely with this object.
*/
public TransportControls getTransportControls() {
return transportControls;
}
////////////////////////////////////////////////////////////////////////////////
// MediaControllerCompat methods
////////////////////////////////////////////////////////////////////////////////
public void addQueueItem(MediaDescriptionCompat description) throws RemoteException {
binder.addQueueItem(controllerId, createBundleWithParcelable(description));
}
public void addQueueItem(MediaDescriptionCompat description, int index) throws RemoteException {
binder.addQueueItemWithIndex(controllerId, createBundleWithParcelable(description), index);
}
public void removeQueueItem(MediaDescriptionCompat description) throws RemoteException {
binder.removeQueueItem(controllerId, createBundleWithParcelable(description));
}
public void setVolumeTo(int value, int flags) throws RemoteException {
binder.setVolumeTo(controllerId, value, flags);
}
public void adjustVolume(int direction, int flags) throws RemoteException {
binder.adjustVolume(controllerId, direction, flags);
}
public void sendCommand(String command, Bundle params, ResultReceiver cb) throws RemoteException {
binder.sendCommand(controllerId, command, params, cb);
}
////////////////////////////////////////////////////////////////////////////////
// MediaControllerCompat.TransportControls methods
////////////////////////////////////////////////////////////////////////////////
/** Transport controls */
public class TransportControls {
public void prepare() throws RemoteException {
binder.prepare(controllerId);
}
public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
binder.prepareFromMediaId(controllerId, mediaId, extras);
}
public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
binder.prepareFromSearch(controllerId, query, extras);
}
public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
binder.prepareFromUri(controllerId, uri, extras);
}
public void play() throws RemoteException {
binder.play(controllerId);
}
public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
binder.playFromMediaId(controllerId, mediaId, extras);
}
public void playFromSearch(String query, Bundle extras) throws RemoteException {
binder.playFromSearch(controllerId, query, extras);
}
public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
binder.playFromUri(controllerId, uri, extras);
}
public void skipToQueueItem(long id) throws RemoteException {
binder.skipToQueueItem(controllerId, id);
}
public void pause() throws RemoteException {
binder.pause(controllerId);
}
public void stop() throws RemoteException {
binder.stop(controllerId);
}
public void seekTo(long pos) throws RemoteException {
binder.seekTo(controllerId, pos);
}
public void setPlaybackSpeed(float speed) throws RemoteException {
binder.setPlaybackSpeed(controllerId, speed);
}
public void skipToNext() throws RemoteException {
binder.skipToNext(controllerId);
}
public void skipToPrevious() throws RemoteException {
binder.skipToPrevious(controllerId);
}
public void setRating(RatingCompat rating) throws RemoteException {
binder.setRating(controllerId, createBundleWithParcelable(rating));
}
public void setRating(RatingCompat rating, Bundle extras) throws RemoteException {
binder.setRatingWithExtras(controllerId, createBundleWithParcelable(rating), extras);
}
public void setCaptioningEnabled(boolean enabled) throws RemoteException {
binder.setCaptioningEnabled(controllerId, enabled);
}
public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode)
throws RemoteException {
binder.setRepeatMode(controllerId, repeatMode);
}
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode)
throws RemoteException {
binder.setShuffleMode(controllerId, shuffleMode);
}
public void sendCustomAction(PlaybackStateCompat.CustomAction customAction, Bundle args)
throws RemoteException {
binder.sendCustomAction(controllerId, createBundleWithParcelable(customAction), args);
}
public void sendCustomAction(String action, Bundle args) throws RemoteException {
binder.sendCustomActionWithName(controllerId, action, args);
}
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connects to client app's MediaControllerCompatProviderService. Should NOT be called main
* thread.
*
* @return true if connected successfully, false if failed to connect.
*/
private boolean connect() {
Intent intent = new Intent(ACTION_MEDIA_CONTROLLER_COMPAT);
intent.setComponent(MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE);
boolean bound = false;
try {
bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to the MediaControllerCompatProviderService.", e);
}
if (bound) {
try {
countDownLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", e);
}
}
return binder != null;
}
/** Disconnects from client app's MediaControllerCompatProviderService. */
private void disconnect() {
if (serviceConnection != null) {
context.unbindService(serviceConnection);
serviceConnection = null;
}
}
/**
* Create a {@link MediaControllerCompat} in the client app. Should be used after successful
* connection through {@link #connect()}.
*
* @param waitForConnection true if this method needs to wait for the connection, false otherwise.
*/
void create(MediaSessionCompat.Token token, boolean waitForConnection) throws RemoteException {
binder.create(controllerId, createBundleWithParcelable(token), waitForConnection);
transportControls = new TransportControls();
}
private Bundle createBundleWithParcelable(Parcelable parcelable) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ARGUMENTS, parcelable);
return bundle;
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Connected to client app's MediaControllerCompatProviderService.");
binder = IRemoteMediaControllerCompat.Stub.asInterface(service);
countDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Disconnected from client app's MediaControllerCompatProviderService.");
}
}
}

View File

@ -0,0 +1,617 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA2_SESSION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_AUDIO_ATTRIBUTES;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_BUFFERED_PERCENTAGE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_BUFFERED_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_BUFFERED_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CONTENT_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_AD_GROUP_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_AD_INDEX_IN_AD_GROUP;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_LIVE_OFFSET;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_PERIOD_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_POSITION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_CURRENT_WINDOW_INDEX;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_INFO;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_MUTED;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DEVICE_VOLUME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_LOADING;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_PLAYING;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_IS_PLAYING_AD;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_MEDIA_ITEM;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_PARAMETERS;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_STATE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_SUPPRESSION_REASON;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYER_ERROR;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYLIST_METADATA;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAY_WHEN_READY;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_REPEAT_MODE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SHUFFLE_MODE_ENABLED;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_TIMELINE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_TOTAL_BUFFERED_DURATION;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_VIDEO_SIZE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_VOLUME;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MEDIA2_SESSION_PROVIDER_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Player.PositionInfo;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaSession;
import com.google.android.exoplayer2.session.vct.common.TestUtils;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.video.VideoSize;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* Represents remote {@link MediaSession} in the service app's MediaSessionProviderService. Users
* can run {@link MediaSession} methods remotely with this object.
*/
public class RemoteMediaSession {
private static final String TAG = "RemoteMediaSession";
private final Context context;
private final String sessionId;
private final Bundle tokenExtras;
private ServiceConnection serviceConnection;
private IRemoteMediaSession binder;
private RemoteMockPlayer remotePlayer;
private CountDownLatch countDownLatch;
/** Create a {@link MediaSession} in the service app. Should NOT be called in main thread. */
public RemoteMediaSession(
@NonNull String sessionId, @NonNull Context context, @Nullable Bundle tokenExtras)
throws RemoteException {
this.sessionId = sessionId;
this.context = context;
countDownLatch = new CountDownLatch(1);
serviceConnection = new MyServiceConnection();
this.tokenExtras = tokenExtras;
if (!connect()) {
assertWithMessage("Failed to connect to the MediaSessionProviderService.").fail();
}
create();
}
public void cleanUp() throws RemoteException {
release();
disconnect();
}
/**
* Gets {@link RemoteMockPlayer} for interact with the remote MockPlayer. Users can run MockPlayer
* methods remotely with this object.
*/
public RemoteMockPlayer getMockPlayer() {
return remotePlayer;
}
////////////////////////////////////////////////////////////////////////////////
// MediaSession methods
////////////////////////////////////////////////////////////////////////////////
/**
* Gets {@link SessionToken} from the service app. Should be used after the creation of the
* session through {@link #create()}.
*
* @return A {@link SessionToken} object.
*/
@Nullable
public SessionToken getToken() throws RemoteException {
return SessionToken.CREATOR.fromBundle(binder.getToken(sessionId));
}
/**
* Gets {@link MediaSessionCompat.Token} from the service app. Should be used after the creation
* of the session through {@link #create()}.
*
* @return A {@link SessionToken} object.
*/
@Nullable
public MediaSessionCompat.Token getCompatToken() throws RemoteException {
Bundle bundle = binder.getCompatToken(sessionId);
bundle.setClassLoader(MediaSession.class.getClassLoader());
return MediaSessionCompat.Token.fromBundle(bundle);
}
public void setSessionPositionUpdateDelayMs(long updateDelayMs) throws RemoteException {
binder.setSessionPositionUpdateDelayMs(sessionId, updateDelayMs);
}
public void setPlayer(@NonNull Bundle config) throws RemoteException {
binder.setPlayer(sessionId, config);
}
public void broadcastCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args)
throws RemoteException {
binder.broadcastCustomCommand(sessionId, command.toBundle(), args);
}
public void sendCustomCommand(@NonNull SessionCommand command, @Nullable Bundle args)
throws RemoteException {
binder.sendCustomCommand(sessionId, null, command.toBundle(), args);
}
public void release() throws RemoteException {
binder.release(sessionId);
}
public void setAvailableCommands(
@NonNull SessionCommands sessionCommands, @NonNull Player.Commands playerCommands)
throws RemoteException {
binder.setAvailableCommands(
sessionId, null, sessionCommands.toBundle(), playerCommands.toBundle());
}
public void setCustomLayout(@NonNull List<CommandButton> layout) throws RemoteException {
List<Bundle> bundleList = new ArrayList<>();
for (CommandButton button : layout) {
bundleList.add(button.toBundle());
}
binder.setCustomLayout(sessionId, null, bundleList);
}
////////////////////////////////////////////////////////////////////////////////
// RemoteMockPlayer methods
////////////////////////////////////////////////////////////////////////////////
/** RemoteMockPlayer */
public class RemoteMockPlayer {
public void notifyPlayerError(@Nullable ExoPlaybackException playerError)
throws RemoteException {
binder.notifyPlayerError(sessionId, BundleableUtils.toNullableBundle(playerError));
}
public void setPlayWhenReady(
boolean playWhenReady, @Player.PlaybackSuppressionReason int reason)
throws RemoteException {
binder.setPlayWhenReady(sessionId, playWhenReady, reason);
}
public void setPlaybackState(@Player.State int state) throws RemoteException {
binder.setPlaybackState(sessionId, state);
}
public void setCurrentPosition(long pos) throws RemoteException {
binder.setCurrentPosition(sessionId, pos);
}
public void setBufferedPosition(long pos) throws RemoteException {
binder.setBufferedPosition(sessionId, pos);
}
public void setDuration(long duration) throws RemoteException {
binder.setDuration(sessionId, duration);
}
public void setBufferedPercentage(int bufferedPercentage) throws RemoteException {
binder.setBufferedPercentage(sessionId, bufferedPercentage);
}
public void setTotalBufferedDuration(long totalBufferedDuration) throws RemoteException {
binder.setTotalBufferedDuration(sessionId, totalBufferedDuration);
}
public void setCurrentLiveOffset(long currentLiveOffset) throws RemoteException {
binder.setCurrentLiveOffset(sessionId, currentLiveOffset);
}
public void setContentDuration(long contentDuration) throws RemoteException {
binder.setContentDuration(sessionId, contentDuration);
}
public void setContentPosition(long contentPosition) throws RemoteException {
binder.setContentPosition(sessionId, contentPosition);
}
public void setContentBufferedPosition(long contentBufferedPosition) throws RemoteException {
binder.setContentBufferedPosition(sessionId, contentBufferedPosition);
}
public void setPlaybackParameters(PlaybackParameters playbackParameters)
throws RemoteException {
binder.setPlaybackParameters(sessionId, playbackParameters.toBundle());
}
public void setIsPlayingAd(boolean isPlayingAd) throws RemoteException {
binder.setIsPlayingAd(sessionId, isPlayingAd);
}
public void setCurrentAdGroupIndex(int currentAdGroupIndex) throws RemoteException {
binder.setCurrentAdGroupIndex(sessionId, currentAdGroupIndex);
}
public void setCurrentAdIndexInAdGroup(int currentAdIndexInAdGroup) throws RemoteException {
binder.setCurrentAdIndexInAdGroup(sessionId, currentAdIndexInAdGroup);
}
public void notifyPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlaybackSuppressionReason int reason)
throws RemoteException {
binder.notifyPlayWhenReadyChanged(sessionId, playWhenReady, reason);
}
public void notifyPlaybackStateChanged(@Player.State int state) throws RemoteException {
binder.notifyPlaybackStateChanged(sessionId, state);
}
public void notifyIsPlayingChanged(boolean isPlaying) throws RemoteException {
binder.notifyIsPlayingChanged(sessionId, isPlaying);
}
public void notifyIsLoadingChanged(boolean isLoading) throws RemoteException {
binder.notifyIsLoadingChanged(sessionId, isLoading);
}
public void notifyPositionDiscontinuity(
PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason)
throws RemoteException {
binder.notifyPositionDiscontinuity(
sessionId, oldPosition.toBundle(), newPosition.toBundle(), reason);
}
public void notifyPlaybackParametersChanged(PlaybackParameters playbackParameters)
throws RemoteException {
binder.notifyPlaybackParametersChanged(sessionId, playbackParameters.toBundle());
}
public void notifyMediaItemTransition(int index, @Player.MediaItemTransitionReason int reason)
throws RemoteException {
binder.notifyMediaItemTransition(sessionId, index, reason);
}
public void notifyAudioAttributesChanged(@NonNull AudioAttributes audioAttributes)
throws RemoteException {
binder.notifyAudioAttributesChanged(sessionId, audioAttributes.toBundle());
}
public void notifyAvailableCommandsChanged(Player.Commands commands) throws RemoteException {
binder.notifyAvailableCommandsChanged(sessionId, commands.toBundle());
}
public void setTimeline(Timeline timeline) throws RemoteException {
binder.setTimeline(sessionId, timeline.toBundle());
}
/**
* Service app will automatically create a timeline of size {@code windowCount}, and sets it to
* the player.
*
* <p>Each item's media ID will be {@link TestUtils#getMediaIdInFakeTimeline(int)}.
*/
public void createAndSetFakeTimeline(int windowCount) throws RemoteException {
binder.createAndSetFakeTimeline(sessionId, windowCount);
}
public void setPlaylistMetadata(MediaMetadata playlistMetadata) throws RemoteException {
binder.setPlaylistMetadata(sessionId, playlistMetadata.toBundle());
}
public void setRepeatMode(@Player.RepeatMode int repeatMode) throws RemoteException {
binder.setRepeatMode(sessionId, repeatMode);
}
public void setShuffleModeEnabled(boolean shuffleModeEnabled) throws RemoteException {
binder.setShuffleModeEnabled(sessionId, shuffleModeEnabled);
}
public void setCurrentWindowIndex(int index) throws RemoteException {
binder.setCurrentWindowIndex(sessionId, index);
}
public void notifyTimelineChanged(@Player.TimelineChangeReason int reason)
throws RemoteException {
binder.notifyTimelineChanged(sessionId, reason);
}
public void notifyPlaylistMetadataChanged() throws RemoteException {
binder.notifyPlaylistMetadataChanged(sessionId);
}
public void notifyShuffleModeEnabledChanged() throws RemoteException {
binder.notifyShuffleModeEnabledChanged(sessionId);
}
public void notifyRepeatModeChanged() throws RemoteException {
binder.notifyRepeatModeChanged(sessionId);
}
public void notifyVideoSizeChanged(@NonNull VideoSize videoSize) throws RemoteException {
binder.notifyVideoSizeChanged(sessionId, videoSize.toBundle());
}
public boolean surfaceExists() throws RemoteException {
return binder.surfaceExists(sessionId);
}
public void notifyDeviceVolumeChanged(int volume, boolean muted) throws RemoteException {
binder.notifyDeviceVolumeChanged(sessionId, volume, muted);
}
public void notifyDeviceInfoChanged(@NonNull DeviceInfo deviceInfo) throws RemoteException {
binder.notifyDeviceInfoChanged(sessionId, deviceInfo.toBundle());
}
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connects to service app's MediaSessionProviderService. Should NOT be called in main thread.
*
* @return true if connected successfully, false if failed to connect.
*/
private boolean connect() {
Intent intent = new Intent(ACTION_MEDIA2_SESSION);
intent.setComponent(MEDIA2_SESSION_PROVIDER_SERVICE);
boolean bound = false;
try {
bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (RuntimeException e) {
Log.e(TAG, "Failed binding to the MediaSessionProviderService of the service app", e);
}
if (bound) {
try {
countDownLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", e);
}
}
return binder != null;
}
/** Disconnects from service app's MediaSessionProviderService. */
private void disconnect() {
if (serviceConnection != null) {
context.unbindService(serviceConnection);
}
serviceConnection = null;
}
/**
* Create a {@link MediaSession} in the service app. Should be used after successful connection
* through {@link #connect}.
*/
private void create() throws RemoteException {
binder.create(sessionId, tokenExtras);
remotePlayer = new RemoteMockPlayer();
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Connected to service app's MediaSessionProviderService.");
binder = IRemoteMediaSession.Stub.asInterface(service);
countDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Disconnected from the service.");
}
}
/**
* Builder to build a {@link Bundle} which represents a configuration of {@link SessionPlayer} in
* order to create a new mock player in the service app. The bundle can be passed to {@link
* #setPlayer(Bundle)}.
*/
public static final class MockPlayerConfigBuilder {
private final Bundle bundle;
public MockPlayerConfigBuilder() {
bundle = new Bundle();
}
public MockPlayerConfigBuilder setPlayerError(@Nullable ExoPlaybackException playerError) {
bundle.putBundle(KEY_PLAYER_ERROR, BundleableUtils.toNullableBundle(playerError));
return this;
}
public MockPlayerConfigBuilder setDuration(long duration) {
bundle.putLong(KEY_DURATION, duration);
return this;
}
public MockPlayerConfigBuilder setCurrentPosition(long pos) {
bundle.putLong(KEY_CURRENT_POSITION, pos);
return this;
}
public MockPlayerConfigBuilder setBufferedPosition(long buffPos) {
bundle.putLong(KEY_BUFFERED_POSITION, buffPos);
return this;
}
public MockPlayerConfigBuilder setBufferedPercentage(int bufferedPercentage) {
bundle.putInt(KEY_BUFFERED_PERCENTAGE, bufferedPercentage);
return this;
}
public MockPlayerConfigBuilder setTotalBufferedDuration(long totalBufferedDuration) {
bundle.putLong(KEY_TOTAL_BUFFERED_DURATION, totalBufferedDuration);
return this;
}
public MockPlayerConfigBuilder setCurrentLiveOffset(long currentLiveOffset) {
bundle.putLong(KEY_CURRENT_LIVE_OFFSET, currentLiveOffset);
return this;
}
public MockPlayerConfigBuilder setContentDuration(long contentDuration) {
bundle.putLong(KEY_CONTENT_DURATION, contentDuration);
return this;
}
public MockPlayerConfigBuilder setContentPosition(long contentPosition) {
bundle.putLong(KEY_CONTENT_POSITION, contentPosition);
return this;
}
public MockPlayerConfigBuilder setContentBufferedPosition(long contentBufferedPosition) {
bundle.putLong(KEY_CONTENT_BUFFERED_POSITION, contentBufferedPosition);
return this;
}
public MockPlayerConfigBuilder setIsPlayingAd(boolean isPlayingAd) {
bundle.putBoolean(KEY_IS_PLAYING_AD, isPlayingAd);
return this;
}
public MockPlayerConfigBuilder setCurrentAdGroupIndex(int currentAdGroupIndex) {
bundle.putInt(KEY_CURRENT_AD_GROUP_INDEX, currentAdGroupIndex);
return this;
}
public MockPlayerConfigBuilder setCurrentAdIndexInAdGroup(int currentAdIndexInAdGroup) {
bundle.putInt(KEY_CURRENT_AD_INDEX_IN_AD_GROUP, currentAdIndexInAdGroup);
return this;
}
public MockPlayerConfigBuilder setPlaybackParameters(PlaybackParameters playbackParameters) {
bundle.putBundle(KEY_PLAYBACK_PARAMETERS, playbackParameters.toBundle());
return this;
}
public MockPlayerConfigBuilder setAudioAttributes(@NonNull AudioAttributes audioAttributes) {
bundle.putBundle(KEY_AUDIO_ATTRIBUTES, audioAttributes.toBundle());
return this;
}
public MockPlayerConfigBuilder setTimeline(@NonNull Timeline timeline) {
bundle.putBundle(KEY_TIMELINE, timeline.toBundle());
return this;
}
public MockPlayerConfigBuilder setCurrentWindowIndex(int index) {
bundle.putInt(KEY_CURRENT_WINDOW_INDEX, index);
return this;
}
public MockPlayerConfigBuilder setCurrentPeriodIndex(int index) {
bundle.putInt(KEY_CURRENT_PERIOD_INDEX, index);
return this;
}
public MockPlayerConfigBuilder setPlaylistMetadata(MediaMetadata playlistMetadata) {
bundle.putBundle(KEY_PLAYLIST_METADATA, playlistMetadata.toBundle());
return this;
}
public MockPlayerConfigBuilder setCurrentMediaItem(@Nullable MediaItem item) {
bundle.putBundle(KEY_MEDIA_ITEM, BundleableUtils.toNullableBundle(item));
return this;
}
public MockPlayerConfigBuilder setVideoSize(@NonNull VideoSize videoSize) {
bundle.putBundle(KEY_VIDEO_SIZE, videoSize.toBundle());
return this;
}
public MockPlayerConfigBuilder setVolume(float volume) {
bundle.putFloat(KEY_VOLUME, volume);
return this;
}
public MockPlayerConfigBuilder setDeviceInfo(@NonNull DeviceInfo deviceInfo) {
bundle.putBundle(KEY_DEVICE_INFO, deviceInfo.toBundle());
return this;
}
public MockPlayerConfigBuilder setDeviceVolume(int volume) {
bundle.putInt(KEY_DEVICE_VOLUME, volume);
return this;
}
public MockPlayerConfigBuilder setDeviceMuted(boolean muted) {
bundle.putBoolean(KEY_DEVICE_MUTED, muted);
return this;
}
public MockPlayerConfigBuilder setPlayWhenReady(boolean playWhenReady) {
bundle.putBoolean(KEY_PLAY_WHEN_READY, playWhenReady);
return this;
}
public MockPlayerConfigBuilder setPlaybackSuppressionReason(
@Player.PlaybackSuppressionReason int playbackSuppressionReason) {
bundle.putInt(KEY_PLAYBACK_SUPPRESSION_REASON, playbackSuppressionReason);
return this;
}
public MockPlayerConfigBuilder setPlaybackState(@Player.State int state) {
bundle.putInt(KEY_PLAYBACK_STATE, state);
return this;
}
public MockPlayerConfigBuilder setIsPlaying(boolean isPlaying) {
bundle.putBoolean(KEY_IS_PLAYING, isPlaying);
return this;
}
public MockPlayerConfigBuilder setIsLoading(boolean isLoading) {
bundle.putBoolean(KEY_IS_LOADING, isLoading);
return this;
}
public MockPlayerConfigBuilder setRepeatMode(@Player.RepeatMode int repeatMode) {
bundle.putInt(KEY_REPEAT_MODE, repeatMode);
return this;
}
public MockPlayerConfigBuilder setShuffleModeEnabled(boolean shuffleModeEnabled) {
bundle.putBoolean(KEY_SHUFFLE_MODE_ENABLED, shuffleModeEnabled);
return this;
}
public Bundle build() {
return bundle;
}
}
}

View File

@ -0,0 +1,242 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_SESSION_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_METADATA_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_PLAYBACK_STATE_COMPAT;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_QUEUE;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MEDIA_SESSION_COMPAT_PROVIDER_SERVICE;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.session.vct.common.IRemoteMediaSessionCompat;
import com.google.android.exoplayer2.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* Represents remote {@link MediaSessionCompat} in the service app's
* MediaSessionCompatProviderService. Users can run {@link MediaSessionCompat} methods remotely with
* this object.
*/
public class RemoteMediaSessionCompat {
private static final String TAG = "RemoteMediaSessionCompat";
private final Context context;
private final String sessionTag;
private ServiceConnection serviceConnection;
private IRemoteMediaSessionCompat binder;
private final CountDownLatch countDownLatch;
/**
* Create a {@link MediaSessionCompat} in the service app. Should NOT be called in main thread.
*/
public RemoteMediaSessionCompat(@NonNull String sessionTag, Context context)
throws RemoteException {
this.sessionTag = sessionTag;
this.context = context;
countDownLatch = new CountDownLatch(1);
serviceConnection = new MyServiceConnection();
if (!connect()) {
assertWithMessage("Failed to connect to the MediaSessionCompatProviderService.").fail();
}
create();
}
public void cleanUp() throws RemoteException {
release();
disconnect();
}
////////////////////////////////////////////////////////////////////////////////
// MediaSessionCompat methods
////////////////////////////////////////////////////////////////////////////////
/**
* Gets {@link MediaSessionCompat.Token} from the service app. Should be used after the creation
* of the session through {@link #create()}.
*
* @return A {@link MediaSessionCompat.Token} object if succeeded, {@code null} if failed.
*/
public MediaSessionCompat.Token getSessionToken() throws RemoteException {
MediaSessionCompat.Token token = null;
Bundle bundle = binder.getSessionToken(sessionTag);
if (bundle != null) {
bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
token = bundle.getParcelable(KEY_SESSION_COMPAT_TOKEN);
}
return token;
}
public void release() throws RemoteException {
binder.release(sessionTag);
}
public void setPlaybackToLocal(int stream) throws RemoteException {
binder.setPlaybackToLocal(sessionTag, stream);
}
/**
* Since we cannot pass VolumeProviderCompat directly, we pass volumeControl, maxVolume,
* currentVolume instead.
*/
public void setPlaybackToRemote(int volumeControl, int maxVolume, int currentVolume)
throws RemoteException {
binder.setPlaybackToRemote(sessionTag, volumeControl, maxVolume, currentVolume);
}
public void setPlaybackState(PlaybackStateCompat state) throws RemoteException {
binder.setPlaybackState(
sessionTag, createBundleWithParcelable(KEY_PLAYBACK_STATE_COMPAT, state));
}
public void setMetadata(MediaMetadataCompat metadata) throws RemoteException {
binder.setMetadata(sessionTag, createBundleWithParcelable(KEY_METADATA_COMPAT, metadata));
}
public void setQueue(@Nullable List<QueueItem> queue) throws RemoteException {
if (queue == null) {
binder.setQueue(sessionTag, null);
} else {
Bundle bundle = new Bundle();
ArrayList<QueueItem> queueAsArrayList = new ArrayList<>(queue);
bundle.putParcelableArrayList(KEY_QUEUE, queueAsArrayList);
binder.setQueue(sessionTag, bundle);
}
}
public void setQueueTitle(CharSequence title) throws RemoteException {
binder.setQueueTitle(sessionTag, title);
}
public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) throws RemoteException {
binder.setRepeatMode(sessionTag, repeatMode);
}
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode)
throws RemoteException {
binder.setShuffleMode(sessionTag, shuffleMode);
}
public void setSessionActivity(PendingIntent intent) throws RemoteException {
binder.setSessionActivity(sessionTag, intent);
}
public void setFlags(int flags) throws RemoteException {
binder.setFlags(sessionTag, flags);
}
public void setRatingType(int type) throws RemoteException {
binder.setRatingType(sessionTag, type);
}
public void sendSessionEvent(String event, Bundle extras) throws RemoteException {
binder.sendSessionEvent(sessionTag, event, extras);
}
public void setCaptioningEnabled(boolean enabled) throws RemoteException {
binder.setCaptioningEnabled(sessionTag, enabled);
}
////////////////////////////////////////////////////////////////////////////////
// Non-public methods
////////////////////////////////////////////////////////////////////////////////
/**
* Connects to service app's MediaSessionCompatProviderService. Should NOT be called in main
* thread.
*
* @return true if connected successfully, false if failed to connect.
*/
private boolean connect() {
Intent intent = new Intent(ACTION_MEDIA_SESSION_COMPAT);
intent.setComponent(MEDIA_SESSION_COMPAT_PROVIDER_SERVICE);
boolean bound = false;
try {
bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (RuntimeException e) {
Log.e(TAG, "Failed binding to the MediaSessionCompatProviderService of the service app", e);
}
if (bound) {
try {
countDownLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", e);
}
}
return binder != null;
}
/** Disconnects from service app's MediaSessionCompatProviderService. */
private void disconnect() {
if (serviceConnection != null) {
context.unbindService(serviceConnection);
}
serviceConnection = null;
}
/**
* Create a {@link MediaSessionCompat} in the service app. Should be used after successful
* connection through {@link #connect}.
*/
private void create() throws RemoteException {
binder.create(sessionTag);
}
private Bundle createBundleWithParcelable(String key, Parcelable value) {
Bundle bundle = new Bundle();
bundle.putParcelable(key, value);
return bundle;
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Connected to service app's MediaSessionCompatProviderService.");
binder = IRemoteMediaSessionCompat.Stub.asInterface(service);
countDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Disconnected from the service.");
}
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2019 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.google.android.exoplayer2.session;
import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.os.Bundle;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.session.MediaBrowser.BrowserCallback;
import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/** A proxy class for {@link BrowserCallback}. */
public final class TestBrowserCallback implements BrowserCallback {
private final ControllerCallback callbackProxy;
private final CountDownLatch connectLatch = new CountDownLatch(1);
private final CountDownLatch disconnectLatch = new CountDownLatch(1);
@GuardedBy("this")
private Runnable onCustomCommandRunnable;
public TestBrowserCallback(@Nullable ControllerCallback callbackProxy) {
this.callbackProxy = callbackProxy == null ? new BrowserCallback() {} : callbackProxy;
}
@Override
public void onConnected(MediaController controller) {
connectLatch.countDown();
callbackProxy.onConnected(controller);
}
@Override
public void onDisconnected(@NonNull MediaController controller) {
disconnectLatch.countDown();
callbackProxy.onDisconnected(controller);
}
public void waitForConnect(boolean expected) throws InterruptedException {
if (expected) {
assertThat(connectLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
} else {
assertThat(connectLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
}
public void waitForDisconnect(boolean expected) throws InterruptedException {
if (expected) {
assertThat(disconnectLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
} else {
assertThat(disconnectLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isFalse();
}
}
@Override
@NonNull
public ListenableFuture<SessionResult> onCustomCommand(
@NonNull MediaController controller, @NonNull SessionCommand command, Bundle args) {
synchronized (this) {
if (onCustomCommandRunnable != null) {
onCustomCommandRunnable.run();
}
}
return callbackProxy.onCustomCommand(controller, command, args);
}
@Override
@NonNull
public ListenableFuture<SessionResult> onSetCustomLayout(
@NonNull MediaController controller, @NonNull List<CommandButton> layout) {
return callbackProxy.onSetCustomLayout(controller, layout);
}
@Override
public void onAvailableSessionCommandsChanged(
MediaController controller, SessionCommands commands) {
callbackProxy.onAvailableSessionCommandsChanged(controller, commands);
}
@Override
public void onChildrenChanged(
@NonNull MediaBrowser browser,
@NonNull String parentId,
int itemCount,
@Nullable MediaLibraryService.LibraryParams params) {
((BrowserCallback) callbackProxy).onChildrenChanged(browser, parentId, itemCount, params);
}
@Override
public void onSearchResultChanged(
@NonNull MediaBrowser browser,
@NonNull String query,
int itemCount,
@Nullable MediaLibraryService.LibraryParams params) {
((BrowserCallback) callbackProxy).onSearchResultChanged(browser, query, itemCount, params);
}
public void setRunnableForOnCustomCommand(Runnable runnable) {
synchronized (this) {
onCustomCommandRunnable = runnable;
}
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.google.android.exoplayer2.session;
import static com.google.common.truth.Truth.assertWithMessage;
import androidx.annotation.GuardedBy;
import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
import java.util.List;
/**
* Keeps the instance of currently running {@link MockMediaSessionService}. And also provides a way
* to control them in one place.
*
* <p>It only support only one service at a time.
*/
public class TestServiceRegistry {
@GuardedBy("TestServiceRegistry.class")
private static TestServiceRegistry instance;
@GuardedBy("TestServiceRegistry.class")
private MediaSessionService service;
@GuardedBy("TestServiceRegistry.class")
private MediaLibrarySessionCallback sessionCallback;
@GuardedBy("TestServiceRegistry.class")
private SessionServiceCallback sessionServiceCallback;
@GuardedBy("TestServiceRegistry.class")
private OnGetSessionHandler onGetSessionHandler;
/** Callback for session service's lifecyle (onCreate() / onDestroy()) */
public interface SessionServiceCallback {
void onCreated();
void onDestroyed();
}
public static TestServiceRegistry getInstance() {
synchronized (TestServiceRegistry.class) {
if (instance == null) {
instance = new TestServiceRegistry();
}
return instance;
}
}
public void setOnGetSessionHandler(OnGetSessionHandler onGetSessionHandler) {
synchronized (TestServiceRegistry.class) {
this.onGetSessionHandler = onGetSessionHandler;
}
}
public OnGetSessionHandler getOnGetSessionHandler() {
synchronized (TestServiceRegistry.class) {
return onGetSessionHandler;
}
}
public void setSessionServiceCallback(SessionServiceCallback sessionServiceCallback) {
synchronized (TestServiceRegistry.class) {
this.sessionServiceCallback = sessionServiceCallback;
}
}
public void setSessionCallback(MediaLibrarySessionCallback sessionCallback) {
synchronized (TestServiceRegistry.class) {
this.sessionCallback = sessionCallback;
}
}
public MediaLibrarySessionCallback getSessionCallback() {
synchronized (TestServiceRegistry.class) {
return sessionCallback;
}
}
public void setServiceInstance(MediaSessionService service) {
synchronized (TestServiceRegistry.class) {
if (this.service != null) {
assertWithMessage(
"Previous service instance is still running. Clean up manually to ensure"
+ " previously running service doesn't break current test")
.fail();
}
this.service = service;
if (sessionServiceCallback != null) {
sessionServiceCallback.onCreated();
}
}
}
public MediaSessionService getServiceInstance() {
synchronized (TestServiceRegistry.class) {
return service;
}
}
public void cleanUp() {
synchronized (TestServiceRegistry.class) {
if (service != null) {
// TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
List<MediaSession> sessions = service.getSessions();
for (int i = 0; i < sessions.size(); i++) {
sessions.get(i).release();
}
// stopSelf() would not kill service while the binder connection established by
// bindService() exists, and release() above will do the job instead.
// So stopSelf() isn't really needed, but just for sure.
service.stopSelf();
service = null;
}
sessionCallback = null;
if (sessionServiceCallback != null) {
sessionServiceCallback.onDestroyed();
sessionServiceCallback = null;
}
onGetSessionHandler = null;
}
}
/** Handles onGetSession */
public interface OnGetSessionHandler {
MediaSession onGetSession(ControllerInfo controllerInfo);
}
}