Commit bd4ba4c5 by gyumin Committed by Oliver Woodman

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
parent 6d3e9fc2
Showing with 4963 additions and 0 deletions
// 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
}
<?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>
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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() {}
}
/*
* 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];
}
};
}
/*
* 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;
}
}
/*
* 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);
});
}
}
/*
* 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() {}
}
/*
* 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() {}
}
/*
* 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() {}
}
/*
* 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 {}
/*
* 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();
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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() {}
}
<?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>
// 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
}
<?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>
/*
* 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);
}
}
/*
* 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();
}
}
}
/*
* 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);
}
}
/*
* 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);
}
}
/*
* 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();
}
}
/*
* 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);
}
}
/*
* 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);
}
}
}
/*
* 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();
}
}
/*
* 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();
}
}
/*
* 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;
}
}
}
/*
* 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();
}
}
/*
* 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);
}
}
/*
* 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();
}
}
/*
* 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();
}
}
/*
* 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;
}
}
/*
* 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();
}
}
/*
* 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();
}
}
/*
* 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);
}
}
/*
* 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();
}
}
/*
* 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);
}
}
/*
* 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();
}
}
/*
* 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);
}
}
}
<?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>
/*
* 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 {}
/*
* 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 {}
/*
* 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();
}
}
}
/*
* 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);
}
}
}
/*
* 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);
}
}
}
/*
* 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());
}
});
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment