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:
parent
6d3e9fc251
commit
bd4ba4c583
21
google3/third_party/java_src/android_libs/media/libraries/test_session_common/build.gradle
vendored
Normal file
21
google3/third_party/java_src/android_libs/media/libraries/test_session_common/build.gradle
vendored
Normal 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
|
||||||
|
}
|
@ -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>
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -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 {}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 |
@ -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>
|
Binary file not shown.
45
google3/third_party/java_src/android_libs/media/libraries/test_session_current/build.gradle
vendored
Normal file
45
google3/third_party/java_src/android_libs/media/libraries/test_session_current/build.gradle
vendored
Normal 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
|
||||||
|
}
|
@ -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>
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
@ -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 {}
|
@ -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 {}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user