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;
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/java/com/google/android/exoplayer2/session/vct/common/TestUtils.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/java/com/google/android/exoplayer2/session/vct/common/TestUtils.java
new file mode 100644
index 0000000000..d281e88ac6
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/java/com/google/android/exoplayer2/session/vct/common/TestUtils.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session.vct.common;
+
+import static android.content.Context.KEYGUARD_SERVICE;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.WindowManager;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import com.google.android.exoplayer2.util.Util;
+import java.util.Locale;
+
+/** Provides utility methods for testing purpose. */
+public class TestUtils {
+
+ public static final long TIMEOUT_MS = 5_000;
+ public static final long NO_RESPONSE_TIMEOUT_MS = 500;
+ public static final long SERVICE_CONNECTION_TIMEOUT_MS = 3_000;
+ public static final long VOLUME_CHANGE_TIMEOUT_MS = 5_000;
+ public static final long LONG_TIMEOUT_MS = 10_000;
+
+ /**
+ * Compares contents of two throwables for both message and class.
+ *
+ * @param a a throwable
+ * @param b another throwable
+ * @return {@code true} if two throwables are the same class and same messages. {@code false}
+ * otherwise.
+ */
+ public static boolean equals(@Nullable Throwable a, @Nullable Throwable b) {
+ if (a == null || b == null) {
+ return a == b;
+ }
+ return a.getClass() == b.getClass() && TextUtils.equals(a.getMessage(), b.getMessage());
+ }
+
+ /**
+ * Compares contents of two bundles.
+ *
+ * @param a a bundle
+ * @param b another bundle
+ * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+ * incorrect if any bundle contains a bundle.
+ */
+ public static boolean equals(Bundle a, Bundle b) {
+ return contains(a, b) && contains(b, a);
+ }
+
+ /**
+ * Checks whether a Bundle contains another bundle.
+ *
+ * @param a a bundle
+ * @param b another bundle
+ * @return {@code true} if a contains b. {@code false} otherwise. This may be incorrect if any
+ * bundle contains a bundle.
+ */
+ public static boolean contains(Bundle a, Bundle b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return b == null;
+ }
+ if (!a.keySet().containsAll(b.keySet())) {
+ return false;
+ }
+ for (String key : b.keySet()) {
+ if (!Util.areEqual(a.get(key), b.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create a bundle for testing purpose.
+ *
+ * @return the newly created bundle.
+ */
+ public static Bundle createTestBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString("test_key", "test_value");
+ return bundle;
+ }
+
+ /** Gets the expected mediaId for the windowIndex when testing with a fake timeline. */
+ public static String getMediaIdInFakeTimeline(int windowIndex) {
+ return String.format(Locale.US, "%08d", windowIndex);
+ }
+
+ @UiThread
+ static void setKeepScreenOn(Activity activity) {
+ if (Build.VERSION.SDK_INT >= 27) {
+ activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ activity.setTurnScreenOn(true);
+ activity.setShowWhenLocked(true);
+ KeyguardManager keyguardManager =
+ (KeyguardManager) activity.getSystemService(KEYGUARD_SERVICE);
+ keyguardManager.requestDismissKeyguard(activity, null);
+ } else {
+ activity
+ .getWindow()
+ .addFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+ }
+
+ private TestUtils() {}
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/drawable/big_buck_bunny.jpg b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/drawable/big_buck_bunny.jpg
new file mode 100644
index 0000000000..f29d12062c
Binary files /dev/null and b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/drawable/big_buck_bunny.jpg differ
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/layout/activity_surface.xml b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/layout/activity_surface.xml
new file mode 100644
index 0000000000..4f132ce5fc
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/layout/activity_surface.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/raw/camera_click.ogg b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/raw/camera_click.ogg
new file mode 100644
index 0000000000..b836e10948
Binary files /dev/null and b/google3/third_party/java_src/android_libs/media/libraries/test_session_common/src/main/res/raw/camera_click.ogg differ
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/build.gradle b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/build.gradle
new file mode 100644
index 0000000000..a115e4b35b
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/build.gradle
@@ -0,0 +1,45 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+apply from: "$gradle.ext.exoplayerSettingsDir/constants.gradle"
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion project.ext.compileSdkVersion
+
+ defaultConfig {
+ versionName project.ext.releaseVersion
+ versionCode project.ext.releaseVersionCode
+ minSdkVersion project.ext.minSdkVersion
+ targetSdkVersion project.ext.appTargetSdkVersion
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// TODO(b/178560255): Remove explicit "group" after moving to androidx package.
+group 'androidx.media3'
+
+dependencies {
+ implementation project(modulePrefix + 'library-session')
+ implementation project(modulePrefix + 'test-session-common')
+ androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
+ androidTestImplementation 'androidx.test.ext:truth:' + androidxTestTruthVersion
+ androidTestImplementation 'androidx.test:core:' + androidxTestCoreVersion
+ androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
+ androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/AndroidManifest.xml b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000..5fbdd4582a
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackTest.java
new file mode 100644
index 0000000000..54c44cdbe4
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackTest.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.LibraryResult.RESULT_ERROR_BAD_VALUE;
+import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.LONG_LIST_COUNT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_EXTRAS;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_ID;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.MediaBrowser.BrowserCallback;
+import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
+import com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeoutException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link MediaBrowser.BrowserCallback}.
+ *
+ * This test inherits {@link MediaControllerCallbackTest} to ensure that inherited APIs from
+ * {@link MediaController} works cleanly.
+ */
+// TODO: (internal cleanup) Move tests that aren't related with callbacks.
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserCallbackTest extends MediaControllerCallbackTest {
+ private static final String TAG = "MediaBrowserCallbackTest";
+
+ @Before
+ public void setControllerType() {
+ controllerTestRule.setControllerType(MediaBrowser.class);
+ }
+
+ private MediaBrowser createBrowser() throws Exception {
+ return createBrowser(null, null);
+ }
+
+ private MediaBrowser createBrowser(
+ @Nullable Bundle connectionHints, @Nullable BrowserCallback callback) throws Exception {
+ SessionToken token = new SessionToken(context, MOCK_MEDIA2_LIBRARY_SERVICE);
+ return (MediaBrowser)
+ controllerTestRule.createController(token, true, connectionHints, callback);
+ }
+
+ @Test
+ public void getLibraryRoot() throws Exception {
+ LibraryParams params =
+ new LibraryParams.Builder()
+ .setOffline(true)
+ .setRecent(true)
+ .setExtras(new Bundle())
+ .build();
+
+ MediaBrowser browser = createBrowser();
+ setExpectedLibraryParam(browser, params);
+ LibraryResult result = browser.getLibraryRoot(params).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(result.item.mediaId).isEqualTo(ROOT_ID);
+ assertThat(TestUtils.equals(ROOT_EXTRAS, result.params.extras)).isTrue();
+ }
+
+ @Test
+ public void getItem() throws Exception {
+ String mediaId = MediaBrowserConstants.MEDIA_ID_GET_ITEM;
+
+ LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(result.item.mediaId).isEqualTo(mediaId);
+ }
+
+ @Test
+ public void getItem_unknownId() throws Exception {
+ String mediaId = "random_media_id";
+
+ LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_ERROR_BAD_VALUE);
+ assertThat(result.item).isNull();
+ }
+
+ @Test
+ public void getItem_nullResult() throws Exception {
+ String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
+
+ // Exception will be thrown in the service side, and the process will be crashed.
+ // In that case one of following will happen
+ // Case 1) Process is crashed. Pending ListenableFuture will get error
+ // Case 2) Due to the frequent crashes with other tests, process may not crash immediately
+ // because the Android shows dialog 'xxx keeps stopping' and defer sending
+ // SIG_KILL until the user's explicit action.
+ try {
+ LibraryResult result = createBrowser().getItem(mediaId).get(TIMEOUT_MS, MILLISECONDS);
+ // Case 1.
+ assertThat(result.resultCode).isNotEqualTo(RESULT_SUCCESS);
+ } catch (TimeoutException e) {
+ // Case 2.
+ }
+
+ // Clean up RemoteMediaSession proactively to avoid crash at cleanUp()
+ try {
+ remoteSession.cleanUp();
+ } catch (RemoteException e) {
+ // Expected
+ }
+ remoteSession = null;
+ }
+
+ @Test
+ public void getChildren() throws Exception {
+ String parentId = MediaBrowserConstants.PARENT_ID;
+ int page = 4;
+ int pageSize = 10;
+ LibraryParams params = MediaTestUtils.createLibraryParams();
+
+ MediaBrowser browser = createBrowser();
+ setExpectedLibraryParam(browser, params);
+
+ LibraryResult result =
+ browser.getChildren(parentId, page, pageSize, params).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(result.params).isNull();
+
+ MediaTestUtils.assertPaginatedListHasIds(
+ result.items, MediaBrowserConstants.GET_CHILDREN_RESULT, page, pageSize);
+ }
+
+ @Test
+ @LargeTest
+ public void getChildren_withLongList() throws Exception {
+ String parentId = MediaBrowserConstants.PARENT_ID_LONG_LIST;
+ int page = 0;
+ int pageSize = Integer.MAX_VALUE;
+ LibraryParams params = MediaTestUtils.createLibraryParams();
+
+ MediaBrowser browser = createBrowser();
+ setExpectedLibraryParam(browser, params);
+
+ LibraryResult result =
+ browser.getChildren(parentId, page, pageSize, params).get(LONG_TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(result.params).isNull();
+
+ assertThat(result.items).hasSize(LONG_LIST_COUNT);
+ for (int i = 0; i < result.items.size(); i++) {
+ assertThat(result.items.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ public void getChildren_emptyResult() throws Exception {
+ String parentId = MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
+
+ MediaBrowser browser = createBrowser();
+ LibraryResult result = browser.getChildren(parentId, 1, 1, null).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(result.items.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void getChildren_nullResult() throws Exception {
+ String parentId = MediaBrowserConstants.PARENT_ID_ERROR;
+
+ MediaBrowser browser = createBrowser();
+ LibraryResult result = browser.getChildren(parentId, 1, 1, null).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isNotEqualTo(RESULT_SUCCESS);
+ assertThat(result.items).isNull();
+ }
+
+ @Test
+ public void searchCallbacks() throws Exception {
+ String query = MediaBrowserConstants.SEARCH_QUERY;
+ int page = 4;
+ int pageSize = 10;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latchForSearch = new CountDownLatch(1);
+ BrowserCallback callback =
+ new BrowserCallback() {
+ @Override
+ public void onSearchResultChanged(
+ MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
+ assertThat(queryOut).isEqualTo(query);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, params);
+ assertThat(itemCount).isEqualTo(MediaBrowserConstants.SEARCH_RESULT_COUNT);
+ latchForSearch.countDown();
+ }
+ };
+
+ // Request the search.
+ MediaBrowser browser = createBrowser(null, callback);
+ setExpectedLibraryParam(browser, testParams);
+ LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ // Get the search result.
+ result =
+ browser.getSearchResult(query, page, pageSize, testParams).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ MediaTestUtils.assertPaginatedListHasIds(
+ result.items, MediaBrowserConstants.SEARCH_RESULT, page, pageSize);
+ }
+
+ @Test
+ @LargeTest
+ public void searchCallbacks_withLongList() throws Exception {
+ String query = MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
+ int page = 0;
+ int pageSize = Integer.MAX_VALUE;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ BrowserCallback callback =
+ new BrowserCallback() {
+ @Override
+ public void onSearchResultChanged(
+ MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
+ assertThat(queryOut).isEqualTo(query);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, params);
+ assertThat(itemCount).isEqualTo(MediaBrowserConstants.LONG_LIST_COUNT);
+ latch.countDown();
+ }
+ };
+
+ MediaBrowser browser = createBrowser(null, callback);
+ setExpectedLibraryParam(browser, testParams);
+ LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ result =
+ browser
+ .getSearchResult(query, page, pageSize, testParams)
+ .get(LONG_TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ for (int i = 0; i < result.items.size(); i++) {
+ assertThat(result.items.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ @LargeTest
+ public void onSearchResultChanged_searchTakesTime() throws Exception {
+ String query = MediaBrowserConstants.SEARCH_QUERY_TAKES_TIME;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ BrowserCallback callback =
+ new BrowserCallback() {
+ @Override
+ public void onSearchResultChanged(
+ MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
+ assertThat(queryOut).isEqualTo(query);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, params);
+ assertThat(itemCount).isEqualTo(MediaBrowserConstants.SEARCH_RESULT_COUNT);
+ latch.countDown();
+ }
+ };
+
+ MediaBrowser browser = createBrowser(null, callback);
+ setExpectedLibraryParam(browser, testParams);
+ LibraryResult result =
+ browser
+ .search(query, testParams)
+ .get(MediaBrowserConstants.SEARCH_TIME_IN_MS + TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onSearchResultChanged_emptyResult() throws Exception {
+ String query = MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ BrowserCallback callback =
+ new BrowserCallback() {
+ @Override
+ public void onSearchResultChanged(
+ MediaBrowser browser, String queryOut, int itemCount, LibraryParams params) {
+ assertThat(queryOut).isEqualTo(query);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, params);
+ assertThat(itemCount).isEqualTo(0);
+ latch.countDown();
+ }
+ };
+
+ MediaBrowser browser = createBrowser(null, callback);
+ setExpectedLibraryParam(browser, testParams);
+ LibraryResult result = browser.search(query, testParams).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onChildrenChanged_calledWhenSubscribed() throws Exception {
+ // This test uses MediaLibrarySession.notifyChildrenChanged().
+ String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL;
+
+ CountDownLatch latch = new CountDownLatch(1);
+ BrowserCallback controllerCallbackProxy =
+ new BrowserCallback() {
+ @Override
+ public void onChildrenChanged(
+ MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
+ assertThat(parentId).isEqualTo(expectedParentId);
+ assertThat(itemCount).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
+ MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
+ latch.countDown();
+ }
+ };
+
+ LibraryResult result =
+ createBrowser(null, controllerCallbackProxy)
+ .subscribe(expectedParentId, null)
+ .get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ // The MediaLibrarySession in MockMediaLibraryService is supposed to call
+ // notifyChildrenChanged() in its callback onSubscribe().
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onChildrenChanged_calledWhenSubscribed2() throws Exception {
+ // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
+ String expectedParentId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
+
+ CountDownLatch latch = new CountDownLatch(1);
+ BrowserCallback controllerCallbackProxy =
+ new BrowserCallback() {
+ @Override
+ public void onChildrenChanged(
+ MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
+ assertThat(parentId).isEqualTo(expectedParentId);
+ assertThat(itemCount).isEqualTo(NOTIFY_CHILDREN_CHANGED_ITEM_COUNT);
+ MediaTestUtils.assertLibraryParamsEquals(params, NOTIFY_CHILDREN_CHANGED_EXTRAS);
+ latch.countDown();
+ }
+ };
+
+ LibraryResult result =
+ createBrowser(null, controllerCallbackProxy)
+ .subscribe(expectedParentId, null)
+ .get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ // The MediaLibrarySession in MockMediaLibraryService is supposed to call
+ // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe().
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onChildrenChanged_notCalledWhenNotSubscribed() throws Exception {
+ // This test uses MediaLibrarySession.notifyChildrenChanged().
+ String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID;
+ CountDownLatch latch = new CountDownLatch(1);
+
+ BrowserCallback controllerCallbackProxy =
+ new BrowserCallback() {
+ @Override
+ public void onChildrenChanged(
+ MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
+ latch.countDown();
+ }
+ };
+
+ LibraryResult result =
+ createBrowser(null, controllerCallbackProxy)
+ .subscribe(subscribedMediaId, null)
+ .get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ // The MediaLibrarySession in MockMediaLibraryService is supposed to call
+ // notifyChildrenChanged() in its callback onSubscribe(), but with a different media ID.
+ // Therefore, onChildrenChanged() should not be called.
+ assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ public void onChildrenChanged_notCalledWhenNotSubscribed2() throws Exception {
+ // This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
+ String subscribedMediaId = SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
+ CountDownLatch latch = new CountDownLatch(1);
+
+ BrowserCallback controllerCallbackProxy =
+ new BrowserCallback() {
+ @Override
+ public void onChildrenChanged(
+ MediaBrowser browser, String parentId, int itemCount, LibraryParams params) {
+ latch.countDown();
+ }
+ };
+
+ LibraryResult result =
+ createBrowser(null, controllerCallbackProxy)
+ .subscribe(subscribedMediaId, null)
+ .get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+
+ // The MediaLibrarySession in MockMediaLibraryService is supposed to call
+ // notifyChildrenChanged(ControllerInfo) in its callback onSubscribe(),
+ // but with a different media ID.
+ // Therefore, onChildrenChanged() should not be called.
+ assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ private void setExpectedLibraryParam(MediaBrowser browser, LibraryParams params)
+ throws Exception {
+ SessionCommand command = new SessionCommand(CUSTOM_ACTION_ASSERT_PARAMS, null);
+ Bundle args = new Bundle();
+ args.putBundle(CUSTOM_ACTION_ASSERT_PARAMS, params.toBundle());
+ SessionResult result = browser.sendCustomCommand(command, args).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(SessionResult.RESULT_SUCCESS);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackWithMediaBrowserServiceCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackWithMediaBrowserServiceCompatTest.java
new file mode 100644
index 0000000000..ae33ff5d06
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCallbackWithMediaBrowserServiceCompatTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA_BROWSER_SERVICE_COMPAT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.MediaBrowserServiceCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.MediaBrowser.BrowserCallback;
+import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link BrowserCallback} with {@link MediaBrowserServiceCompat}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserCallbackWithMediaBrowserServiceCompatTest {
+
+ private final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaBrowserCallbackTestWithMediaBrowserServiceCompat");
+ private final MediaControllerTestRule controllerTestRule =
+ new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ private Context context;
+ private RemoteMediaBrowserServiceCompat remoteService;
+
+ private MediaBrowser createBrowser(boolean waitForConnect, @Nullable BrowserCallback callback)
+ throws Exception {
+ SessionToken token = new SessionToken(context, MOCK_MEDIA_BROWSER_SERVICE_COMPAT);
+ return (MediaBrowser)
+ controllerTestRule.createController(
+ token, waitForConnect, /* connectionHints= */ null, callback);
+ }
+
+ @Before
+ public void setUp() {
+ controllerTestRule.setControllerType(MediaBrowser.class);
+ context = ApplicationProvider.getApplicationContext();
+ remoteService = new RemoteMediaBrowserServiceCompat(context);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ remoteService.release();
+ }
+
+ @Test
+ public void connect() throws Exception {
+ createBrowser(/* waitForConnect= */ true, new BrowserCallback() {});
+ // If connection failed, exception will be thrown inside of #createBrowser().
+ }
+
+ @Test
+ public void connect_rejected() throws Exception {
+ remoteService.setProxyForTest(TEST_CONNECT_REJECTED);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ createBrowser(
+ /* waitForConnect= */ false,
+ new BrowserCallback() {
+ @Override
+ public void onConnected(MediaController controller) {
+ assertWithMessage("shouldn't allow connection").fail();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull MediaController controller) {
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onChildrenChanged_subscribeAndUnsubscribe() throws Exception {
+ String testParentId = "testOnChildrenChanged";
+ CountDownLatch latch = new CountDownLatch(2);
+ BrowserCallback browserCallback =
+ new BrowserCallback() {
+ @Override
+ public void onChildrenChanged(
+ @NonNull MediaBrowser browser,
+ @NonNull String parentId,
+ int itemCount,
+ @Nullable LibraryParams params) {
+ // Triggered by both subscribe and notifyChildrenChanged().
+ // Shouldn't be called after the unsubscribe().
+ assertThat(latch.getCount()).isNotEqualTo(0);
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(params).isNull();
+ latch.countDown();
+ }
+ };
+
+ remoteService.setProxyForTest(TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE);
+ MediaBrowser browser = createBrowser(/* waitForConnect= */ true, browserCallback);
+
+ LibraryResult resultForSubscribe =
+ browser.subscribe(testParentId, null).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(resultForSubscribe.resultCode).isEqualTo(RESULT_SUCCESS);
+ remoteService.notifyChildrenChanged(testParentId);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ LibraryResult resultForUnsubscribe =
+ browser.unsubscribe(testParentId).get(TIMEOUT_MS, MILLISECONDS);
+ assertThat(resultForUnsubscribe.resultCode).isEqualTo(RESULT_SUCCESS);
+ // Unsubscribe takes some time. Wait for some time.
+ Thread.sleep(TIMEOUT_MS);
+ remoteService.notifyChildrenChanged(testParentId);
+ // This shouldn't trigger browser's onChildrenChanged().
+ // Wait for some time. Exception will be thrown in the callback if error happens.
+ Thread.sleep(TIMEOUT_MS);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaLibraryServiceTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaLibraryServiceTest.java
new file mode 100644
index 0000000000..13e40f7e43
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaLibraryServiceTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CHILDREN_COUNT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.CUSTOM_ACTION_EXTRAS;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.GET_CHILDREN_RESULT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.LONG_LIST_COUNT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.MEDIA_ID_GET_ITEM;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_ERROR;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_LONG_LIST;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_EXTRAS;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.ROOT_ID;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_ERROR;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_QUERY_LONG_LIST;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT;
+import static com.google.android.exoplayer2.session.vct.common.MediaBrowserConstants.SEARCH_RESULT_COUNT;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserCompat.CustomActionCallback;
+import android.support.v4.media.MediaBrowserCompat.ItemCallback;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaBrowserCompat.SearchCallback;
+import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaBrowserCompat} with {@link MediaLibraryService}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserCompatWithMediaLibraryServiceTest
+ extends MediaBrowserCompatWithMediaSessionServiceTest {
+
+ @Override
+ ComponentName getServiceComponent() {
+ return MOCK_MEDIA2_LIBRARY_SERVICE;
+ }
+
+ @Test
+ public void getRoot() throws Exception {
+ // The MockMediaLibraryService gives MediaBrowserConstants.ROOT_ID as root ID, and
+ // MediaBrowserConstants.ROOT_EXTRAS as extras.
+ handler.postAndSync(
+ () -> {
+ browserCompat =
+ new MediaBrowserCompat(
+ context, getServiceComponent(), connectionCallback, /* rootHint= */ null);
+ });
+ connectAndWait();
+ assertThat(browserCompat.getRoot()).isEqualTo(ROOT_ID);
+
+ // Note: Cannot use equals() here because browser compat's extra contains server version,
+ // extra binder, and extra messenger.
+ assertThat(TestUtils.contains(browserCompat.getExtras(), ROOT_EXTRAS)).isTrue();
+ }
+
+ @Test
+ public void getItem() throws InterruptedException {
+ String mediaId = MEDIA_ID_GET_ITEM;
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.getItem(
+ mediaId,
+ new ItemCallback() {
+ @Override
+ public void onItemLoaded(MediaItem item) {
+ assertThat(item.getMediaId()).isEqualTo(mediaId);
+ assertThat(item).isNotNull();
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getItem_nullResult() throws InterruptedException {
+ String mediaId = "random_media_id";
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.getItem(
+ mediaId,
+ new ItemCallback() {
+ @Override
+ public void onItemLoaded(MediaItem item) {
+ assertThat(item).isNull();
+ latch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull String itemId) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getChildren() throws InterruptedException {
+ String testParentId = PARENT_ID;
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.subscribe(
+ testParentId,
+ new SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(children).isNotNull();
+ assertThat(children.size()).isEqualTo(GET_CHILDREN_RESULT.size());
+
+ // Compare the given results with originals.
+ for (int i = 0; i < children.size(); i++) {
+ assertThat(children.get(i).getMediaId()).isEqualTo(GET_CHILDREN_RESULT.get(i));
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children, @NonNull Bundle option) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getChildren_withLongList() throws InterruptedException {
+ String testParentId = PARENT_ID_LONG_LIST;
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.subscribe(
+ testParentId,
+ new SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(children).isNotNull();
+ assertThat(children.size() < LONG_LIST_COUNT).isTrue();
+
+ // Compare the given results with originals.
+ for (int i = 0; i < children.size(); i++) {
+ assertThat(children.get(i).getMediaId())
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children, @NonNull Bundle option) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getChildren_withPagination() throws InterruptedException {
+ String testParentId = PARENT_ID;
+ int page = 4;
+ int pageSize = 10;
+ Bundle extras = new Bundle();
+ extras.putString(testParentId, testParentId);
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ Bundle option = new Bundle();
+ option.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+ option.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+ browserCompat.subscribe(
+ testParentId,
+ option,
+ new SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId,
+ @NonNull List children,
+ @NonNull Bundle options) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(option.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(page);
+ assertThat(option.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(pageSize);
+ assertThat(children).isNotNull();
+
+ int fromIndex = page * pageSize;
+ int toIndex = Math.min((page + 1) * pageSize, CHILDREN_COUNT);
+
+ // Compare the given results with originals.
+ for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
+ int relativeIndex = originalIndex - fromIndex;
+ assertThat(children.get(relativeIndex).getMediaId())
+ .isEqualTo(GET_CHILDREN_RESULT.get(originalIndex));
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getChildren_emptyResult() throws InterruptedException {
+ String testParentId = PARENT_ID_NO_CHILDREN;
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.subscribe(
+ testParentId,
+ new SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId, @NonNull List children) {
+ assertThat(children).isNotNull();
+ assertThat(children.size()).isEqualTo(0);
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getChildren_nullResult() throws InterruptedException {
+ String testParentId = PARENT_ID_ERROR;
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.subscribe(
+ testParentId,
+ new SubscriptionCallback() {
+ @Override
+ public void onError(@NonNull String parentId) {
+ assertThat(parentId).isEqualTo(testParentId);
+ latch.countDown();
+ }
+
+ @Override
+ public void onChildrenLoaded(
+ @NonNull String parentId,
+ @NonNull List children,
+ @NonNull Bundle options) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void search() throws InterruptedException {
+ String testQuery = SEARCH_QUERY;
+ int page = 4;
+ int pageSize = 10;
+ Bundle testExtras = new Bundle();
+ testExtras.putString(testQuery, testQuery);
+ testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+ testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.search(
+ testQuery,
+ testExtras,
+ new SearchCallback() {
+ @Override
+ public void onSearchResult(
+ @NonNull String query, Bundle extras, @NonNull List items) {
+ assertThat(query).isEqualTo(testQuery);
+ assertThat(TestUtils.equals(testExtras, extras)).isTrue();
+ int expectedSize =
+ Math.max(Math.min(pageSize, SEARCH_RESULT_COUNT - pageSize * page), 0);
+ assertThat(items.size()).isEqualTo(expectedSize);
+
+ int fromIndex = page * pageSize;
+ int toIndex = Math.min((page + 1) * pageSize, SEARCH_RESULT_COUNT);
+
+ // Compare the given results with originals.
+ for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
+ int relativeIndex = originalIndex - fromIndex;
+ assertThat(items.get(relativeIndex).getMediaId())
+ .isEqualTo(SEARCH_RESULT.get(originalIndex));
+ }
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void search_withLongList() throws InterruptedException {
+ String testQuery = SEARCH_QUERY_LONG_LIST;
+ int page = 0;
+ int pageSize = Integer.MAX_VALUE;
+ Bundle testExtras = new Bundle();
+ testExtras.putString(testQuery, testQuery);
+ testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+ testExtras.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.search(
+ testQuery,
+ testExtras,
+ new SearchCallback() {
+ @Override
+ public void onSearchResult(
+ @NonNull String query, Bundle extras, @NonNull List items) {
+ assertThat(query).isEqualTo(testQuery);
+ assertThat(TestUtils.equals(testExtras, extras)).isTrue();
+
+ assertThat(items).isNotNull();
+ assertThat(items.size() < LONG_LIST_COUNT).isTrue();
+ for (int i = 0; i < items.size(); i++) {
+ assertThat(items.get(i).getMediaId())
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void search_emptyResult() throws InterruptedException {
+ String testQuery = SEARCH_QUERY_EMPTY_RESULT;
+ Bundle testExtras = new Bundle();
+ testExtras.putString(testQuery, testQuery);
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.search(
+ testQuery,
+ testExtras,
+ new SearchCallback() {
+ @Override
+ public void onSearchResult(
+ @NonNull String query, Bundle extras, @NonNull List items) {
+ assertThat(query).isEqualTo(testQuery);
+ assertThat(TestUtils.equals(testExtras, extras)).isTrue();
+ assertThat(items).isNotNull();
+ assertThat(items.size()).isEqualTo(0);
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void search_error() throws InterruptedException {
+ String testQuery = SEARCH_QUERY_ERROR;
+ Bundle testExtras = new Bundle();
+ testExtras.putString(testQuery, testQuery);
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.search(
+ testQuery,
+ testExtras,
+ new SearchCallback() {
+ @Override
+ public void onError(@NonNull String query, Bundle extras) {
+ assertThat(query).isEqualTo(testQuery);
+ assertThat(TestUtils.equals(testExtras, extras)).isTrue();
+ latch.countDown();
+ }
+
+ @Override
+ public void onSearchResult(
+ @NonNull String query, Bundle extras, @NonNull List items) {
+ assertWithMessage("").fail();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
+ @Test
+ public void customAction() throws InterruptedException {
+ Bundle testArgs = new Bundle();
+ testArgs.putString("args_key", "args_value");
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.sendCustomAction(
+ CUSTOM_ACTION,
+ testArgs,
+ new CustomActionCallback() {
+ @Override
+ public void onResult(String action, Bundle extras, Bundle resultData) {
+ assertThat(action).isEqualTo(CUSTOM_ACTION);
+ assertThat(TestUtils.equals(testArgs, extras)).isTrue();
+ assertThat(TestUtils.equals(CUSTOM_ACTION_EXTRAS, resultData)).isTrue();
+ latch.countDown();
+ }
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ // TODO: Add test for onCustomCommand() in MediaLibrarySessionLegacyCallbackTest.
+ @Test
+ public void customAction_rejected() throws InterruptedException {
+ // This action will not be allowed by the library session.
+ String testAction = "random_custom_action";
+
+ connectAndWait();
+ CountDownLatch latch = new CountDownLatch(1);
+ browserCompat.sendCustomAction(
+ testAction,
+ null,
+ new CustomActionCallback() {
+ @Override
+ public void onResult(String action, Bundle extras, Bundle resultData) {
+ latch.countDown();
+ }
+ });
+ assertWithMessage("BrowserCompat shouldn't receive custom command")
+ .that(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS))
+ .isFalse();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaSessionServiceTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaSessionServiceTest.java
new file mode 100644
index 0000000000..b2c6f56314
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserCompatWithMediaSessionServiceTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaBrowserCompat} with {@link MediaSessionService}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserCompatWithMediaSessionServiceTest {
+
+ private final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaBrowserCompatTestWithMediaSessionService");
+ private final MediaControllerTestRule controllerTestRule =
+ new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ Context context;
+ TestHandler handler;
+ MediaBrowserCompat browserCompat;
+ TestConnectionCallback connectionCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ handler = threadTestRule.getHandler();
+ connectionCallback = new TestConnectionCallback();
+ handler.postAndSync(
+ () -> {
+ // Make browser's internal handler to be initialized with test thread.
+ browserCompat =
+ new MediaBrowserCompat(context, getServiceComponent(), connectionCallback, null);
+ });
+ }
+
+ @After
+ public void cleanUp() {
+ if (browserCompat != null) {
+ browserCompat.disconnect();
+ browserCompat = null;
+ }
+ }
+
+ ComponentName getServiceComponent() {
+ return MOCK_MEDIA2_SESSION_SERVICE;
+ }
+
+ void connectAndWait() throws InterruptedException {
+ browserCompat.connect();
+ assertThat(connectionCallback.connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS))
+ .isTrue();
+ }
+
+ @Test
+ public void connect() throws InterruptedException {
+ connectAndWait();
+ assertThat(connectionCallback.failedLatch.getCount()).isNotEqualTo(0);
+ }
+
+ @Ignore
+ @Test
+ public void connect_rejected() throws InterruptedException {
+ // TODO: Connect the browser to the session service whose onConnect() returns null.
+ assertThat(connectionCallback.failedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(connectionCallback.connectedLatch.getCount()).isNotEqualTo(0);
+ }
+
+ @Test
+ public void getSessionToken() throws Exception {
+ connectAndWait();
+ MediaControllerCompat controller =
+ new MediaControllerCompat(context, browserCompat.getSessionToken());
+ assertThat(controller.getPackageName())
+ .isEqualTo(browserCompat.getServiceComponent().getPackageName());
+ }
+
+ class TestConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+ public final CountDownLatch connectedLatch = new CountDownLatch(1);
+ public final CountDownLatch suspendedLatch = new CountDownLatch(1);
+ public final CountDownLatch failedLatch = new CountDownLatch(1);
+
+ TestConnectionCallback() {
+ super();
+ }
+
+ @Override
+ public void onConnected() {
+ super.onConnected();
+ connectedLatch.countDown();
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ super.onConnectionSuspended();
+ suspendedLatch.countDown();
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ super.onConnectionFailed();
+ failedLatch.countDown();
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
new file mode 100644
index 0000000000..07b14583c8
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import androidx.media.MediaBrowserServiceCompat;
+import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
+import androidx.media.MediaBrowserServiceCompat.Result;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
+import com.google.android.exoplayer2.session.MockMediaBrowserServiceCompat.Proxy;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaBrowserServiceCompat} with {@link MediaBrowser}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private Context context;
+ private SessionToken token;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ token =
+ new SessionToken(
+ context, new ComponentName(context, LocalMockMediaBrowserServiceCompat.class));
+ }
+
+ @Test
+ public void onGetRootCalledByGetLibraryRoot() throws Exception {
+ String testMediaId = "testOnGetRootCalledByGetLibraryRoot";
+ Bundle testExtras = new Bundle();
+ testExtras.putString(testMediaId, testMediaId);
+ LibraryParams testParams =
+ new LibraryParams.Builder().setSuggested(true).setExtras(testExtras).build();
+
+ Bundle testReturnedExtras = new Bundle(testExtras);
+ testReturnedExtras.putBoolean(BrowserRoot.EXTRA_OFFLINE, true);
+ BrowserRoot browserRoot = new BrowserRoot(testMediaId, testReturnedExtras);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ assertThat(clientPackageName).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ if (rootHints != null && rootHints.keySet().contains(testMediaId)) {
+ MediaTestUtils.assertLibraryParamsEquals(testParams, rootHints);
+ // This should happen because getLibraryRoot() is called with testExtras.
+ latch.countDown();
+ return browserRoot;
+ }
+ // For other random connection requests.
+ return new BrowserRoot("rootId", null);
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getLibraryRoot(testParams);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertThat(result.item.mediaId).isEqualTo(testMediaId);
+
+ LibraryParams returnedParams = result.params;
+ assertThat(returnedParams.recent)
+ .isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_RECENT));
+ assertThat(returnedParams.offline)
+ .isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_OFFLINE));
+ assertThat(returnedParams.suggested)
+ .isEqualTo(testReturnedExtras.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
+
+ // Note that TestUtils#equals() cannot be used for this because
+ // MediaBrowserServiceCompat adds extra_client_version to the rootHints.
+ assertThat(TestUtils.contains(returnedParams.extras, testExtras)).isTrue();
+ }
+
+ @Test
+ public void onLoadItemCalledByGetItem() throws Exception {
+ String testMediaId = "test_media_item";
+ MediaBrowserCompat.MediaItem testItem = createBrowserMediaItem(testMediaId);
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadItem(String itemId, Result result) {
+ assertThat(itemId).isEqualTo(testMediaId);
+ result.sendResult(testItem);
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getItem(testMediaId);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertItemEquals(testItem, result.item);
+ }
+
+ @Test
+ public void onLoadItemCalledByGetItem_nullResult() throws Exception {
+ String testMediaId = "test_media_item";
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadItem(String itemId, Result result) {
+ assertThat(itemId).isEqualTo(testMediaId);
+ result.sendResult(null);
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getItem(testMediaId);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onLoadChildrenWithoutOptionsCalledByGetChildrenWithoutOptions() throws Exception {
+ String testParentId = "test_media_parent";
+ int testPage = 2;
+ int testPageSize = 4;
+ List testFullMediaItemList =
+ createBrowserMediaItems((testPage + 1) * testPageSize);
+ List testPaginatedMediaItemList =
+ testFullMediaItemList.subList(
+ testPage * testPageSize,
+ Math.min((testPage + 1) * testPageSize, testFullMediaItemList.size()));
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result) {
+ assertThat(parentId).isEqualTo(testParentId);
+ result.sendResult(testFullMediaItemList);
+ latch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertItemsEquals(testPaginatedMediaItemList, result.items);
+ }
+
+ @Test
+ public void onLoadChildrenWithOptionsCalledByGetChildrenWithoutOptions() throws Exception {
+ String testParentId = "test_media_parent";
+ int testPage = 2;
+ int testPageSize = 4;
+ List testMediaItemList = createBrowserMediaItems(testPageSize);
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result) {
+ assertWithMessage("This isn't expected to be called").fail();
+ }
+
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result, Bundle options) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
+ assertThat(options.keySet().size()).isEqualTo(2);
+ result.sendResult(testMediaItemList);
+ latch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertItemsEquals(testMediaItemList, result.items);
+ assertThat(result.params).isNull();
+ }
+
+ @Test
+ public void onLoadChildrenWithOptionsCalledByGetChildrenWithoutOptions_nullResult()
+ throws Exception {
+ String testParentId = "test_media_parent";
+ int testPage = 2;
+ int testPageSize = 4;
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result, Bundle options) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
+ result.sendResult(null);
+ latch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, null);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertThat(result.params).isNull();
+ }
+
+ @Test
+ public void onLoadChildrenWithOptionsCalledByGetChildrenWithOptions() throws Exception {
+ String testParentId = "test_media_parent";
+ int testPage = 2;
+ int testPageSize = 4;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+ List testMediaItemList =
+ createBrowserMediaItems(testPageSize / 2);
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result, Bundle options) {
+ assertThat(parentId).isEqualTo(testParentId);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
+ assertThat(options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
+ assertThat(TestUtils.contains(options, testParams.extras)).isTrue();
+ result.sendResult(testMediaItemList);
+ latch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getChildren(testParentId, testPage, testPageSize, testParams);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ assertItemsEquals(testMediaItemList, result.items);
+ assertThat(result.params).isNull();
+ }
+
+ @Test
+ public void onLoadChildrenCalledBySubscribe() throws Exception {
+ String testParentId = "testOnLoadChildrenCalledBySubscribe";
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+ CountDownLatch subscribeLatch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result, Bundle option) {
+ assertThat(parentId).isEqualTo(testParentId);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, option);
+ result.sendResult(Collections.emptyList());
+ subscribeLatch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.subscribe(testParentId, testParams);
+ assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onLoadChildrenCalledBySubscribe_failed() throws Exception {
+ String testParentId = "testOnLoadChildrenCalledBySubscribe_failed";
+ CountDownLatch subscribeLatch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onLoadChildren(
+ String parentId, Result> result, Bundle option) {
+ assertThat(parentId).isEqualTo(testParentId);
+ // Cannot use Result#sendError() for sending error here. The API is specific to
+ // custom action.
+ result.sendResult(null);
+ subscribeLatch.countDown();
+ }
+ });
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.subscribe(testParentId, null);
+ assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onSearchCalledBySearch() throws Exception {
+ String testQuery = "search_query";
+ int testPage = 2;
+ int testPageSize = 4;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+ List testFullSearchResult =
+ createBrowserMediaItems((testPage + 1) * testPageSize + 3);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onSearch(
+ String query, Bundle extras, Result> result) {
+ assertThat(query).isEqualTo(testQuery);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, extras);
+ result.sendResult(testFullSearchResult);
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.search(testQuery, testParams);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onSearchCalledBySearch_nullResult() throws Exception {
+ String testQuery = "search_query";
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onSearch(
+ String query, Bundle extras, Result> result) {
+ result.sendResult(null);
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.search(testQuery, null);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onSearchCalledByGetSearchResult() throws Exception {
+ String testQuery = "search_query";
+ int testPage = 2;
+ int testPageSize = 4;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onSearch(
+ String query, Bundle extras, Result> result) {
+ assertThat(query).isEqualTo(testQuery);
+ MediaTestUtils.assertLibraryParamsEquals(testParams, extras);
+ assertThat(extras.getInt(MediaBrowserCompat.EXTRA_PAGE)).isEqualTo(testPage);
+ assertThat(extras.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)).isEqualTo(testPageSize);
+ result.sendResult(Collections.emptyList());
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void onSearchCalledByGetSearchResult_nullResult() throws Exception {
+ String testQuery = "search_query";
+ int testPage = 2;
+ int testPageSize = 4;
+ LibraryParams testParams = MediaTestUtils.createLibraryParams();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MockMediaBrowserServiceCompat.setMediaBrowserServiceProxy(
+ new Proxy() {
+ @Override
+ public void onSearch(
+ String query, Bundle extras, Result> result) {
+ result.sendResult(null);
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
+ LibraryResult result = browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
+ }
+
+ private static MediaBrowserCompat.MediaItem createBrowserMediaItem(String mediaId) {
+ MediaDescriptionCompat desc =
+ new MediaDescriptionCompat.Builder()
+ .setMediaId(mediaId)
+ .setTitle("title: " + mediaId)
+ .build();
+ return new MediaBrowserCompat.MediaItem(desc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
+ }
+
+ private static List createBrowserMediaItems(int size) {
+ List list = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ list.add(createBrowserMediaItem("browserItem_" + i));
+ }
+ return list;
+ }
+
+ private static void assertItemEquals(
+ MediaBrowserCompat.MediaItem browserItem, MediaItem commonItem) {
+ assertThat(commonItem.mediaId).isEqualTo(browserItem.getMediaId());
+ assertThat(commonItem.mediaMetadata.title).isEqualTo(browserItem.getDescription().getTitle());
+ }
+
+ private static void assertItemsEquals(
+ List browserItemList, List commonItemList) {
+ assertThat(commonItemList.size()).isEqualTo(browserItemList.size());
+ for (int i = 0; i < browserItemList.size(); i++) {
+ assertItemEquals(browserItemList.get(i), commonItemList.get(i));
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserTest.java
new file mode 100644
index 0000000000..5f9a4ad8cf
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaBrowserTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaBrowser}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaBrowserTest extends MediaControllerTest {
+
+ @Before
+ public void setControllerType() {
+ controllerTestRule.setControllerType(MediaBrowser.class);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackTest.java
new file mode 100644
index 0000000000..374e74763d
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackTest.java
@@ -0,0 +1,1909 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE;
+import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE;
+import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED;
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
+import static com.google.android.exoplayer2.Player.STATE_BUFFERING;
+import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWith;
+import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWithAllCommands;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+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.MediaSessionConstants.TEST_CONTROLLER_CALLBACK_SESSION_REJECTS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.AudioAttributesCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.Commands;
+import com.google.android.exoplayer2.Player.DiscontinuityReason;
+import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason;
+import com.google.android.exoplayer2.Player.PositionInfo;
+import com.google.android.exoplayer2.Player.RepeatMode;
+import com.google.android.exoplayer2.Player.State;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.session.RemoteMediaSession.RemoteMockPlayer;
+import com.google.android.exoplayer2.session.SessionPlayer.PlayerCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.android.exoplayer2.util.ExoFlags;
+import com.google.android.exoplayer2.video.VideoSize;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link MediaController.ControllerCallback}. It also tests {@link
+ * SessionPlayer.PlayerCallback} passed by {@link MediaController#addListener}.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerCallbackTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private static final int EVENT_ON_EVENTS = C.INDEX_UNSET;
+
+ private final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaControllerCallbackTest");
+ final MediaControllerTestRule controllerTestRule = new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ Context context;
+ RemoteMediaSession remoteSession;
+ private MediaController controller;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ remoteSession = createRemoteMediaSession(DEFAULT_TEST_NAME);
+ }
+
+ @After
+ public void cleanUp() throws RemoteException {
+ if (remoteSession != null) {
+ remoteSession.cleanUp();
+ remoteSession = null;
+ }
+ }
+
+ @Test
+ public void connection_sessionAccepts() throws Exception {
+ // createController() uses controller callback to wait until the controller becomes
+ // available.
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ assertThat(controller).isNotNull();
+ }
+
+ @Test
+ public void connection_sessionRejects() throws Exception {
+ RemoteMediaSession session = createRemoteMediaSession(TEST_CONTROLLER_CALLBACK_SESSION_REJECTS);
+ try {
+ MediaController controller =
+ controllerTestRule.createController(
+ session.getToken(), /* waitForConnect= */ false, null, null);
+ assertThat(controller).isNotNull();
+ controllerTestRule.waitForConnect(controller, /* expected= */ false);
+ controllerTestRule.waitForDisconnect(controller, /* expected= */ true);
+ } finally {
+ session.cleanUp();
+ }
+ }
+
+ @Test
+ public void connection_toSessionService() throws Exception {
+ SessionToken token = new SessionToken(context, MOCK_MEDIA2_SESSION_SERVICE);
+ MediaController controller = controllerTestRule.createController(token);
+ assertThat(controller).isNotNull();
+ }
+
+ @Test
+ public void connection_toLibraryService() throws Exception {
+ SessionToken token = new SessionToken(context, MOCK_MEDIA2_LIBRARY_SERVICE);
+ MediaController controller = controllerTestRule.createController(token);
+ assertThat(controller).isNotNull();
+ }
+
+ @Test
+ public void connection_sessionClosed() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ remoteSession.release();
+ controllerTestRule.waitForDisconnect(controller, true);
+ }
+
+ @Test
+ public void connection_controllerClosed() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ threadTestRule.getHandler().postAndSync(controller::release);
+ controllerTestRule.waitForDisconnect(controller, true);
+ }
+
+ @Test
+ @LargeTest
+ public void noInteractionAfterSessionClose_session() throws Exception {
+ SessionToken token = remoteSession.getToken();
+ controller = controllerTestRule.createController(token);
+ testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
+ }
+
+ @Test
+ @LargeTest
+ public void noInteractionAfterControllerClose_session() throws Exception {
+ SessionToken token = remoteSession.getToken();
+ controller = controllerTestRule.createController(token);
+
+ threadTestRule.getHandler().postAndSync(controller::release);
+ // release is done immediately for session.
+ testNoInteraction();
+
+ // Test whether the controller is notified about later release of the session or
+ // re-creation.
+ testControllerAfterSessionIsClosed(DEFAULT_TEST_NAME);
+ }
+
+ @Test
+ @LargeTest
+ public void connection_withLongPlaylist() throws Exception {
+ int windowCount = 5_000;
+ remoteSession.getMockPlayer().createAndSetFakeTimeline(windowCount);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionToken(remoteSession.getToken())
+ .setControllerCallback(
+ new MediaController.ControllerCallback() {
+ @Override
+ public void onConnected(MediaController controller) {
+ latch.countDown();
+ }
+ })
+ .setApplicationLooper(threadTestRule.getHandler().getLooper())
+ .build();
+
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ Timeline timeline = threadTestRule.getHandler().postAndSync(controller::getCurrentTimeline);
+ assertThat(timeline.getWindowCount()).isEqualTo(windowCount);
+ Timeline.Window window = new Timeline.Window();
+ for (int i = 0; i < timeline.getWindowCount(); i++) {
+ assertThat(timeline.getWindow(i, window).mediaItem.mediaId)
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ public void onPlayerError_isNotified() throws Exception {
+ ExoPlaybackException testPlayerError = ExoPlaybackException.createForRemote("test exception");
+
+ AtomicReference playerErrorRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ controller.addListener(
+ new PlayerCallback() {
+ @Override
+ public void onPlayerError(ExoPlaybackException playerError) {
+ playerErrorRef.set(playerError);
+ latch.countDown();
+ }
+ });
+
+ remoteSession.getMockPlayer().notifyPlayerError(testPlayerError);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(TestUtils.equals(playerErrorRef.get(), testPlayerError)).isTrue();
+ }
+
+ @Test
+ public void setPlayer_notifiesChangedValues() throws Exception {
+ @State int testState = STATE_BUFFERING;
+ Timeline testTimeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder().setTitle("title").build();
+ AudioAttributes testAudioAttributes =
+ MediaUtils.convertToAudioAttributes(
+ new AudioAttributesCompat.Builder()
+ .setLegacyStreamType(AudioManager.STREAM_RING)
+ .build());
+ boolean testShuffleModeEnabled = true;
+ @RepeatMode int testRepeatMode = REPEAT_MODE_ALL;
+ int testCurrentAdGroupIndex = 33;
+ int testCurrentAdIndexInAdGroup = 11;
+
+ AtomicInteger stateRef = new AtomicInteger();
+ AtomicReference timelineRef = new AtomicReference<>();
+ AtomicReference playlistMetadataRef = new AtomicReference<>();
+ AtomicReference audioAttributesRef = new AtomicReference<>();
+ AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
+ AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
+ AtomicBoolean shuffleModeEnabledRef = new AtomicBoolean();
+ AtomicInteger repeatModeRef = new AtomicInteger();
+ CountDownLatch latch = new CountDownLatch(7);
+ controller = controllerTestRule.createController(remoteSession.getToken());
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onAudioAttributesChanged(@NonNull AudioAttributes attributes) {
+ audioAttributesRef.set(attributes);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(@State int state) {
+ stateRef.set(state);
+ latch.countDown();
+ }
+
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineRef.set(timeline);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlaylistMetadataChanged(MediaMetadata playlistMetadata) {
+ playlistMetadataRef.set(playlistMetadata);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ currentAdGroupIndexRef.set(newPosition.adGroupIndex);
+ currentAdIndexInAdGroupRef.set(newPosition.adIndexInAdGroup);
+ latch.countDown();
+ }
+
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ shuffleModeEnabledRef.set(shuffleModeEnabled);
+ latch.countDown();
+ }
+
+ @Override
+ public void onRepeatModeChanged(@RepeatMode int repeatMode) {
+ repeatModeRef.set(repeatMode);
+ latch.countDown();
+ }
+ });
+
+ // TODO(b/149713425): Stop setting MediaItem as current media item.
+ // Currently it's necessary for trigger position discontinuity.
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaybackState(testState)
+ .setAudioAttributes(testAudioAttributes)
+ .setTimeline(testTimeline)
+ .setPlaylistMetadata(testPlaylistMetadata)
+ .setCurrentMediaItem(MediaTestUtils.createConvergedMediaItem("mediaId"))
+ .setShuffleModeEnabled(testShuffleModeEnabled)
+ .setRepeatMode(testRepeatMode)
+ .setCurrentAdGroupIndex(testCurrentAdGroupIndex)
+ .setCurrentAdIndexInAdGroup(testCurrentAdIndexInAdGroup)
+ .build();
+
+ remoteSession.setPlayer(playerConfig);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(stateRef.get()).isEqualTo(testState);
+ MediaTestUtils.assertMediaIdEquals(testTimeline, timelineRef.get());
+ assertThat(playlistMetadataRef.get()).isEqualTo(testPlaylistMetadata);
+ assertThat(audioAttributesRef.get()).isEqualTo(testAudioAttributes);
+ assertThat(currentAdGroupIndexRef.get()).isEqualTo(testCurrentAdGroupIndex);
+ assertThat(currentAdIndexInAdGroupRef.get()).isEqualTo(testCurrentAdIndexInAdGroup);
+ assertThat(shuffleModeEnabledRef.get()).isEqualTo(testShuffleModeEnabled);
+ assertThat(repeatModeRef.get()).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void setPlayer_updatesGetters() throws Exception {
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testDurationMs = 200;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentDurationMs = 300;
+ long testContentBufferedPositionMs = 240;
+ boolean testIsPlayingAd = true;
+ int testCurrentAdGroupIndex = 2;
+ int testCurrentAdIndexInAdGroup = 6;
+ int testWindowIndex = 1;
+ int testPeriodIndex = 2;
+
+ controller = controllerTestRule.createController(remoteSession.getToken());
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong durationMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentDurationMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
+ AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
+ AtomicInteger currentWindowIndexRef = new AtomicInteger();
+ AtomicInteger currentPeriodIndexRef = new AtomicInteger();
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ durationMsRef.set(controller.getDuration());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentDurationMsRef.set(controller.getContentDuration());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ isPlayingAdRef.set(controller.isPlayingAd());
+ currentAdGroupIndexRef.set(controller.getCurrentAdGroupIndex());
+ currentAdIndexInAdGroupRef.set(controller.getCurrentAdIndexInAdGroup());
+ currentWindowIndexRef.set(controller.getCurrentWindowIndex());
+ currentPeriodIndexRef.set(controller.getCurrentPeriodIndex());
+ latch.countDown();
+ }
+ });
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaybackState(Player.STATE_READY)
+ .setCurrentPosition(testCurrentPositionMs)
+ .setContentPosition(testContentPositionMs)
+ .setDuration(testDurationMs)
+ .setBufferedPosition(testBufferedPositionMs)
+ .setBufferedPercentage(testBufferedPercentage)
+ .setTotalBufferedDuration(testTotalBufferedDurationMs)
+ .setCurrentLiveOffset(testCurrentLiveOffsetMs)
+ .setContentDuration(testContentDurationMs)
+ .setContentBufferedPosition(testContentBufferedPositionMs)
+ .setIsPlayingAd(testIsPlayingAd)
+ .setCurrentAdGroupIndex(testCurrentAdGroupIndex)
+ .setCurrentAdIndexInAdGroup(testCurrentAdIndexInAdGroup)
+ .setCurrentWindowIndex(testWindowIndex)
+ .setCurrentPeriodIndex(testPeriodIndex)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(durationMsRef.get()).isEqualTo(testDurationMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentDurationMsRef.get()).isEqualTo(testContentDurationMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ assertThat(isPlayingAdRef.get()).isEqualTo(testIsPlayingAd);
+ assertThat(currentAdGroupIndexRef.get()).isEqualTo(testCurrentAdGroupIndex);
+ assertThat(currentAdIndexInAdGroupRef.get()).isEqualTo(testCurrentAdIndexInAdGroup);
+ assertThat(currentWindowIndexRef.get()).isEqualTo(testWindowIndex);
+ assertThat(currentPeriodIndexRef.get()).isEqualTo(testPeriodIndex);
+ }
+
+ @Test
+ public void onMediaItemTransition() throws Exception {
+ int currentIndex = 0;
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 5);
+ remoteSession.getMockPlayer().setTimeline(timeline);
+ remoteSession.getMockPlayer().setCurrentWindowIndex(currentIndex);
+ remoteSession
+ .getMockPlayer()
+ .notifyMediaItemTransition(
+ currentIndex, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
+
+ AtomicReference mediaItemFromParam = new AtomicReference<>();
+ AtomicReference mediaItemFromGetter = new AtomicReference<>();
+ AtomicInteger reasonRef = new AtomicInteger();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onMediaItemTransition(
+ @Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
+ mediaItemFromParam.set(mediaItem);
+ mediaItemFromGetter.set(controller.getCurrentMediaItem());
+ reasonRef.set(reason);
+ latch.countDown();
+ }
+ });
+
+ int testIndex = 3;
+ int testReason = Player.MEDIA_ITEM_TRANSITION_REASON_SEEK;
+ remoteSession.getMockPlayer().setCurrentWindowIndex(testIndex);
+ remoteSession.getMockPlayer().notifyMediaItemTransition(testIndex, testReason);
+ Timeline.Window window = new Timeline.Window();
+ MediaItem currentMediaItem = timeline.getWindow(testIndex, window).mediaItem;
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(mediaItemFromParam.get()).isEqualTo(currentMediaItem);
+ assertThat(mediaItemFromGetter.get()).isEqualTo(currentMediaItem);
+ assertThat(reasonRef.get()).isEqualTo(testReason);
+ }
+
+ @Test
+ public void onMediaItemTransition_withNullMediaIteam() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 1);
+ remoteSession.getMockPlayer().setTimeline(timeline);
+ remoteSession.getMockPlayer().setCurrentWindowIndex(0);
+ remoteSession
+ .getMockPlayer()
+ .notifyMediaItemTransition(0, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
+
+ AtomicReference mediaItemRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onMediaItemTransition(
+ @Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
+ mediaItemRef.set(mediaItem);
+ latch.countDown();
+ }
+ });
+
+ remoteSession.getMockPlayer().setTimeline(Timeline.EMPTY);
+ remoteSession
+ .getMockPlayer()
+ .notifyMediaItemTransition(
+ C.INDEX_UNSET, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(mediaItemRef.get()).isNull();
+ }
+
+ /** This also tests {@link MediaController#getPlaybackParameters()}. */
+ @Test
+ public void onPlaybackParametersChanged_isNotified() throws Exception {
+ PlaybackParameters testPlaybackParameters =
+ new PlaybackParameters(/* speed= */ 3.2f, /* pitch= */ 2.1f);
+ remoteSession.getMockPlayer().setPlaybackParameters(PlaybackParameters.DEFAULT);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference playbackParametersRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ playbackParametersRef.set(playbackParameters);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyPlaybackParametersChanged(testPlaybackParameters);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackParametersRef.get()).isEqualTo(testPlaybackParameters);
+ }
+
+ @Test
+ public void onPlaybackParametersChanged_updatesGetters() throws Exception {
+ PlaybackParameters testPlaybackParameters =
+ new PlaybackParameters(/* speed= */ 3.2f, /* pitch= */ 2.1f);
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentBufferedPositionMs = 240;
+
+ controller = controllerTestRule.createController(remoteSession.getToken());
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference playbackParametersRef = new AtomicReference<>();
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong durationMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentDurationMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
+ AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ playbackParametersRef.set(controller.getPlaybackParameters());
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ latch.countDown();
+ }
+ });
+
+ remoteSession.getMockPlayer().setCurrentPosition(testCurrentPositionMs);
+ remoteSession.getMockPlayer().setContentPosition(testContentPositionMs);
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPositionMs);
+ remoteSession.getMockPlayer().setBufferedPercentage(testBufferedPercentage);
+ remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteSession.getMockPlayer().notifyPlaybackParametersChanged(testPlaybackParameters);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackParametersRef.get()).isEqualTo(testPlaybackParameters);
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ }
+
+ /** This also tests {@link MediaController#getCurrentTimeline()}. */
+ @Test
+ public void onTimelineChanged() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference timelineFromParamRef = new AtomicReference<>();
+ AtomicReference timelineFromGetterRef = new AtomicReference<>();
+ AtomicInteger reasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineFromParamRef.set(timeline);
+ timelineFromGetterRef.set(controller.getCurrentTimeline());
+ reasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 2);
+ @Player.TimelineChangeReason int reason = Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE;
+ remoteSession.getMockPlayer().setTimeline(timeline);
+ remoteSession.getMockPlayer().notifyTimelineChanged(reason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaTestUtils.assertMediaIdEquals(timeline, timelineFromParamRef.get());
+ MediaTestUtils.assertMediaIdEquals(timeline, timelineFromGetterRef.get());
+ assertThat(reasonRef.get()).isEqualTo(reason);
+ }
+
+ @Test
+ @LargeTest
+ public void onTimelineChanged_withLongPlaylist() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicReference timelineRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineRef.set(timeline);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int windowCount = 5_000;
+ remoteSession.getMockPlayer().createAndSetFakeTimeline(windowCount);
+ remoteSession
+ .getMockPlayer()
+ .notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(timelineRef.get().getWindowCount()).isEqualTo(windowCount);
+ Timeline.Window window = new Timeline.Window();
+ for (int i = 0; i < windowCount; i++) {
+ assertThat(timelineRef.get().getWindow(i, window).mediaItem.mediaId)
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ public void onTimelineChanged_withEmptyTimeline() throws Exception {
+ remoteSession.getMockPlayer().createAndSetFakeTimeline(/* windowCount= */ 1);
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference timelineRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineRef.set(timeline);
+ latch.countDown();
+ }
+ };
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().setTimeline(Timeline.EMPTY);
+ remoteSession
+ .getMockPlayer()
+ .notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(timelineRef.get().getWindowCount()).isEqualTo(0);
+ assertThat(timelineRef.get().getPeriodCount()).isEqualTo(0);
+ }
+
+ /** This also tests {@link MediaController#getPlaylistMetadata()}. */
+ @Test
+ public void onPlaylistMetadataChanged() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicReference metadataFromParamRef = new AtomicReference<>();
+ AtomicReference metadataFromGetterRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaylistMetadataChanged(MediaMetadata metadata) {
+ metadataFromParamRef.set(metadata);
+ metadataFromGetterRef.set(controller.getPlaylistMetadata());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
+ RemoteMediaSession.RemoteMockPlayer player = remoteSession.getMockPlayer();
+ player.setPlaylistMetadata(playlistMetadata);
+ player.notifyPlaylistMetadataChanged();
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(metadataFromParamRef.get()).isEqualTo(playlistMetadata);
+ assertThat(metadataFromGetterRef.get()).isEqualTo(playlistMetadata);
+ }
+
+ /** This also tests {@link MediaController#getShuffleModeEnabled()}. */
+ @Test
+ public void onShuffleModeEnabledChanged() throws Exception {
+ RemoteMediaSession.RemoteMockPlayer player = remoteSession.getMockPlayer();
+ Timeline timeline =
+ new PlaylistTimeline(
+ MediaTestUtils.createConvergedMediaItems(/* size= */ 3),
+ /* shuffledIndices= */ new int[] {0, 2, 1});
+ player.setTimeline(timeline);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ player.setCurrentWindowIndex(2);
+ player.setShuffleModeEnabled(false);
+ player.notifyShuffleModeEnabledChanged();
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean shuffleModeEnabledFromParamRef = new AtomicBoolean();
+ AtomicBoolean shuffleModeEnabledFromGetterRef = new AtomicBoolean();
+ AtomicInteger previousIndexRef = new AtomicInteger();
+ AtomicInteger nextIndexRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ shuffleModeEnabledFromParamRef.set(shuffleModeEnabled);
+ shuffleModeEnabledFromGetterRef.set(controller.getShuffleModeEnabled());
+ previousIndexRef.set(controller.getPreviousWindowIndex());
+ nextIndexRef.set(controller.getNextWindowIndex());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ player.setShuffleModeEnabled(true);
+ player.notifyShuffleModeEnabledChanged();
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(shuffleModeEnabledFromParamRef.get()).isTrue();
+ assertThat(shuffleModeEnabledFromGetterRef.get()).isTrue();
+ assertThat(previousIndexRef.get()).isEqualTo(0);
+ assertThat(nextIndexRef.get()).isEqualTo(1);
+ }
+
+ /** This also tests {@link MediaController#getRepeatMode()}. */
+ @Test
+ public void onRepeatModeChanged() throws Exception {
+ RemoteMediaSession.RemoteMockPlayer player = remoteSession.getMockPlayer();
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ player.setTimeline(timeline);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ player.setCurrentWindowIndex(2);
+ player.setRepeatMode(Player.REPEAT_MODE_OFF);
+ player.notifyRepeatModeChanged();
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger repeatModeFromParamRef = new AtomicInteger();
+ AtomicInteger repeatModeFromGetterRef = new AtomicInteger();
+ AtomicInteger previousIndexRef = new AtomicInteger();
+ AtomicInteger nextIndexRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onRepeatModeChanged(@RepeatMode int repeatMode) {
+ repeatModeFromParamRef.set(repeatMode);
+ repeatModeFromGetterRef.set(controller.getRepeatMode());
+ previousIndexRef.set(controller.getPreviousWindowIndex());
+ nextIndexRef.set(controller.getNextWindowIndex());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int testRepeatMode = REPEAT_MODE_ALL;
+ player.setRepeatMode(testRepeatMode);
+ player.notifyRepeatModeChanged();
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(repeatModeFromParamRef.get()).isEqualTo(testRepeatMode);
+ assertThat(repeatModeFromGetterRef.get()).isEqualTo(testRepeatMode);
+ assertThat(previousIndexRef.get()).isEqualTo(1);
+ assertThat(nextIndexRef.get()).isEqualTo(0);
+ }
+
+ @Test
+ public void onPlayWhenReadyChanged_isNotified() throws Exception {
+ boolean testPlayWhenReady = true;
+ @Player.PlayWhenReadyChangeReason
+ int testReason = Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
+ @Player.PlaybackSuppressionReason
+ int testSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE;
+ remoteSession
+ .getMockPlayer()
+ .setPlayWhenReady(false, Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(2);
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ AtomicInteger playWhenReadyReasonRef = new AtomicInteger();
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ playWhenReadyRef.set(playWhenReady);
+ playWhenReadyReasonRef.set(reason);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlaybackSuppressionReasonChanged(int playbackSuppressionReason) {
+ playbackSuppressionReasonRef.set(playbackSuppressionReason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
+ assertThat(playWhenReadyReasonRef.get()).isEqualTo(testReason);
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testSuppressionReason);
+ }
+
+ @Test
+ public void onPlayWhenReadyChanged_updatesGetters() throws Exception {
+ boolean testPlayWhenReady = true;
+ @Player.PlaybackSuppressionReason int testReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE;
+ remoteSession
+ .getMockPlayer()
+ .setPlayWhenReady(false, Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentBufferedPositionMs = 240;
+
+ controller = controllerTestRule.createController(remoteSession.getToken());
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ controller.addListener(
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
+ playWhenReadyRef.set(controller.getPlayWhenReady());
+ playbackSuppressionReasonRef.set(controller.getPlaybackSuppressionReason());
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ latch.countDown();
+ }
+ });
+
+ remoteSession.getMockPlayer().setCurrentPosition(testCurrentPositionMs);
+ remoteSession.getMockPlayer().setContentPosition(testContentPositionMs);
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPositionMs);
+ remoteSession.getMockPlayer().setBufferedPercentage(testBufferedPercentage);
+ remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason);
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ }
+
+ @Test
+ public void onPlaybackSuppressionReasonChanged_isNotified() throws Exception {
+ boolean testPlayWhenReady = true;
+ @Player.PlaybackSuppressionReason
+ int testReason = Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
+ remoteSession
+ .getMockPlayer()
+ .setPlayWhenReady(testPlayWhenReady, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackSuppressionReasonChanged(int reason) {
+ playbackSuppressionReasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason);
+ }
+
+ @Test
+ public void onPlaybackSuppressionReasonChanged_updatesGetters() throws Exception {
+ boolean testPlayWhenReady = true;
+ @Player.PlaybackSuppressionReason
+ int testReason = Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentBufferedPositionMs = 240;
+ remoteSession
+ .getMockPlayer()
+ .setPlayWhenReady(testPlayWhenReady, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackSuppressionReasonChanged(int reason) {
+ playbackSuppressionReasonRef.set(controller.getPlaybackSuppressionReason());
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().setCurrentPosition(testCurrentPositionMs);
+ remoteSession.getMockPlayer().setContentPosition(testContentPositionMs);
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPositionMs);
+ remoteSession.getMockPlayer().setBufferedPercentage(testBufferedPercentage);
+ remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason);
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ }
+
+ @Test
+ public void onPlaybackStateChanged_isNotified() throws Exception {
+ @Player.State int testPlaybackState = STATE_BUFFERING;
+ remoteSession.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_IDLE);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackStateRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int reason) {
+ playbackStateRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyPlaybackStateChanged(testPlaybackState);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get()).isEqualTo(testPlaybackState);
+ }
+
+ @Test
+ public void onPlaybackStateChanged_updatesGetters() throws Exception {
+ @Player.State int testPlaybackState = STATE_BUFFERING;
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentBufferedPositionMs = 240;
+ remoteSession.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_IDLE);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackStateRef = new AtomicInteger();
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int reason) {
+ playbackStateRef.set(controller.getPlaybackState());
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().setCurrentPosition(testCurrentPositionMs);
+ remoteSession.getMockPlayer().setContentPosition(testContentPositionMs);
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPositionMs);
+ remoteSession.getMockPlayer().setBufferedPercentage(testBufferedPercentage);
+ remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteSession.getMockPlayer().notifyPlaybackStateChanged(testPlaybackState);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get()).isEqualTo(testPlaybackState);
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ }
+
+ @Test
+ public void onIsPlayingChanged_isNotified() throws Exception {
+ boolean testIsPlaying = true;
+ remoteSession.getMockPlayer().notifyIsPlayingChanged(false);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean isPlayingRef = new AtomicBoolean();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ isPlayingRef.set(isPlaying);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyIsPlayingChanged(testIsPlaying);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(isPlayingRef.get()).isEqualTo(testIsPlaying);
+ }
+
+ @Test
+ public void onIsPlayingChanged_updatesGetters() throws Exception {
+ boolean testIsPlaying = true;
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentBufferedPositionMs = 240;
+ remoteSession.getMockPlayer().notifyIsPlayingChanged(false);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(/* timeDiff= */ 0L));
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean isPlayingRef = new AtomicBoolean();
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ isPlayingRef.set(controller.isPlaying());
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().setCurrentPosition(testCurrentPositionMs);
+ remoteSession.getMockPlayer().setContentPosition(testContentPositionMs);
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPositionMs);
+ remoteSession.getMockPlayer().setBufferedPercentage(testBufferedPercentage);
+ remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteSession.getMockPlayer().notifyIsPlayingChanged(testIsPlaying);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(isPlayingRef.get()).isEqualTo(testIsPlaying);
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ }
+
+ @Test
+ public void onIsLoadingChanged_isNotified() throws Exception {
+ boolean testIsLoading = true;
+ remoteSession.getMockPlayer().notifyIsLoadingChanged(false);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean isLoadingFromParamRef = new AtomicBoolean();
+ AtomicBoolean isLoadingFromGetterRef = new AtomicBoolean();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onIsLoadingChanged(boolean isLoading) {
+ isLoadingFromParamRef.set(isLoading);
+ isLoadingFromGetterRef.set(controller.isLoading());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyIsLoadingChanged(testIsLoading);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(isLoadingFromParamRef.get()).isEqualTo(testIsLoading);
+ assertThat(isLoadingFromGetterRef.get()).isEqualTo(testIsLoading);
+ }
+
+ @Test
+ public void onPositionDiscontinuity_isNotified() throws Exception {
+ PositionInfo testOldPosition =
+ new PositionInfo(
+ /* windowUid= */ null,
+ /* windowIndex= */ 2,
+ /* periodUid= */ null,
+ /* periodIndex= */ C.INDEX_UNSET,
+ /* positionMs= */ 300L,
+ /* contentPositionMs= */ 200L,
+ /* adGroupIndex= */ 33,
+ /* adIndexInAdGroup= */ 2);
+ PositionInfo testNewPosition =
+ new PositionInfo(
+ /* windowUid= */ null,
+ /* windowIndex= */ 3,
+ /* periodUid= */ null,
+ /* periodIndex= */ C.INDEX_UNSET,
+ /* positionMs= */ 0L,
+ /* contentPositionMs= */ 0L,
+ /* adGroupIndex= */ C.INDEX_UNSET,
+ /* adIndexInAdGroup= */ C.INDEX_UNSET);
+ @DiscontinuityReason int testReason = Player.DISCONTINUITY_REASON_INTERNAL;
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference oldPositionRef = new AtomicReference<>();
+ AtomicReference newPositionRef = new AtomicReference<>();
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ oldPositionRef.set(oldPosition);
+ newPositionRef.set(newPosition);
+ positionDiscontinuityReasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession
+ .getMockPlayer()
+ .notifyPositionDiscontinuity(testOldPosition, testNewPosition, testReason);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get()).isEqualTo(testReason);
+ assertThat(oldPositionRef.get()).isEqualTo(testOldPosition);
+ assertThat(newPositionRef.get()).isEqualTo(testNewPosition);
+ }
+
+ @Test
+ public void onPositionDiscontinuity_updatesGetters() throws Exception {
+ long testCurrentPositionMs = 11;
+ long testContentPositionMs = 33;
+ long testDurationMs = 200;
+ long testBufferedPositionMs = 100;
+ int testBufferedPercentage = 50;
+ long testTotalBufferedDurationMs = 120;
+ long testCurrentLiveOffsetMs = 10;
+ long testContentDurationMs = 300;
+ long testContentBufferedPositionMs = 240;
+ boolean testIsPlayingAd = true;
+ int testCurrentAdGroupIndex = 33;
+ int testCurrentAdIndexInAdGroup = 11;
+ PositionInfo newPositionInfo =
+ new PositionInfo(
+ /* windowUid= */ null,
+ /* windowIndex= */ C.INDEX_UNSET,
+ /* periodUid= */ null,
+ /* periodIndex= */ C.INDEX_UNSET,
+ testCurrentPositionMs,
+ testContentPositionMs,
+ testCurrentAdGroupIndex,
+ testCurrentAdIndexInAdGroup);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong durationMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentDurationMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
+ AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ durationMsRef.set(controller.getDuration());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentDurationMsRef.set(controller.getContentDuration());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ isPlayingAdRef.set(controller.isPlayingAd());
+ currentAdGroupIndexRef.set(controller.getCurrentAdGroupIndex());
+ currentAdIndexInAdGroupRef.set(controller.getCurrentAdIndexInAdGroup());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ RemoteMockPlayer remoteMockPlayer = remoteSession.getMockPlayer();
+ remoteMockPlayer.setCurrentPosition(testCurrentPositionMs);
+ remoteMockPlayer.setContentPosition(testContentPositionMs);
+ remoteMockPlayer.setDuration(testDurationMs);
+ remoteMockPlayer.setBufferedPosition(testBufferedPositionMs);
+ remoteMockPlayer.setBufferedPercentage(testBufferedPercentage);
+ remoteMockPlayer.setTotalBufferedDuration(testTotalBufferedDurationMs);
+ remoteMockPlayer.setCurrentLiveOffset(testCurrentLiveOffsetMs);
+ remoteMockPlayer.setContentDuration(testContentDurationMs);
+ remoteMockPlayer.setContentBufferedPosition(testContentBufferedPositionMs);
+ remoteMockPlayer.setIsPlayingAd(testIsPlayingAd);
+ remoteMockPlayer.setCurrentAdGroupIndex(testCurrentAdGroupIndex);
+ remoteMockPlayer.setCurrentAdIndexInAdGroup(testCurrentAdIndexInAdGroup);
+ remoteMockPlayer.notifyPositionDiscontinuity(
+ /* oldPositionInfo= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
+ newPositionInfo,
+ Player.DISCONTINUITY_REASON_INTERNAL);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs);
+ assertThat(durationMsRef.get()).isEqualTo(testDurationMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(testBufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs);
+ assertThat(contentDurationMsRef.get()).isEqualTo(testContentDurationMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs);
+ assertThat(isPlayingAdRef.get()).isEqualTo(testIsPlayingAd);
+ assertThat(currentAdGroupIndexRef.get()).isEqualTo(testCurrentAdGroupIndex);
+ assertThat(currentAdIndexInAdGroupRef.get()).isEqualTo(testCurrentAdIndexInAdGroup);
+ }
+
+ /** This also tests {@link MediaController#getAvailableSessionCommands()}. */
+ @Test
+ public void onAvailableSessionCommandsChanged() throws Exception {
+ SessionCommands commands =
+ new SessionCommands.Builder()
+ .add(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
+ .build();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController.ControllerCallback callback =
+ new MediaController.ControllerCallback() {
+ @Override
+ public void onAvailableSessionCommandsChanged(
+ MediaController controller, SessionCommands commandsOut) {
+ assertThat(commandsOut).isEqualTo(commands);
+ latch.countDown();
+ }
+ };
+
+ MediaController controller =
+ controllerTestRule.createController(remoteSession.getToken(), true, null, callback);
+ remoteSession.setAvailableCommands(commands, Player.Commands.EMPTY);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(controller.getAvailableSessionCommands()).isEqualTo(commands);
+ }
+
+ /** This also tests {@link MediaController#getAvailableCommands()}. */
+ @Test
+ public void onAvailableCommandsChanged_isCalledByPlayerChange() throws Exception {
+ Commands commandsWithAllCommands = createPlayerCommandsWithAllCommands();
+ Commands commandsWithSetRepeat = createPlayerCommandsWith(COMMAND_SET_REPEAT_MODE);
+
+ remoteSession.getMockPlayer().notifyAvailableCommandsChanged(commandsWithAllCommands);
+ MediaController controller =
+ controllerTestRule.createController(remoteSession.getToken(), true, null, null);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference availableCommandsRef = new AtomicReference<>();
+ AtomicReference availableCommandsFromGetterRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onAvailableCommandsChanged(Commands availableCommands) {
+ availableCommandsRef.set(availableCommands);
+ availableCommandsFromGetterRef.set(controller.getAvailableCommands());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyAvailableCommandsChanged(commandsWithSetRepeat);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(availableCommandsRef.get()).isEqualTo(commandsWithSetRepeat);
+ assertThat(availableCommandsFromGetterRef.get()).isEqualTo(commandsWithSetRepeat);
+ }
+
+ /** This also tests {@link MediaController#getAvailableCommands()}. */
+ @Test
+ public void onAvailableCommandsChanged_isCalledBySessionChange() throws Exception {
+ Commands commandsWithAllCommands = createPlayerCommandsWithAllCommands();
+ Commands commandsWithSetRepeat = createPlayerCommandsWith(COMMAND_SET_REPEAT_MODE);
+
+ remoteSession.getMockPlayer().notifyAvailableCommandsChanged(commandsWithAllCommands);
+ MediaController controller =
+ controllerTestRule.createController(remoteSession.getToken(), true, null, null);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference availableCommandsRef = new AtomicReference<>();
+ AtomicReference availableCommandsFromGetterRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onAvailableCommandsChanged(Commands availableCommands) {
+ availableCommandsRef.set(availableCommands);
+ availableCommandsFromGetterRef.set(controller.getAvailableCommands());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.setAvailableCommands(SessionCommands.EMPTY, commandsWithSetRepeat);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(availableCommandsRef.get()).isEqualTo(commandsWithSetRepeat);
+ assertThat(availableCommandsFromGetterRef.get()).isEqualTo(commandsWithSetRepeat);
+ }
+
+ @Test
+ public void onCustomCommand() throws Exception {
+ String testCommandAction = "test_action";
+ SessionCommand testCommand = new SessionCommand(testCommandAction, null);
+ Bundle testArgs = TestUtils.createTestBundle();
+
+ CountDownLatch latch = new CountDownLatch(2);
+ MediaController.ControllerCallback callback =
+ new MediaController.ControllerCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onCustomCommand(
+ @NonNull MediaController controller, @NonNull SessionCommand command, Bundle args) {
+ assertThat(command).isEqualTo(testCommand);
+ assertThat(TestUtils.equals(testArgs, args)).isTrue();
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ controllerTestRule.createController(
+ remoteSession.getToken(),
+ /* waitForConnect= */ true,
+ /* connectionHints= */ null,
+ callback);
+
+ // TODO(jaewan): Test with multiple controllers
+ remoteSession.broadcastCustomCommand(testCommand, testArgs);
+
+ // TODO(jaewan): Test receivers as well.
+ remoteSession.sendCustomCommand(testCommand, testArgs);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onCustomLayoutChanged() throws Exception {
+ List buttons = new ArrayList<>();
+
+ CommandButton button =
+ new CommandButton.Builder()
+ .setPlayerCommand(COMMAND_PLAY_PAUSE)
+ .setDisplayName("button")
+ .build();
+ buttons.add(button);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController.ControllerCallback callback =
+ new MediaController.ControllerCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onSetCustomLayout(
+ @NonNull MediaController controller, @NonNull List layout) {
+ assertThat(layout).hasSize(buttons.size());
+ for (int i = 0; i < layout.size(); i++) {
+ assertThat(layout.get(i).playerCommand).isEqualTo(buttons.get(i).playerCommand);
+ assertThat(layout.get(i).displayName.toString())
+ .isEqualTo(buttons.get(i).displayName.toString());
+ }
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ controllerTestRule.createController(
+ remoteSession.getToken(),
+ /* waitForConnect= */ true,
+ /* connectionHints= */ null,
+ callback);
+ remoteSession.setCustomLayout(buttons);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onVideoSizeChanged() throws Exception {
+ VideoSize testVideoSize =
+ new VideoSize(
+ /* width= */ 100,
+ /* height= */ 42,
+ /* unappliedRotationDegrees= */ 90,
+ /* pixelWidthHeightRatio= */ 1.2f);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference videoSizeFromParamRef = new AtomicReference<>();
+ AtomicReference videoSizeFromGetterRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
+ videoSizeFromParamRef.set(videoSize);
+ videoSizeFromGetterRef.set(controller.getVideoSize());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyVideoSizeChanged(testVideoSize);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(videoSizeFromParamRef.get()).isEqualTo(testVideoSize);
+ assertThat(videoSizeFromGetterRef.get()).isEqualTo(testVideoSize);
+ }
+
+ @Test
+ public void onAudioAttributesChanged_isCalledAndUpdatesGetter() throws Exception {
+ AudioAttributes testAttributes =
+ new AudioAttributes.Builder()
+ .setUsage(C.USAGE_MEDIA)
+ .setContentType(C.CONTENT_TYPE_MOVIE)
+ .build();
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference attributesRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onAudioAttributesChanged(@NonNull AudioAttributes attributes) {
+ if (testAttributes.equals(attributes)) {
+ attributesRef.set(controller.getAudioAttributes());
+ latch.countDown();
+ }
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyAudioAttributesChanged(testAttributes);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(attributesRef.get()).isEqualTo(testAttributes);
+ }
+
+ @Test
+ public void onDeviceInfoChanged_isCalledByPlayerChange() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicReference deviceInfoFromParamRef = new AtomicReference<>();
+ AtomicReference deviceInfoFromGetterRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceInfoChanged(@NonNull DeviceInfo deviceInfo) {
+ deviceInfoFromParamRef.set(deviceInfo);
+ deviceInfoFromGetterRef.set(controller.getDeviceInfo());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setDeviceInfo(deviceInfo).build();
+ remoteSession.setPlayer(playerConfig);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceInfoFromParamRef.get()).isEqualTo(deviceInfo);
+ assertThat(deviceInfoFromGetterRef.get()).isEqualTo(deviceInfo);
+ }
+
+ @Test
+ public void onDeviceInfoChanged_isCalledByDeviceInfoChange() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicReference deviceInfoFromParamRef = new AtomicReference<>();
+ AtomicReference deviceInfoFromGetterRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceInfoChanged(@NonNull DeviceInfo deviceInfo) {
+ deviceInfoFromParamRef.set(deviceInfo);
+ deviceInfoFromGetterRef.set(controller.getDeviceInfo());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 1, /* maxVolume= */ 23);
+ remoteSession.getMockPlayer().notifyDeviceInfoChanged(deviceInfo);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceInfoFromParamRef.get()).isEqualTo(deviceInfo);
+ assertThat(deviceInfoFromGetterRef.get()).isEqualTo(deviceInfo);
+ }
+
+ @Test
+ public void onDeviceVolumeChanged_isCalledByDeviceVolumeChange() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfo)
+ .setDeviceVolume(23)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicInteger deviceVolumeFromParamRef = new AtomicInteger();
+ AtomicInteger deviceVolumeFromGetterRef = new AtomicInteger();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceVolumeChanged(int volume, boolean muted) {
+ deviceVolumeFromParamRef.set(volume);
+ deviceVolumeFromGetterRef.set(controller.getDeviceVolume());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int targetVolume = 45;
+ remoteSession.getMockPlayer().notifyDeviceVolumeChanged(targetVolume, /* muted= */ false);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceVolumeFromParamRef.get()).isEqualTo(targetVolume);
+ assertThat(deviceVolumeFromGetterRef.get()).isEqualTo(targetVolume);
+ }
+
+ @Test
+ public void onDeviceVolumeChanged_isCalledByDeviceMutedChange() throws Exception {
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setDeviceMuted(false).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AtomicBoolean deviceMutedFromParamRef = new AtomicBoolean();
+ AtomicBoolean deviceMutedFromGetterRef = new AtomicBoolean();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceVolumeChanged(int volume, boolean muted) {
+ deviceMutedFromParamRef.set(muted);
+ deviceMutedFromGetterRef.set(controller.isDeviceMuted());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ remoteSession.getMockPlayer().notifyDeviceVolumeChanged(/* volume= */ 0, /* muted= */ true);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceMutedFromParamRef.get()).isTrue();
+ assertThat(deviceMutedFromGetterRef.get()).isTrue();
+ }
+
+ @Test
+ public void onEvents_whenOnRepeatModeChanges_isCalledAfterOtherCallbacks() throws Exception {
+ Player.Events testEvents =
+ new Player.Events(new ExoFlags.Builder().add(EVENT_REPEAT_MODE_CHANGED).build());
+ CopyOnWriteArrayList callbackEventCodes = new CopyOnWriteArrayList<>();
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(2);
+ AtomicReference 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);
+ remoteSession.getMockPlayer().setRepeatMode(REPEAT_MODE_ONE);
+ remoteSession.getMockPlayer().notifyRepeatModeChanged();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(callbackEventCodes).containsExactly(EVENT_REPEAT_MODE_CHANGED, EVENT_ON_EVENTS);
+ assertThat(eventsRef.get()).isEqualTo(testEvents);
+ }
+
+ // TODO(b/144387281): Move this into a separate test class for state masking.
+ @Test
+ public void setPlayWhenReady_stateMasking() throws Exception {
+ boolean testPlayWhenReady = true;
+ @Player.PlaybackSuppressionReason int testReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE;
+ boolean testIsPlaying = true;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaybackState(Player.STATE_READY)
+ .setPlayWhenReady(false)
+ .setPlaybackSuppressionReason(
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(4);
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ AtomicBoolean isPlayingRef = new AtomicBoolean();
+ AtomicReference onEventsRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ playWhenReadyRef.set(playWhenReady);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlaybackSuppressionReasonChanged(int playbackSuppressionReason) {
+ playbackSuppressionReasonRef.set(playbackSuppressionReason);
+ latch.countDown();
+ }
+
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ isPlayingRef.set(isPlaying);
+ latch.countDown();
+ }
+
+ @Override
+ public void onEvents(Player player, Player.Events events) {
+ onEventsRef.set(events);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setPlayWhenReady(testPlayWhenReady);
+ assertThat(controller.getPlayWhenReady()).isEqualTo(testPlayWhenReady);
+ assertThat(controller.getPlaybackSuppressionReason()).isEqualTo(testReason);
+ assertThat(controller.isPlaying()).isEqualTo(testIsPlaying);
+ });
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason);
+ assertThat(isPlayingRef.get()).isEqualTo(testIsPlaying);
+ assertThat(onEventsRef.get().contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)).isTrue();
+ assertThat(onEventsRef.get().contains(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED))
+ .isTrue();
+ assertThat(onEventsRef.get().contains(Player.EVENT_IS_PLAYING_CHANGED)).isTrue();
+ }
+
+ // TODO(b/144387281): Move this into a separate test class for state masking.
+ @Test
+ public void setShuffleModeEnabled_stateMasking() throws Exception {
+ boolean testShuffleModeEnabled = true;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setShuffleModeEnabled(false).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(2);
+ AtomicBoolean shuffleModeEnabledRef = new AtomicBoolean();
+ AtomicReference onEventsRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ shuffleModeEnabledRef.set(shuffleModeEnabled);
+ latch.countDown();
+ }
+
+ @Override
+ public void onEvents(Player player, Player.Events events) {
+ onEventsRef.set(events);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setShuffleModeEnabled(testShuffleModeEnabled);
+ assertThat(controller.getShuffleModeEnabled()).isEqualTo(testShuffleModeEnabled);
+ });
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(shuffleModeEnabledRef.get()).isEqualTo(testShuffleModeEnabled);
+ assertThat(onEventsRef.get().contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)).isTrue();
+ }
+
+ // TODO(b/144387281): Move this into a separate test class for state masking.
+ @Test
+ public void setRepeatMode_stateMasking() throws Exception {
+ int testRepeatMode = REPEAT_MODE_ALL;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setRepeatMode(REPEAT_MODE_ONE).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ CountDownLatch latch = new CountDownLatch(2);
+ AtomicInteger repeatModeRef = new AtomicInteger();
+ AtomicReference onEventsRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ repeatModeRef.set(repeatMode);
+ latch.countDown();
+ }
+
+ @Override
+ public void onEvents(Player player, Player.Events events) {
+ onEventsRef.set(events);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setRepeatMode(testRepeatMode);
+ assertThat(controller.getRepeatMode()).isEqualTo(testRepeatMode);
+ });
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(repeatModeRef.get()).isEqualTo(testRepeatMode);
+ assertThat(onEventsRef.get().contains(Player.EVENT_REPEAT_MODE_CHANGED)).isTrue();
+ }
+
+ private void testControllerAfterSessionIsClosed(@NonNull String id) throws Exception {
+ // This cause session service to be died.
+ remoteSession.release();
+ controllerTestRule.waitForDisconnect(controller, true);
+ testNoInteraction();
+
+ // Ensure that the controller cannot use newly create session with the same ID.
+ // Recreated session has different session stub, so previously created controller
+ // shouldn't be available.
+ remoteSession = createRemoteMediaSession(id);
+ testNoInteraction();
+ }
+
+ // Test that session and controller doesn't interact.
+ // Note that this method can be called after the session is died, so session may not have
+ // valid player.
+ private void testNoInteraction() throws Exception {
+ // TODO: check that calls from the controller to session shouldn't be delivered.
+
+ // Calls from the session to controller shouldn't be delivered.
+ CountDownLatch latch = new CountDownLatch(1);
+ controllerTestRule.setRunnableForOnCustomCommand(
+ controller,
+ new Runnable() {
+ @Override
+ public void run() {
+ latch.countDown();
+ }
+ });
+ SessionCommand customCommand = new SessionCommand("testNoInteraction", null);
+
+ remoteSession.broadcastCustomCommand(customCommand, null);
+
+ assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ controllerTestRule.setRunnableForOnCustomCommand(controller, null);
+ }
+
+ private RemoteMediaSession createRemoteMediaSession(@NonNull String id) throws RemoteException {
+ return new RemoteMediaSession(id, context, /* tokenExtras= */ null);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackWithMediaSessionCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackWithMediaSessionCompatTest.java
new file mode 100644
index 0000000000..9dfef301e3
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCallbackWithMediaSessionCompatTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.util.ExoFlags;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaController.ControllerCallback} with {@link MediaSessionCompat}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerCallbackWithMediaSessionCompatTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private static final int EVENT_ON_EVENTS = C.INDEX_UNSET;
+
+ private final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaControllerCallbackTest");
+ private final MediaControllerTestRule controllerTestRule =
+ new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ private Context context;
+ private RemoteMediaSessionCompat session;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ session = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
+ }
+
+ @After
+ public void cleanUp() throws RemoteException {
+ session.cleanUp();
+ }
+
+ @Test
+ public void onEvents_whenOnRepeatModeChanges_isCalledAfterOtherCallbacks() throws Exception {
+ Player.Events testEvents =
+ new Player.Events(new ExoFlags.Builder().add(EVENT_REPEAT_MODE_CHANGED).build());
+ CopyOnWriteArrayList callbackEventCodes = new CopyOnWriteArrayList<>();
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(2);
+ AtomicReference 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);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCompatCallbackWithMediaSessionTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCompatCallbackWithMediaSessionTest.java
new file mode 100644
index 0000000000..e22dee8b50
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -0,0 +1,947 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
+import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID;
+import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_USER_RATING;
+import static com.google.android.exoplayer2.Player.STATE_ENDED;
+import static com.google.android.exoplayer2.Player.STATE_READY;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat;
+import androidx.media.AudioAttributesCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.HeartRating;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.RepeatMode;
+import com.google.android.exoplayer2.Player.State;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.PollingCheck;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaControllerCompat.Callback} with {@link MediaSession}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerCompatCallbackWithMediaSessionTest {
+
+ private static final String TAG = "MCCCallbackTestWithMS2";
+ private static final float EPSILON = 1e-6f;
+
+ @Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+
+ private Context context;
+ private TestHandler handler;
+ private RemoteMediaSession session;
+ private MediaControllerCompat controllerCompat;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ handler = threadTestRule.getHandler();
+ session = new RemoteMediaSession(TAG, context, null);
+ controllerCompat = new MediaControllerCompat(context, session.getCompatToken());
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ session.release();
+ }
+
+ @Test
+ public void gettersAfterConnected() throws Exception {
+ @State int testState = STATE_READY;
+ int testBufferingPosition = 1500;
+ float testSpeed = 1.5f;
+ int testItemIndex = 0;
+ List testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 3);
+ testMediaItems.set(
+ testItemIndex,
+ new MediaItem.Builder()
+ .setMediaId(testMediaItems.get(testItemIndex).mediaId)
+ .setMediaMetadata(
+ new MediaMetadata.Builder()
+ .setUserRating(new HeartRating(/* isHeart= */ true))
+ .build())
+ .build());
+ Timeline testTimeline = new PlaylistTimeline(testMediaItems);
+ String testPlaylistTitle = "testPlaylistTitle";
+ MediaMetadata testPlaylistMetadata =
+ new MediaMetadata.Builder().setTitle(testPlaylistTitle).build();
+ boolean testShuffleModeEnabled = true;
+ @RepeatMode int testRepeatMode = Player.REPEAT_MODE_ONE;
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaybackState(testState)
+ .setBufferedPosition(testBufferingPosition)
+ .setPlaybackParameters(new PlaybackParameters(testSpeed))
+ .setTimeline(testTimeline)
+ .setPlaylistMetadata(testPlaylistMetadata)
+ .setCurrentMediaItem(testMediaItems.get(testItemIndex))
+ .setShuffleModeEnabled(testShuffleModeEnabled)
+ .setRepeatMode(testRepeatMode)
+ .build();
+ session.setPlayer(playerConfig);
+
+ MediaControllerCompat controller = new MediaControllerCompat(context, session.getCompatToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ controller.registerCallback(
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onSessionReady() {
+ latch.countDown();
+ }
+ },
+ handler);
+
+ assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(
+ MediaUtils.convertToPlaybackState(
+ controller.getPlaybackState(),
+ controller.getMetadata(),
+ /* timeDiffMs= */ C.TIME_UNSET))
+ .isEqualTo(testState);
+ assertThat(controller.getPlaybackState().getBufferedPosition())
+ .isEqualTo(testBufferingPosition);
+ assertThat(controller.getPlaybackState().getPlaybackSpeed()).isWithin(EPSILON).of(testSpeed);
+
+ assertThat(controller.getMetadata().getString(METADATA_KEY_MEDIA_ID))
+ .isEqualTo(testMediaItems.get(testItemIndex).mediaId);
+ assertThat(controller.getRatingType()).isEqualTo(RatingCompat.RATING_HEART);
+
+ List queue = controller.getQueue();
+ assertThat(queue).isNotNull();
+ assertThat(queue).hasSize(testTimeline.getWindowCount());
+ for (int i = 0; i < testTimeline.getWindowCount(); i++) {
+ assertThat(queue.get(i).getDescription().getMediaId())
+ .isEqualTo(testMediaItems.get(i).mediaId);
+ }
+ assertThat(testPlaylistTitle).isEqualTo(controller.getQueueTitle().toString());
+ assertThat(PlaybackStateCompat.SHUFFLE_MODE_ALL).isEqualTo(controller.getShuffleMode());
+ assertThat(PlaybackStateCompat.REPEAT_MODE_ONE).isEqualTo(controller.getRepeatMode());
+ }
+
+ @Test
+ public void getError_withPlayerErrorAfterConnected_returnsError() throws Exception {
+ ExoPlaybackException testPlayerError = ExoPlaybackException.createForRemote("testremote");
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setPlayerError(testPlayerError).build();
+ session.setPlayer(playerConfig);
+
+ MediaControllerCompat controller = new MediaControllerCompat(context, session.getCompatToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ controller.registerCallback(
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onSessionReady() {
+ latch.countDown();
+ }
+ },
+ handler);
+
+ assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ assertPlaybackStateCompatErrorEquals(controller.getPlaybackState(), testPlayerError);
+ }
+
+ @Test
+ public void playerError_notified() throws Exception {
+ ExoPlaybackException playerError =
+ ExoPlaybackException.createForUnexpected(new RuntimeException("player error"));
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ playbackStateCompatRef.set(state);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().notifyPlayerError(playerError);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ PlaybackStateCompat state = playbackStateCompatRef.get();
+ assertPlaybackStateCompatErrorEquals(state, playerError);
+ }
+
+ @Test
+ public void repeatModeChange() throws Exception {
+ @PlaybackStateCompat.RepeatMode int testRepeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger repeatModeRef = new AtomicInteger();
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
+ repeatModeRef.set(repeatMode);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().setRepeatMode(Player.REPEAT_MODE_ALL);
+ session.getMockPlayer().notifyRepeatModeChanged();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(repeatModeRef.get()).isEqualTo(testRepeatMode);
+ assertThat(controllerCompat.getRepeatMode()).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void shuffleModeChange() throws Exception {
+ @PlaybackStateCompat.ShuffleMode
+ int testShuffleModeEnabled = PlaybackStateCompat.SHUFFLE_MODE_ALL;
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger shuffleModeRef = new AtomicInteger();
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
+ shuffleModeRef.set(shuffleMode);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().setShuffleModeEnabled(/* shuffleModeEnabled= */ true);
+ session.getMockPlayer().notifyShuffleModeEnabledChanged();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(shuffleModeRef.get()).isEqualTo(testShuffleModeEnabled);
+ assertThat(controllerCompat.getShuffleMode()).isEqualTo(testShuffleModeEnabled);
+ }
+
+ @Test
+ public void release() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onSessionDestroyed() {
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.release();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void setPlayer_isNotified() throws Exception {
+ @State int testState = STATE_READY;
+ boolean testPlayWhenReady = true;
+ long testDurationMs = 200;
+ long testCurrentPositionMs = 11;
+ long testBufferedPositionMs = 100;
+ PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
+ int testItemIndex = 0;
+ List testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 3);
+ testMediaItems.set(
+ testItemIndex,
+ new MediaItem.Builder()
+ .setMediaId(testMediaItems.get(testItemIndex).mediaId)
+ .setMediaMetadata(
+ new MediaMetadata.Builder()
+ .setUserRating(new HeartRating(/* isHeart= */ true))
+ .build())
+ .build());
+ Timeline testTimeline = new PlaylistTimeline(testMediaItems);
+ String testPlaylistTitle = "testPlaylistTitle";
+ MediaMetadata testPlaylistMetadata =
+ new MediaMetadata.Builder().setTitle(testPlaylistTitle).build();
+ boolean testShuffleModeEnabled = true;
+ @RepeatMode int testRepeatMode = Player.REPEAT_MODE_ONE;
+
+ AtomicReference playbackStateRef = new AtomicReference<>();
+ AtomicReference metadataRef = new AtomicReference<>();
+ AtomicReference queueTitleRef = new AtomicReference<>();
+ AtomicInteger shuffleModeRef = new AtomicInteger();
+ AtomicInteger repeatModeRef = new AtomicInteger();
+ CountDownLatch latchForPlaybackState = new CountDownLatch(1);
+ CountDownLatch latchForMetadata = new CountDownLatch(1);
+ CountDownLatch latchForQueue = new CountDownLatch(2);
+ CountDownLatch latchForShuffleMode = new CountDownLatch(1);
+ CountDownLatch latchForRepeatMode = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ playbackStateRef.set(state);
+ latchForPlaybackState.countDown();
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ metadataRef.set(metadata);
+ latchForMetadata.countDown();
+ }
+
+ @Override
+ public void onQueueChanged(List queue) {
+ latchForQueue.countDown();
+ }
+
+ @Override
+ public void onQueueTitleChanged(CharSequence title) {
+ queueTitleRef.set(title);
+ latchForQueue.countDown();
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ repeatModeRef.set(repeatMode);
+ latchForRepeatMode.countDown();
+ }
+
+ @Override
+ public void onShuffleModeChanged(int shuffleMode) {
+ shuffleModeRef.set(shuffleMode);
+ latchForShuffleMode.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaybackState(testState)
+ .setPlayWhenReady(testPlayWhenReady)
+ .setCurrentPosition(testCurrentPositionMs)
+ .setBufferedPosition(testBufferedPositionMs)
+ .setDuration(testDurationMs)
+ .setPlaybackParameters(playbackParameters)
+ .setTimeline(testTimeline)
+ .setPlaylistMetadata(testPlaylistMetadata)
+ .setCurrentMediaItem(testMediaItems.get(testItemIndex))
+ .setShuffleModeEnabled(testShuffleModeEnabled)
+ .setRepeatMode(testRepeatMode)
+ .build();
+ session.setPlayer(playerConfig);
+
+ assertThat(latchForPlaybackState.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get().getBufferedPosition()).isEqualTo(testBufferedPositionMs);
+ assertThat(playbackStateRef.get().getPosition()).isEqualTo(testCurrentPositionMs);
+ assertThat(playbackStateRef.get().getPlaybackSpeed())
+ .isWithin(EPSILON)
+ .of(playbackParameters.speed);
+
+ assertThat(latchForMetadata.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(metadataRef.get().getString(METADATA_KEY_MEDIA_ID))
+ .isEqualTo(testMediaItems.get(testItemIndex).mediaId);
+ assertThat(metadataRef.get().getLong(METADATA_KEY_DURATION)).isEqualTo(testDurationMs);
+ @PlaybackStateCompat.State
+ int playbackStateFromControllerCompat =
+ MediaUtils.convertToPlaybackState(
+ playbackStateRef.get(), metadataRef.get(), /* timeDiffMs= */ C.TIME_UNSET);
+ assertThat(playbackStateFromControllerCompat).isEqualTo(testState);
+ assertThat(metadataRef.get().getRating(METADATA_KEY_USER_RATING).hasHeart()).isTrue();
+
+ assertThat(latchForQueue.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ List queue = controllerCompat.getQueue();
+ assertThat(queue).hasSize(testTimeline.getWindowCount());
+ for (int i = 0; i < testTimeline.getWindowCount(); i++) {
+ assertThat(queue.get(i).getDescription().getMediaId())
+ .isEqualTo(testMediaItems.get(i).mediaId);
+ }
+ assertThat(queueTitleRef.get().toString()).isEqualTo(testPlaylistTitle);
+
+ assertThat(latchForShuffleMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(shuffleModeRef.get()).isEqualTo(PlaybackStateCompat.SHUFFLE_MODE_ALL);
+ assertThat(latchForRepeatMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(repeatModeRef.get()).isEqualTo(PlaybackStateCompat.REPEAT_MODE_ONE);
+ }
+
+ @Test
+ public void setPlayer_playbackTypeChangedToRemote() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 25);
+ int legacyPlaybackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ int deviceVolume = 10;
+
+ CountDownLatch playbackInfoNotified = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+ if (info.getPlaybackType() == legacyPlaybackType
+ && info.getMaxVolume() == deviceInfo.maxVolume
+ && info.getCurrentVolume() == deviceVolume) {
+ playbackInfoNotified.countDown();
+ }
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfo)
+ .setDeviceVolume(deviceVolume)
+ .build();
+ session.setPlayer(playerConfig);
+
+ assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackType);
+ assertThat(info.getMaxVolume()).isEqualTo(deviceInfo.maxVolume);
+ assertThat(info.getCurrentVolume()).isEqualTo(deviceVolume);
+ }
+
+ @Test
+ public void setPlayer_playbackTypeChangedToLocal() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setDeviceInfo(deviceInfo).build();
+ session.setPlayer(playerConfig);
+
+ DeviceInfo deviceInfoToUpdate =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 0, /* maxVolume= */ 10);
+ int legacyPlaybackTypeToUpdate = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ int legacyStream = AudioManager.STREAM_RING;
+ AudioAttributesCompat attrsCompat =
+ new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream).build();
+ AudioAttributes attrs = MediaUtils.convertToAudioAttributes(attrsCompat);
+
+ CountDownLatch playbackInfoNotified = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+ if (info.getPlaybackType() == legacyPlaybackTypeToUpdate
+ && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
+ playbackInfoNotified.countDown();
+ }
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Bundle playerConfigToUpdate =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfoToUpdate)
+ .setAudioAttributes(attrs)
+ .build();
+ session.setPlayer(playerConfigToUpdate);
+
+ // In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local.
+ if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
+ PollingCheck.waitFor(
+ TIMEOUT_MS,
+ () -> {
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ return info.getPlaybackType() == legacyPlaybackTypeToUpdate
+ && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
+ });
+ } else {
+ assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackTypeToUpdate);
+ assertThat(info.getAudioAttributes().getLegacyStreamType()).isEqualTo(legacyStream);
+ }
+ }
+
+ @Test
+ public void setPlayer_playbackTypeNotChanged_local() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 0, /* maxVolume= */ 10);
+ int legacyPlaybackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ int legacyStream = AudioManager.STREAM_RING;
+ AudioAttributesCompat attrsCompat =
+ new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream).build();
+ AudioAttributes attrs = MediaUtils.convertToAudioAttributes(attrsCompat);
+
+ CountDownLatch playbackInfoNotified = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+ if (info.getPlaybackType() == legacyPlaybackType
+ && info.getAudioAttributes().getLegacyStreamType() == legacyStream) {
+ playbackInfoNotified.countDown();
+ }
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfo)
+ .setAudioAttributes(attrs)
+ .build();
+ session.setPlayer(playerConfig);
+
+ // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
+ if (Build.VERSION.SDK_INT >= 21) {
+ PollingCheck.waitFor(
+ TIMEOUT_MS,
+ () -> {
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ return info.getPlaybackType() == legacyPlaybackType
+ && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
+ });
+ } else {
+ assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackType);
+ assertThat(info.getAudioAttributes().getLegacyStreamType()).isEqualTo(legacyStream);
+ }
+ }
+
+ @Test
+ public void setPlayer_playbackTypeNotChanged_remote() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfo)
+ .setDeviceVolume(1)
+ .build();
+ session.setPlayer(playerConfig);
+
+ DeviceInfo deviceInfoToUpdate =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 25);
+ int legacyPlaybackTypeToUpdate = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ int deviceVolumeToUpdate = 10;
+
+ CountDownLatch playbackInfoNotified = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+ if (info.getPlaybackType() == legacyPlaybackTypeToUpdate
+ && info.getMaxVolume() == deviceInfoToUpdate.maxVolume
+ && info.getCurrentVolume() == deviceVolumeToUpdate) {
+ playbackInfoNotified.countDown();
+ }
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Bundle playerConfigToUpdate =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfoToUpdate)
+ .setDeviceVolume(deviceVolumeToUpdate)
+ .build();
+ session.setPlayer(playerConfigToUpdate);
+
+ // In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
+ if (Build.VERSION.SDK_INT >= 21) {
+ PollingCheck.waitFor(
+ TIMEOUT_MS,
+ () -> {
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ return info.getPlaybackType() == legacyPlaybackTypeToUpdate
+ && info.getMaxVolume() == deviceInfoToUpdate.maxVolume
+ && info.getCurrentVolume() == deviceVolumeToUpdate;
+ });
+ } else {
+ assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
+ assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackTypeToUpdate);
+ assertThat(info.getMaxVolume()).isEqualTo(deviceInfoToUpdate.maxVolume);
+ assertThat(info.getCurrentVolume()).isEqualTo(deviceVolumeToUpdate);
+ }
+ }
+
+ @Test
+ public void onPlaybackParametersChanged_notifiesPlaybackStateCompatChanges() throws Exception {
+ PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
+
+ AtomicReference playbackStateRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ playbackStateRef.set(state);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().notifyPlaybackParametersChanged(playbackParameters);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get().getPlaybackSpeed())
+ .isWithin(EPSILON)
+ .of(playbackParameters.speed);
+ assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed())
+ .isWithin(EPSILON)
+ .of(playbackParameters.speed);
+ }
+
+ @Test
+ public void playbackStateChange_playWhenReadyBecomesFalseWhenReady_notifiesPaused()
+ throws Exception {
+ session
+ .getMockPlayer()
+ .setPlayWhenReady(
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY);
+ session.getMockPlayer().notifyIsPlayingChanged(false);
+
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
+ playbackStateCompatRef.set(playbackStateCompat);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session
+ .getMockPlayer()
+ .notifyPlayWhenReadyChanged(
+ /* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
+ }
+
+ @Test
+ public void playbackStateChange_playWhenReadyBecomesTrueWhenBuffering_notifiesBuffering()
+ throws Exception {
+ session
+ .getMockPlayer()
+ .setPlayWhenReady(/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ session.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_BUFFERING);
+ session.getMockPlayer().notifyIsPlayingChanged(false);
+
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
+ playbackStateCompatRef.set(playbackStateCompat);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+ session
+ .getMockPlayer()
+ .notifyPlayWhenReadyChanged(
+ /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(playbackStateCompatRef.get().getState())
+ .isEqualTo(PlaybackStateCompat.STATE_BUFFERING);
+ }
+
+ @Test
+ public void playbackStateChange_playbackStateBecomesEnded_notifiesPaused() throws Exception {
+ session
+ .getMockPlayer()
+ .setPlayWhenReady(
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY);
+ session.getMockPlayer().notifyIsPlayingChanged(false);
+
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
+ playbackStateCompatRef.set(playbackStateCompat);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().notifyPlaybackStateChanged(STATE_ENDED);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
+ }
+
+ @Test
+ public void playbackStateChange_isPlayingBecomesTrue_notifiesPlaying() throws Exception {
+ session.getMockPlayer().notifyIsPlayingChanged(false);
+
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
+ playbackStateCompatRef.set(playbackStateCompat);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().notifyIsPlayingChanged(true);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(playbackStateCompatRef.get().getState())
+ .isEqualTo(PlaybackStateCompat.STATE_PLAYING);
+ }
+
+ @Test
+ public void playbackStateChange_positionDiscontinuityNotifies_updatesPosition() throws Exception {
+ long testSeekPosition = 1300;
+
+ AtomicReference playbackStateRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ playbackStateRef.set(state);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().setCurrentPosition(testSeekPosition);
+ session
+ .getMockPlayer()
+ .notifyPositionDiscontinuity(
+ /* oldPosition= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
+ /* newPosition= */ SessionPositionInfo.DEFAULT_POSITION_INFO,
+ Player.DISCONTINUITY_REASON_SEEK);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get().getPosition()).isEqualTo(testSeekPosition);
+ assertThat(controllerCompat.getPlaybackState().getPosition()).isEqualTo(testSeekPosition);
+ }
+
+ @Test
+ public void currentMediaItemChange() throws Exception {
+ int testItemIndex = 3;
+ long testPosition = 1234;
+ String testDisplayTitle = "displayTitle";
+ List testMediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 5);
+ testMediaItems.set(
+ testItemIndex,
+ new MediaItem.Builder()
+ .setMediaId(testMediaItems.get(testItemIndex).mediaId)
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle(testDisplayTitle).build())
+ .build());
+ Timeline timeline = new PlaylistTimeline(testMediaItems);
+ session.getMockPlayer().setTimeline(timeline);
+
+ AtomicReference metadataRef = new AtomicReference<>();
+ AtomicReference playbackStateRef = new AtomicReference<>();
+ CountDownLatch latchForMetadata = new CountDownLatch(1);
+ CountDownLatch latchForPlaybackState = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ metadataRef.set(metadata);
+ latchForMetadata.countDown();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ playbackStateRef.set(state);
+ latchForPlaybackState.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().setCurrentWindowIndex(testItemIndex);
+ session.getMockPlayer().setCurrentPosition(testPosition);
+ session
+ .getMockPlayer()
+ .notifyMediaItemTransition(testItemIndex, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
+
+ assertThat(latchForMetadata.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(metadataRef.get().getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE))
+ .isEqualTo(testDisplayTitle);
+ assertThat(
+ controllerCompat
+ .getMetadata()
+ .getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE))
+ .isEqualTo(testDisplayTitle);
+ assertThat(latchForPlaybackState.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get().getPosition()).isEqualTo(testPosition);
+ assertThat(controllerCompat.getPlaybackState().getPosition()).isEqualTo(testPosition);
+ assertThat(playbackStateRef.get().getActiveQueueItemId())
+ .isEqualTo(MediaUtils.convertToQueueItemId(testItemIndex));
+ assertThat(controllerCompat.getPlaybackState().getActiveQueueItemId())
+ .isEqualTo(MediaUtils.convertToQueueItemId(testItemIndex));
+ }
+
+ @Test
+ public void playlistChange() throws Exception {
+ AtomicReference> queueRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onQueueChanged(List queue) {
+ queueRef.set(queue);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 5);
+ session.getMockPlayer().setTimeline(timeline);
+ session.getMockPlayer().notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ List queueFromParam = queueRef.get();
+ List queueFromGetter = controllerCompat.getQueue();
+ assertThat(queueFromParam).hasSize(timeline.getWindowCount());
+ assertThat(queueFromGetter).hasSize(timeline.getWindowCount());
+ Timeline.Window window = new Timeline.Window();
+ for (int i = 0; i < timeline.getWindowCount(); i++) {
+ assertThat(queueFromParam.get(i).getDescription().getMediaId())
+ .isEqualTo(timeline.getWindow(i, window).mediaItem.mediaId);
+ assertThat(queueFromGetter.get(i).getDescription().getMediaId())
+ .isEqualTo(timeline.getWindow(i, window).mediaItem.mediaId);
+ }
+ }
+
+ @Test
+ public void playlistChange_longList() throws Exception {
+ AtomicReference> queueRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onQueueChanged(List queue) {
+ queueRef.set(queue);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ int listSize = 5_000;
+ session.getMockPlayer().createAndSetFakeTimeline(listSize);
+ session.getMockPlayer().notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ List queueFromParam = queueRef.get();
+ List queueFromGetter = controllerCompat.getQueue();
+ if (Build.VERSION.SDK_INT >= 21) {
+ assertThat(queueFromParam).hasSize(listSize);
+ assertThat(queueFromGetter).hasSize(listSize);
+ } else {
+ // Below API 21, only the initial part of the playlist is sent to the
+ // MediaControllerCompat when the list is too long.
+ assertThat(queueFromParam.size() < listSize).isTrue();
+ assertThat(queueFromGetter).hasSize(queueFromParam.size());
+ }
+ for (int i = 0; i < listSize; i++) {
+ assertThat(queueFromParam.get(i).getDescription().getMediaId())
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ assertThat(queueFromGetter.get(i).getDescription().getMediaId())
+ .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ public void playlistMetadataChange() throws Exception {
+ AtomicReference queueTitleRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onQueueTitleChanged(CharSequence title) {
+ queueTitleRef.set(title);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ String playlistTitle = "playlistTitle";
+ MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle(playlistTitle).build();
+ session.getMockPlayer().setPlaylistMetadata(playlistMetadata);
+ session.getMockPlayer().notifyPlaylistMetadataChanged();
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(queueTitleRef.get().toString()).isEqualTo(playlistTitle);
+ }
+
+ @Test
+ public void onAudioInfoChanged_isCalledByVolumeChange() throws Exception {
+ DeviceInfo deviceInfo =
+ new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 10);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setDeviceInfo(deviceInfo)
+ .setDeviceVolume(1)
+ .build();
+ session.setPlayer(playerConfig);
+
+ int targetVolume = 3;
+ CountDownLatch targetVolumeNotified = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+ if (info.getCurrentVolume() == targetVolume) {
+ targetVolumeNotified.countDown();
+ }
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session.getMockPlayer().notifyDeviceVolumeChanged(targetVolume, /* muted= */ false);
+
+ assertThat(targetVolumeNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(controllerCompat.getPlaybackInfo().getCurrentVolume()).isEqualTo(targetVolume);
+ }
+
+ private static void assertPlaybackStateCompatErrorEquals(
+ PlaybackStateCompat state, ExoPlaybackException playerError) {
+ assertThat(state.getState()).isEqualTo(PlaybackStateCompat.STATE_ERROR);
+ assertThat(state.getErrorCode()).isEqualTo(PlaybackStateCompat.ERROR_CODE_UNKNOWN_ERROR);
+ assertThat(state.getErrorMessage()).isEqualTo(playerError.getMessage());
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerSurfaceTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerSurfaceTest.java
new file mode 100644
index 0000000000..e577354d25
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerSurfaceTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+import android.view.Surface;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.SurfaceActivity;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaController#setVideoSurface(Surface)}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerSurfaceTest {
+ private static final String TAG = "MC_SurfaceTest";
+
+ private final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+ private final MediaControllerTestRule controllerTestRule =
+ new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ private SurfaceActivity activity;
+ private RemoteMediaSession remoteSession;
+
+ @Rule
+ public ActivityTestRule 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTest.java
new file mode 100644
index 0000000000..ebd4d52bba
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTest.java
@@ -0,0 +1,805 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.HeartRating;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.RepeatMode;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.PollingCheck;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.android.exoplayer2.video.VideoSize;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaController}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerTest {
+
+ private static final String TAG = "MediaControllerTest";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+ final MediaControllerTestRule controllerTestRule = new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ private final List remoteSessionList = new ArrayList<>();
+
+ private Context context;
+ private RemoteMediaSession remoteSession;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ remoteSession = createRemoteMediaSession(DEFAULT_TEST_NAME, null);
+ }
+
+ @After
+ public void cleanUp() throws RemoteException {
+ for (int i = 0; i < remoteSessionList.size(); i++) {
+ RemoteMediaSession session = remoteSessionList.get(i);
+ if (session != null) {
+ session.cleanUp();
+ }
+ }
+ }
+
+ @Test
+ public void builder() throws Exception {
+ MediaController.Builder builder;
+
+ try {
+ builder = new MediaController.Builder(null);
+ assertWithMessage("null context shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+
+ try {
+ builder = new MediaController.Builder(context);
+ builder.setSessionToken(null);
+ assertWithMessage("null token shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+
+ try {
+ builder = new MediaController.Builder(context);
+ builder.setSessionCompatToken(null);
+ assertWithMessage("null compat token shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+
+ try {
+ builder = new MediaController.Builder(context);
+ builder.setControllerCallback(null);
+ assertWithMessage("null callback shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+
+ try {
+ builder = new MediaController.Builder(context);
+ builder.setApplicationLooper(null);
+ assertWithMessage("null looper shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionToken(remoteSession.getToken())
+ .setControllerCallback(new ControllerCallback() {})
+ .setApplicationLooper(threadTestRule.getHandler().getLooper())
+ .build();
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void getSessionActivity() throws Exception {
+ RemoteMediaSession session = createRemoteMediaSession(TEST_GET_SESSION_ACTIVITY, null);
+
+ MediaController controller = controllerTestRule.createController(session.getToken());
+ PendingIntent sessionActivity = controller.getSessionActivity();
+ assertThat(sessionActivity).isNotNull();
+ if (Build.VERSION.SDK_INT >= 17) {
+ // PendingIntent#getCreatorPackage() is added in API 17.
+ assertThat(sessionActivity.getCreatorPackage()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+
+ // TODO: Add getPid/getUid in MediaControllerProviderService and compare them.
+ // assertThat(sessionActivity.getCreatorUid()).isEqualTo(remoteSession.getUid());
+ }
+ session.cleanUp();
+ }
+
+ @Test
+ public void getPackageName() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ assertThat(controller.getConnectedToken().getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ }
+
+ @Test
+ public void getTokenExtras() throws Exception {
+ Bundle testTokenExtras = TestUtils.createTestBundle();
+ RemoteMediaSession session = createRemoteMediaSession("testGetExtras", testTokenExtras);
+
+ MediaController controller = controllerTestRule.createController(session.getToken());
+ SessionToken connectedToken = controller.getConnectedToken();
+ assertThat(connectedToken).isNotNull();
+ assertThat(TestUtils.equals(testTokenExtras, connectedToken.getExtras())).isTrue();
+ }
+
+ @Test
+ public void isConnected() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ assertThat(controller.isConnected()).isTrue();
+
+ remoteSession.release();
+ controllerTestRule.waitForDisconnect(controller, true);
+ assertThat(controller.isConnected()).isFalse();
+ }
+
+ @Test
+ public void close_beforeConnected() throws Exception {
+ MediaController controller =
+ controllerTestRule.createController(
+ remoteSession.getToken(), /* waitForConnect= */ false, null, /* callback= */ null);
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void close_twice() throws Exception {
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ threadTestRule.getHandler().postAndSync(controller::release);
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void gettersAfterConnected() throws Exception {
+ long currentPositionMs = 11;
+ long contentPositionMs = 33;
+ long durationMs = 200;
+ long bufferedPositionMs = 100;
+ int bufferedPercentage = 50;
+ long totalBufferedDurationMs = 120;
+ long currentLiveOffsetMs = 10;
+ long contentDurationMs = 300;
+ long contentBufferedPositionMs = 240;
+ boolean isPlayingAd = true;
+ int currentAdGroupIndex = 33;
+ int currentAdIndexInAdGroup = 22;
+ PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 0.5f);
+ boolean playWhenReady = true;
+ @Player.PlaybackSuppressionReason
+ int playbackSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
+ @Player.State int playbackState = Player.STATE_READY;
+ boolean isPlaying = true;
+ boolean isLoading = true;
+ MediaItem currentMediaItem = MediaTestUtils.createConvergedMediaItem(/* mediaId= */ "current");
+ boolean isShuffleModeEnabled = true;
+ @RepeatMode int repeatMode = Player.REPEAT_MODE_ONE;
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setCurrentPosition(currentPositionMs)
+ .setContentPosition(contentPositionMs)
+ .setDuration(durationMs)
+ .setBufferedPosition(bufferedPositionMs)
+ .setBufferedPercentage(bufferedPercentage)
+ .setTotalBufferedDuration(totalBufferedDurationMs)
+ .setCurrentLiveOffset(currentLiveOffsetMs)
+ .setContentDuration(contentDurationMs)
+ .setContentBufferedPosition(contentBufferedPositionMs)
+ .setIsPlayingAd(isPlayingAd)
+ .setCurrentAdGroupIndex(currentAdGroupIndex)
+ .setCurrentAdIndexInAdGroup(currentAdIndexInAdGroup)
+ .setPlaybackParameters(playbackParameters)
+ .setCurrentMediaItem(currentMediaItem)
+ .setPlayWhenReady(playWhenReady)
+ .setPlaybackSuppressionReason(playbackSuppressionReason)
+ .setPlaybackState(playbackState)
+ .setIsPlaying(isPlaying)
+ .setIsLoading(isLoading)
+ .setShuffleModeEnabled(isShuffleModeEnabled)
+ .setRepeatMode(repeatMode)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(0L));
+
+ AtomicLong currentPositionMsRef = new AtomicLong();
+ AtomicLong contentPositionMsRef = new AtomicLong();
+ AtomicLong durationMsRef = new AtomicLong();
+ AtomicLong bufferedPositionMsRef = new AtomicLong();
+ AtomicInteger bufferedPercentageRef = new AtomicInteger();
+ AtomicLong totalBufferedDurationMsRef = new AtomicLong();
+ AtomicLong currentLiveOffsetMsRef = new AtomicLong();
+ AtomicLong contentDurationMsRef = new AtomicLong();
+ AtomicLong contentBufferedPositionMsRef = new AtomicLong();
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ AtomicInteger currentAdGroupIndexRef = new AtomicInteger();
+ AtomicInteger currentAdIndexInAdGroupRef = new AtomicInteger();
+ AtomicReference playbackParametersRef = new AtomicReference<>();
+ AtomicReference mediaItemRef = new AtomicReference<>();
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ AtomicInteger playbackSuppressionReasonRef = new AtomicInteger();
+ AtomicInteger playbackStateRef = new AtomicInteger();
+ AtomicBoolean isPlayingRef = new AtomicBoolean();
+ AtomicBoolean isLoadingRef = new AtomicBoolean();
+ AtomicBoolean isShuffleModeEnabledRef = new AtomicBoolean();
+ AtomicInteger repeatModeRef = new AtomicInteger();
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ currentPositionMsRef.set(controller.getCurrentPosition());
+ contentPositionMsRef.set(controller.getContentPosition());
+ durationMsRef.set(controller.getDuration());
+ bufferedPositionMsRef.set(controller.getBufferedPosition());
+ bufferedPercentageRef.set(controller.getBufferedPercentage());
+ totalBufferedDurationMsRef.set(controller.getTotalBufferedDuration());
+ currentLiveOffsetMsRef.set(controller.getCurrentLiveOffset());
+ contentDurationMsRef.set(controller.getContentDuration());
+ contentBufferedPositionMsRef.set(controller.getContentBufferedPosition());
+ playbackParametersRef.set(controller.getPlaybackParameters());
+ isPlayingAdRef.set(controller.isPlayingAd());
+ currentAdGroupIndexRef.set(controller.getCurrentAdGroupIndex());
+ currentAdIndexInAdGroupRef.set(controller.getCurrentAdIndexInAdGroup());
+ mediaItemRef.set(controller.getCurrentMediaItem());
+ playWhenReadyRef.set(controller.getPlayWhenReady());
+ playbackSuppressionReasonRef.set(controller.getPlaybackSuppressionReason());
+ playbackStateRef.set(controller.getPlaybackState());
+ isPlayingRef.set(controller.isPlaying());
+ isLoadingRef.set(controller.isLoading());
+ isShuffleModeEnabledRef.set(controller.getShuffleModeEnabled());
+ repeatModeRef.set(controller.getRepeatMode());
+ });
+
+ assertThat(currentPositionMsRef.get()).isEqualTo(currentPositionMs);
+ assertThat(contentPositionMsRef.get()).isEqualTo(contentPositionMs);
+ assertThat(durationMsRef.get()).isEqualTo(durationMs);
+ assertThat(bufferedPositionMsRef.get()).isEqualTo(bufferedPositionMs);
+ assertThat(bufferedPercentageRef.get()).isEqualTo(bufferedPercentage);
+ assertThat(totalBufferedDurationMsRef.get()).isEqualTo(totalBufferedDurationMs);
+ assertThat(currentLiveOffsetMsRef.get()).isEqualTo(currentLiveOffsetMs);
+ assertThat(contentDurationMsRef.get()).isEqualTo(contentDurationMs);
+ assertThat(contentBufferedPositionMsRef.get()).isEqualTo(contentBufferedPositionMs);
+ assertThat(playbackParametersRef.get()).isEqualTo(playbackParameters);
+ assertThat(isPlayingAdRef.get()).isEqualTo(isPlayingAd);
+ assertThat(currentAdGroupIndexRef.get()).isEqualTo(currentAdGroupIndex);
+ assertThat(currentAdIndexInAdGroupRef.get()).isEqualTo(currentAdIndexInAdGroup);
+ MediaTestUtils.assertMediaIdEquals(currentMediaItem, mediaItemRef.get());
+ assertThat(playWhenReadyRef.get()).isEqualTo(playWhenReady);
+ assertThat(playbackSuppressionReasonRef.get()).isEqualTo(playbackSuppressionReason);
+ assertThat(playbackStateRef.get()).isEqualTo(playbackState);
+ assertThat(isPlayingRef.get()).isEqualTo(isPlaying);
+ assertThat(isLoadingRef.get()).isEqualTo(isLoading);
+ assertThat(isShuffleModeEnabledRef.get()).isEqualTo(isShuffleModeEnabled);
+ assertThat(repeatModeRef.get()).isEqualTo(repeatMode);
+ }
+
+ @Test
+ public void getPlayerError() throws Exception {
+ ExoPlaybackException testPlayerError = ExoPlaybackException.createForRemote("test");
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setPlayerError(testPlayerError).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ ExoPlaybackException playerError =
+ threadTestRule.getHandler().postAndSync(controller::getPlayerError);
+ assertThat(TestUtils.equals(playerError, testPlayerError)).isTrue();
+ }
+
+ @Test
+ public void getVideoSize_returnsVideoSizeOfPlayerInSession() throws Exception {
+ VideoSize testVideoSize =
+ new VideoSize(
+ /* width= */ 100,
+ /* height= */ 42,
+ /* unappliedRotationDegrees= */ 90,
+ /* pixelWidthHeightRatio= */ 1.2f);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setVideoSize(testVideoSize).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ VideoSize videoSize = threadTestRule.getHandler().postAndSync(controller::getVideoSize);
+ assertThat(videoSize).isEqualTo(testVideoSize);
+ }
+
+ @Test
+ public void futuresCompleted_AvailableCommandsChange() throws Exception {
+ RemoteMediaSession session = remoteSession;
+ MediaController controller = controllerTestRule.createController(session.getToken());
+
+ SessionCommands.Builder builder = new SessionCommands.Builder();
+ SessionCommand setRatingCommand =
+ new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING);
+ SessionCommand customCommand = new SessionCommand("custom", null);
+
+ int trials = 100;
+ CountDownLatch latch = new CountDownLatch(trials * 2);
+
+ for (int trial = 0; trial < trials; trial++) {
+ if (trial % 2 == 0) {
+ builder.add(setRatingCommand);
+ builder.add(customCommand);
+ } else {
+ builder.remove(setRatingCommand);
+ builder.remove(customCommand);
+ }
+ session.setAvailableCommands(builder.build(), Player.Commands.EMPTY);
+
+ String testMediaId = "testMediaId";
+ Rating testRating = new HeartRating(/* hasHeart= */ true);
+ controller.setRating(testMediaId, testRating).addListener(latch::countDown, Runnable::run);
+ controller
+ .sendCustomCommand(customCommand, null)
+ .addListener(latch::countDown, Runnable::run);
+ }
+
+ assertWithMessage("All futures should be completed")
+ .that(latch.await(LONG_TIMEOUT_MS, MILLISECONDS))
+ .isTrue();
+ }
+
+ @Test
+ public void getPlaylistMetadata_returnsPlaylistMetadataOfPlayerInSession() throws Exception {
+ MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setPlaylistMetadata(playlistMetadata)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ assertThat(threadTestRule.getHandler().postAndSync(controller::getPlaylistMetadata))
+ .isEqualTo(playlistMetadata);
+ }
+
+ @Test
+ public void getAudioAttributes_returnsAudioAttributesOfPlayerInSession() throws Exception {
+ AudioAttributes testAttributes =
+ new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MUSIC).build();
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setAudioAttributes(testAttributes).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ AudioAttributes attributes =
+ threadTestRule.getHandler().postAndSync(controller::getAudioAttributes);
+ assertThat(attributes).isEqualTo(testAttributes);
+ }
+
+ @Test
+ public void getVolume_returnsVolumeOfPlayerInSession() throws Exception {
+ float testVolume = .5f;
+
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setVolume(testVolume).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ float volume = threadTestRule.getHandler().postAndSync(controller::getVolume);
+ assertThat(volume).isEqualTo(testVolume);
+ }
+
+ @Test
+ public void getCurrentWindowIndex() throws Exception {
+ int testWindowIndex = 1;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setCurrentWindowIndex(testWindowIndex)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int currentWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getCurrentWindowIndex);
+
+ assertThat(currentWindowIndex).isEqualTo(testWindowIndex);
+ }
+
+ @Test
+ public void getCurrentPeriodIndex() throws Exception {
+ int testPeriodIndex = 1;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setCurrentPeriodIndex(testPeriodIndex)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int currentPeriodIndex =
+ threadTestRule.getHandler().postAndSync(controller::getCurrentPeriodIndex);
+
+ assertThat(currentPeriodIndex).isEqualTo(testPeriodIndex);
+ }
+
+ @Test
+ public void getPreviousWindowIndex() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(1)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int previousWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
+
+ assertThat(previousWindowIndex).isEqualTo(0);
+ }
+
+ @Test
+ public void getPreviousWindowIndex_withRepeatModeOne() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(1)
+ .setRepeatMode(Player.REPEAT_MODE_ONE)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int previousWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
+
+ assertThat(previousWindowIndex).isEqualTo(0);
+ }
+
+ @Test
+ public void getPreviousWindowIndex_atTheFirstWindow() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(0)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int previousWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
+
+ assertThat(previousWindowIndex).isEqualTo(C.INDEX_UNSET);
+ }
+
+ @Test
+ public void getPreviousWindowIndex_atTheFirstWindowWithRepeatModeAll() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(0)
+ .setRepeatMode(Player.REPEAT_MODE_ALL)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int previousWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
+
+ assertThat(previousWindowIndex).isEqualTo(2);
+ }
+
+ @Test
+ public void getPreviousWindowIndex_withShuffleModeEnabled() throws Exception {
+ Timeline timeline =
+ new PlaylistTimeline(
+ MediaTestUtils.createConvergedMediaItems(/* size= */ 3),
+ /* shuffledIndices= */ new int[] {0, 2, 1});
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(2)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(true)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int previousWindowIndex =
+ threadTestRule.getHandler().postAndSync(controller::getPreviousWindowIndex);
+
+ assertThat(previousWindowIndex).isEqualTo(0);
+ }
+
+ @Test
+ public void getNextWindowIndex() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(1)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
+
+ assertThat(nextWindowIndex).isEqualTo(2);
+ }
+
+ @Test
+ public void getNextWindowIndex_withRepeatModeOne() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(1)
+ .setRepeatMode(Player.REPEAT_MODE_ONE)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
+
+ assertThat(nextWindowIndex).isEqualTo(2);
+ }
+
+ @Test
+ public void getNextWindowIndex_atTheLastWindow() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(2)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
+
+ assertThat(nextWindowIndex).isEqualTo(C.INDEX_UNSET);
+ }
+
+ @Test
+ public void getNextWindowIndex_atTheLastWindowWithRepeatModeAll() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 3);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(2)
+ .setRepeatMode(Player.REPEAT_MODE_ALL)
+ .setShuffleModeEnabled(false)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
+
+ assertThat(nextWindowIndex).isEqualTo(0);
+ }
+
+ @Test
+ public void getNextWindowIndex_withShuffleModeEnabled() throws Exception {
+ Timeline timeline =
+ new PlaylistTimeline(
+ MediaTestUtils.createConvergedMediaItems(/* size= */ 3),
+ /* shuffledIndices= */ new int[] {0, 2, 1});
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setTimeline(timeline)
+ .setCurrentWindowIndex(2)
+ .setRepeatMode(Player.REPEAT_MODE_OFF)
+ .setShuffleModeEnabled(true)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int nextWindowIndex = threadTestRule.getHandler().postAndSync(controller::getNextWindowIndex);
+
+ assertThat(nextWindowIndex).isEqualTo(1);
+ }
+
+ @Test
+ public void getMediaItemCount() throws Exception {
+ int windowCount = 3;
+ Timeline timeline = MediaTestUtils.createTimeline(windowCount);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setTimeline(timeline).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ int mediaItemCount = threadTestRule.getHandler().postAndSync(controller::getMediaItemCount);
+
+ assertThat(mediaItemCount).isEqualTo(windowCount);
+ }
+
+ @Test
+ public void getMediaItemAt() throws Exception {
+ int windowCount = 3;
+ int windowIndex = 1;
+ Timeline timeline = MediaTestUtils.createTimeline(windowCount);
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder().setTimeline(timeline).build();
+ remoteSession.setPlayer(playerConfig);
+
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ MediaItem mediaItem =
+ threadTestRule.getHandler().postAndSync(() -> controller.getMediaItemAt(windowIndex));
+
+ assertThat(mediaItem)
+ .isEqualTo(timeline.getWindow(windowIndex, new Timeline.Window()).mediaItem);
+ }
+
+ private RemoteMediaSession createRemoteMediaSession(String id, Bundle tokenExtras)
+ throws Exception {
+ RemoteMediaSession session = new RemoteMediaSession(id, context, tokenExtras);
+ remoteSessionList.add(session);
+ return session;
+ }
+
+ @Test
+ public void getCurrentPosition_whenNotPlaying_doesNotAdvance() throws Exception {
+ long testCurrentPositionMs = 100L;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setIsPlaying(false)
+ .setCurrentPosition(testCurrentPositionMs)
+ .setDuration(10_000L)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ long currentPositionMs =
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setTimeDiffMs(50L);
+ return controller.getCurrentPosition();
+ });
+
+ assertThat(currentPositionMs).isEqualTo(testCurrentPositionMs);
+ }
+
+ @Test
+ public void getCurrentPosition_whenPlaying_advances() throws Exception {
+ long testCurrentPosition = 100L;
+ PlaybackParameters testPlaybackParameters = new PlaybackParameters(/* speed= */ 2.0f);
+ long testTimeDiff = 50L;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setIsPlaying(true)
+ .setCurrentPosition(testCurrentPosition)
+ .setDuration(10_000L)
+ .setPlaybackParameters(testPlaybackParameters)
+ .build();
+ remoteSession.setPlayer(playerConfig);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ long currentPositionMs =
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setTimeDiffMs(testTimeDiff);
+ return controller.getCurrentPosition();
+ });
+
+ long expectedCurrentPositionMs =
+ testCurrentPosition + (long) (testTimeDiff * testPlaybackParameters.speed);
+ assertThat(currentPositionMs).isEqualTo(expectedCurrentPositionMs);
+ }
+
+ @Test
+ public void getContentPosition_whenPlayingAd_doesNotAdvance() throws Exception {
+ long testContentPosition = 100L;
+ Bundle playerConfig =
+ new RemoteMediaSession.MockPlayerConfigBuilder()
+ .setContentPosition(testContentPosition)
+ .setDuration(10_000L)
+ .setIsPlaying(true)
+ .setIsPlayingAd(true)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f))
+ .build();
+ remoteSession.setPlayer(playerConfig);
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+
+ long contentPositionMs =
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ controller.setTimeDiffMs(50L);
+ return controller.getContentPosition();
+ });
+
+ assertThat(contentPositionMs).isEqualTo(testContentPosition);
+ }
+
+ @Test
+ public void getBufferedPosition_withPeriodicUpdate_updatedWithoutCallback() throws Exception {
+ long testBufferedPosition = 999L;
+ MediaController controller = controllerTestRule.createController(remoteSession.getToken());
+ remoteSession.getMockPlayer().setBufferedPosition(testBufferedPosition);
+
+ remoteSession.setSessionPositionUpdateDelayMs(0L);
+
+ PollingCheck.waitFor(
+ TIMEOUT_MS,
+ () -> {
+ long bufferedPosition =
+ threadTestRule.getHandler().postAndSync(controller::getBufferedPosition);
+ return bufferedPosition == testBufferedPosition;
+ });
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTestRule.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTestRule.java
new file mode 100644
index 0000000000..ec8a594d1f
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerTestRule.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.media.session.MediaSessionCompat;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.ArrayMap;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.util.Log;
+import java.util.Map;
+import org.junit.rules.ExternalResource;
+
+/**
+ * TestRule for managing {@link MediaController} instances. This class is not thread-safe, so call
+ * its methods on the junit test thread only.
+ */
+public final class MediaControllerTestRule extends ExternalResource {
+ private static final String TAG = "MediaControllerTestRule";
+
+ private final HandlerThreadTestRule handlerThreadTestRule;
+ private final Map 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);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithFrameworkMediaSessionTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithFrameworkMediaSessionTest.java
new file mode 100644
index 0000000000..55c8329b49
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithFrameworkMediaSessionTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.STATE_READY;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.support.v4.media.session.MediaSessionCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import com.google.android.exoplayer2.Player.State;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaController} with framework MediaSession, which exists since Android-L. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) // For framework MediaSession
+public class MediaControllerWithFrameworkMediaSessionTest {
+ private static final String TAG = "MediaControllerWithFrameworkMediaSessionTest";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private Context context;
+ private TestHandler handler;
+ private MediaSession fwkSession;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ TestHandler handler = new TestHandler(handlerThread.getLooper());
+ this.handler = handler;
+
+ fwkSession = new android.media.session.MediaSession(context, TAG);
+ fwkSession.setActive(true);
+ fwkSession.setFlags(
+ MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ fwkSession.setCallback(new android.media.session.MediaSession.Callback() {}, handler);
+ }
+
+ @After
+ public void cleanUp() {
+ if (fwkSession != null) {
+ fwkSession.release();
+ fwkSession = null;
+ }
+ if (handler != null) {
+ if (Build.VERSION.SDK_INT >= 18) {
+ handler.getLooper().quitSafely();
+ } else {
+ handler.getLooper().quit();
+ }
+ handler = null;
+ }
+ }
+
+ @Test
+ public void onConnected_calledAfterCreated() throws Exception {
+ CountDownLatch connectedLatch = new CountDownLatch(1);
+ MediaController.ControllerCallback callback =
+ new MediaController.ControllerCallback() {
+ @Override
+ public void onConnected(MediaController controller) {
+ connectedLatch.countDown();
+ }
+ };
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionCompatToken(MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
+ .setControllerCallback(callback)
+ .setApplicationLooper(handler.getLooper())
+ .build();
+ try {
+ assertThat(connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ } finally {
+ handler.postAndSync(controller::release);
+ }
+ }
+
+ @Test
+ public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Exception {
+ CountDownLatch connectedLatch = new CountDownLatch(1);
+ CountDownLatch playbackStateChangedLatch = new CountDownLatch(1);
+ AtomicInteger playbackStateRef = new AtomicInteger();
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ MediaController.ControllerCallback callback =
+ new MediaController.ControllerCallback() {
+ @Override
+ public void onConnected(MediaController controller) {
+ connectedLatch.countDown();
+ }
+ };
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionCompatToken(MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
+ .setControllerCallback(callback)
+ .setApplicationLooper(handler.getLooper())
+ .build();
+ SessionPlayer.PlayerCallback playerCallback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(@State int state) {
+ playbackStateRef.set(state);
+ playWhenReadyRef.set(controller.getPlayWhenReady());
+ playbackStateChangedLatch.countDown();
+ }
+ };
+ try {
+ controller.addListener(playerCallback);
+ assertThat(connectedLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ fwkSession.setPlaybackState(
+ new PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1.0f)
+ .build());
+ assertThat(playbackStateChangedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateRef.get()).isEqualTo(STATE_READY);
+ assertThat(playWhenReadyRef.get()).isTrue();
+ } finally {
+ handler.postAndSync(controller::release);
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithMediaSessionCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithMediaSessionCompatTest.java
new file mode 100644
index 0000000000..292cc74553
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaControllerWithMediaSessionCompatTest.java
@@ -0,0 +1,1336 @@
+/*
+ * 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_BUFFERING;
+import static com.google.android.exoplayer2.Player.STATE_READY;
+import static com.google.android.exoplayer2.session.LegacyMediaMetadata.METADATA_KEY_ADVERTISEMENT;
+import static com.google.android.exoplayer2.session.LegacyMediaMetadata.METADATA_KEY_DURATION;
+import static com.google.android.exoplayer2.session.LegacyMediaMetadata.METADATA_KEY_MEDIA_ID;
+import static com.google.android.exoplayer2.session.MediaConstants.ARGUMENT_CAPTIONING_ENABLED;
+import static com.google.android.exoplayer2.session.MediaConstants.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_INFO_SKIPPED;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+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 android.support.v4.media.session.PlaybackStateCompat.CustomAction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.VolumeProviderCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.ext.truth.os.BundleSubject;
+import androidx.test.filters.MediumTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.DiscontinuityReason;
+import com.google.android.exoplayer2.Player.PositionInfo;
+import com.google.android.exoplayer2.Player.RepeatMode;
+import com.google.android.exoplayer2.Player.State;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.session.MediaController.ControllerCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.MockActivity;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.common.collect.Range;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaController} interacting with {@link MediaSessionCompat}. */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class MediaControllerWithMediaSessionCompatTest {
+
+ private static final String TAG = "MCwMSCTest";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ 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 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 Exception {
+ session.cleanUp();
+ }
+
+ @Test
+ public void connected() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ assertThat(controller.isConnected()).isTrue();
+ }
+
+ @Test
+ public void disconnected_bySessionRelease() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ threadTestRule.getHandler().postAndSync(controller::release);
+ controllerTestRule.waitForDisconnect(controller, /* expected= */ true);
+ assertThat(controller.isConnected()).isFalse();
+ }
+
+ @Test
+ public void disconnected_byControllerClose() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ threadTestRule.getHandler().postAndSync(controller::release);
+ controllerTestRule.waitForDisconnect(controller, /* expected= */ true);
+ assertThat(controller.isConnected()).isFalse();
+ }
+
+ @Test
+ public void close_twice_doesNotCrash() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ threadTestRule.getHandler().postAndSync(controller::release);
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void close_beforeConnected_doesNotCrash() throws Exception {
+ MediaController controller =
+ controllerTestRule.createController(
+ session.getSessionToken(), /* waitForConnect= */ false, /* callback= */ null);
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void gettersAfterConnected() throws Exception {
+ long position = 150_000;
+ long bufferedPosition = 900_000;
+ long duration = 1_000_000;
+ float speed = 1.5f;
+ CharSequence queueTitle = "queueTitle";
+ @PlaybackStateCompat.ShuffleMode int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_GROUP;
+ @PlaybackStateCompat.RepeatMode int repeatMode = PlaybackStateCompat.REPEAT_MODE_GROUP;
+ boolean isPlayingAd = true;
+
+ MediaMetadataCompat metadata =
+ MediaUtils.convertToMediaMetadataCompat(
+ new LegacyMediaMetadata.Builder()
+ .putString(METADATA_KEY_MEDIA_ID, "gettersAfterConnected")
+ .putLong(METADATA_KEY_DURATION, duration)
+ .putLong(METADATA_KEY_ADVERTISEMENT, isPlayingAd ? 1 : 0)
+ .build());
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, position, speed)
+ .setBufferedPosition(bufferedPosition)
+ .build());
+ session.setMetadata(metadata);
+ session.setQueueTitle(queueTitle);
+ session.setShuffleMode(shuffleMode);
+ session.setRepeatMode(repeatMode);
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ AtomicInteger playerStateRef = new AtomicInteger();
+ AtomicInteger bufferingStateRef = new AtomicInteger();
+ AtomicLong positionRef = new AtomicLong();
+ AtomicLong bufferedPositionRef = new AtomicLong();
+ AtomicReference speedRef = new AtomicReference<>();
+ AtomicReference mediaItemRef = new AtomicReference<>();
+ AtomicBoolean playWhenReadyRef = new AtomicBoolean();
+ AtomicLong playbackStateRef = new AtomicLong();
+ AtomicBoolean shuffleModeEnabledRef = new AtomicBoolean();
+ AtomicLong repeatModeRef = new AtomicLong();
+ AtomicReference playlistMetadataRef = new AtomicReference<>();
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ positionRef.set(controller.getCurrentPosition());
+ bufferedPositionRef.set(controller.getBufferedPosition());
+ speedRef.set(controller.getPlaybackParameters().speed);
+ mediaItemRef.set(controller.getCurrentMediaItem());
+ playWhenReadyRef.set(controller.getPlayWhenReady());
+ playbackStateRef.set(controller.getPlaybackState());
+ repeatModeRef.set(controller.getRepeatMode());
+ shuffleModeEnabledRef.set(controller.getShuffleModeEnabled());
+ playlistMetadataRef.set(controller.getPlaylistMetadata());
+ isPlayingAdRef.set(controller.isPlayingAd());
+ });
+
+ assertThat(positionRef.get())
+ .isIn(Range.closedOpen(position, position + (long) (speed * TIMEOUT_MS)));
+ assertThat(bufferedPositionRef.get()).isEqualTo(bufferedPosition);
+ assertThat(speedRef.get()).isEqualTo(speed);
+ assertThat(mediaItemRef.get().mediaId).isEqualTo(metadata.getDescription().getMediaId());
+ assertThat(playWhenReadyRef.get()).isTrue();
+ assertThat(playbackStateRef.get()).isEqualTo(STATE_READY);
+ assertThat(shuffleModeEnabledRef.get()).isTrue();
+ assertThat(repeatModeRef.get()).isEqualTo(Player.REPEAT_MODE_ALL);
+ assertThat(playlistMetadataRef.get().title.toString()).isEqualTo(queueTitle.toString());
+ assertThat(isPlayingAdRef.get()).isEqualTo(isPlayingAd);
+ }
+
+ @Test
+ public void getPackageName() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ assertThat(controller.getConnectedToken().getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ }
+
+ @Test
+ public void getSessionActivity() throws Exception {
+ Intent sessionActivity = new Intent(context, MockActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(context, 0, sessionActivity, 0);
+ session.setSessionActivity(pi);
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ PendingIntent sessionActivityOut = controller.getSessionActivity();
+ assertThat(sessionActivityOut).isNotNull();
+ if (Build.VERSION.SDK_INT >= 17) {
+ // PendingIntent#getCreatorPackage() is added in API 17.
+ assertThat(sessionActivityOut.getCreatorPackage()).isEqualTo(context.getPackageName());
+ }
+ }
+
+ @Test
+ public void setRepeatMode_updatesAndNotifiesRepeatMode() throws Exception {
+ @Player.RepeatMode int testRepeatMode = Player.REPEAT_MODE_ALL;
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger repeatModeFromParamRef = new AtomicInteger();
+ AtomicInteger repeatModeFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onRepeatModeChanged(@RepeatMode int repeatMode) {
+ repeatModeFromParamRef.set(repeatMode);
+ repeatModeFromGetterRef.set(controller.getRepeatMode());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_GROUP);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(repeatModeFromParamRef.get()).isEqualTo(testRepeatMode);
+ assertThat(repeatModeFromGetterRef.get()).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void setShuffleModeEnabled_updatesAndNotifiesShuffleModeEnabled() throws Exception {
+ boolean testShuffleModeEnabled = true;
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean shuffleModeFromParamRef = new AtomicBoolean();
+ AtomicBoolean shuffleModeFromGetterRef = new AtomicBoolean();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ shuffleModeFromParamRef.set(shuffleModeEnabled);
+ shuffleModeFromGetterRef.set(controller.getShuffleModeEnabled());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(shuffleModeFromParamRef.get()).isEqualTo(testShuffleModeEnabled);
+ assertThat(shuffleModeFromGetterRef.get()).isEqualTo(testShuffleModeEnabled);
+ }
+
+ @Test
+ public void setQueue_updatesAndNotifiesTimeline() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference timelineFromParamRef = new AtomicReference<>();
+ AtomicReference timelineFromGetterRef = new AtomicReference<>();
+ AtomicInteger reasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineFromParamRef.set(timeline);
+ timelineFromGetterRef.set(controller.getCurrentTimeline());
+ reasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ Timeline testTimeline = MediaTestUtils.createTimeline(/* windowCount= */ 2);
+ List testQueue =
+ MediaUtils.convertToQueueItemList(MediaUtils.convertToMediaItemList(testTimeline));
+ session.setQueue(testQueue);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaTestUtils.assertMediaIdEquals(testTimeline, timelineFromParamRef.get());
+ MediaTestUtils.assertMediaIdEquals(testTimeline, timelineFromParamRef.get());
+ assertThat(reasonRef.get()).isEqualTo(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ }
+
+ @Test
+ public void setQueue_withNull_notifiesEmptyTimeline() throws Exception {
+ Timeline timeline = MediaTestUtils.createTimeline(/* windowCount= */ 2);
+ List queue =
+ MediaUtils.convertToQueueItemList(MediaUtils.convertToMediaItemList(timeline));
+ session.setQueue(queue);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference timelineRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Player.TimelineChangeReason int reason) {
+ timelineRef.set(timeline);
+ latch.countDown();
+ }
+ };
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ controller.addListener(callback);
+
+ session.setQueue(null);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(timelineRef.get().getWindowCount()).isEqualTo(0);
+ assertThat(timelineRef.get().getPeriodCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void setQueueTitle_updatesAndNotifiesPlaylistMetadata() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference metadataFromParamRef = new AtomicReference<>();
+ AtomicReference metadataFromGetterRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaylistMetadataChanged(MediaMetadata playlistMetadata) {
+ metadataFromParamRef.set(playlistMetadata);
+ metadataFromGetterRef.set(controller.getPlaylistMetadata());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ CharSequence queueTitle = "queueTitle";
+ session.setQueueTitle(queueTitle);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(metadataFromParamRef.get().title.toString()).isEqualTo(queueTitle.toString());
+ assertThat(metadataFromGetterRef.get().title.toString()).isEqualTo(queueTitle.toString());
+ }
+
+ @Test
+ public void getCurrentMediaItem_byDefault_returnsNull() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ MediaItem mediaItem = threadTestRule.getHandler().postAndSync(controller::getCurrentMediaItem);
+ assertThat(mediaItem).isNull();
+ }
+
+ @Test
+ public void getCurrentMediaItem_withSetMetadata_returnsMediaItemWithMediaId() throws Exception {
+ String testMediaId = "testMediaId";
+ MediaMetadataCompat metadata =
+ new MediaMetadataCompat.Builder().putText(METADATA_KEY_MEDIA_ID, testMediaId).build();
+ session.setMetadata(metadata);
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ MediaItem mediaItem = threadTestRule.getHandler().postAndSync(controller::getCurrentMediaItem);
+
+ assertThat(mediaItem.mediaId).isEqualTo(testMediaId);
+ }
+
+ @Test
+ public void setMetadata_notifiesCurrentMediaItem() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference itemRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onMediaItemTransition(
+ @Nullable MediaItem item, @Player.MediaItemTransitionReason int reason) {
+ itemRef.set(item);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ String testMediaId = "testMediaId";
+ MediaMetadataCompat metadata =
+ new MediaMetadataCompat.Builder().putText(METADATA_KEY_MEDIA_ID, testMediaId).build();
+ session.setMetadata(metadata);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(itemRef.get().mediaId).isEqualTo(testMediaId);
+ }
+
+ @Test
+ public void isPlayingAd_withMetadataWithAd_returnsTrue() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean isPlayingAdRef = new AtomicBoolean();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ isPlayingAdRef.set(controller.isPlayingAd());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ MediaMetadataCompat metadata =
+ new MediaMetadataCompat.Builder()
+ .putLong(MediaMetadataCompat.METADATA_KEY_ADVERTISEMENT, 1)
+ .build();
+ session.setMetadata(metadata);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(isPlayingAdRef.get()).isTrue();
+ }
+
+ @Test
+ public void setMediaUri_resultSetAfterPrepare() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ Uri testUri = Uri.parse("androidx://test");
+ ListenableFuture future =
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.setMediaUri(testUri, /* extras= */ null));
+
+ SessionResult result;
+ try {
+ result = future.get(NO_RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertWithMessage("TimeoutException is expected").fail();
+ } catch (TimeoutException e) {
+ // expected.
+ }
+
+ threadTestRule.getHandler().postAndSync(controller::prepare);
+
+ result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ }
+
+ @Test
+ public void setMediaUri_resultSetAfterPlay() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ Uri testUri = Uri.parse("androidx://test");
+ ListenableFuture future =
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.setMediaUri(testUri, /* extras= */ null));
+
+ SessionResult result;
+ try {
+ result = future.get(NO_RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertWithMessage("TimeoutException is expected").fail();
+ } catch (TimeoutException e) {
+ // expected.
+ }
+
+ threadTestRule.getHandler().postAndSync(controller::play);
+
+ result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ }
+
+ @Test
+ public void setMediaUris_multipleCalls_previousCallReturnsResultInfoSkipped() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ Uri testUri1 = Uri.parse("androidx://test1");
+ Uri testUri2 = Uri.parse("androidx://test2");
+ ListenableFuture future1 =
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.setMediaUri(testUri1, /* extras= */ null));
+ ListenableFuture future2 =
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.setMediaUri(testUri2, /* extras= */ null));
+
+ threadTestRule.getHandler().postAndSync(controller::prepare);
+
+ SessionResult result1 = future1.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ SessionResult result2 = future2.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertThat(result1.resultCode).isEqualTo(RESULT_INFO_SKIPPED);
+ assertThat(result2.resultCode).isEqualTo(RESULT_SUCCESS);
+ }
+
+ @Test
+ public void seekToDefaultPosition_withWindowIndex_updatesExpectedWindowIndex() throws Exception {
+ List testList = MediaTestUtils.createConvergedMediaItems(3);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setPlaybackState(/* state= */ null);
+ int testWindowIndex = 2;
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger currentWindowIndexRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ currentWindowIndexRef.set(controller.getCurrentWindowIndex());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.seekToDefaultPosition(testWindowIndex));
+
+ session.setMetadata(
+ new MediaMetadataCompat.Builder()
+ .putString(
+ MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testList.get(testWindowIndex).mediaId)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(currentWindowIndexRef.get()).isEqualTo(testWindowIndex);
+ }
+
+ @Test
+ public void seekTo_withWindowIndex_updatesExpectedWindowIndex() throws Exception {
+ List testList = MediaTestUtils.createConvergedMediaItems(3);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setPlaybackState(/* state= */ null);
+ long testPositionMs = 23L;
+ int testWindowIndex = 2;
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger windowIndexFromParamRef = new AtomicInteger();
+ AtomicInteger windowIndexFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ windowIndexFromParamRef.set(newPosition.windowIndex);
+ windowIndexFromGetterRef.set(controller.getCurrentWindowIndex());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+ threadTestRule
+ .getHandler()
+ .postAndSync(() -> controller.seekTo(testWindowIndex, testPositionMs));
+
+ session.setMetadata(
+ new MediaMetadataCompat.Builder()
+ .putString(
+ MediaMetadataCompat.METADATA_KEY_MEDIA_ID, testList.get(testWindowIndex).mediaId)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(windowIndexFromParamRef.get()).isEqualTo(testWindowIndex);
+ assertThat(windowIndexFromGetterRef.get()).isEqualTo(testWindowIndex);
+ }
+
+ @Test
+ public void setMetadata_withAd_notifiesPositionDiscontinuityByAdInsertion() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ String testMediaId = "testMediaId";
+ MediaMetadataCompat metadata =
+ new MediaMetadataCompat.Builder()
+ .putText(METADATA_KEY_MEDIA_ID, testMediaId)
+ .putLong(METADATA_KEY_ADVERTISEMENT, 1)
+ .build();
+ session.setMetadata(metadata);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get())
+ .isEqualTo(Player.DISCONTINUITY_REASON_AUTO_TRANSITION);
+ }
+
+ @Test
+ public void setPlaybackState_withActiveQueueItemId_notifiesCurrentMediaItem() throws Exception {
+ List testList = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+ // Set the current active queue item to index 'oldItemIndex'.
+ int oldItemIndex = 0;
+ builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
+ session.setPlaybackState(builder.build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference itemRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onMediaItemTransition(
+ @Nullable MediaItem item, @Player.MediaItemTransitionReason int reason) {
+ itemRef.set(item);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ // The new playbackState will tell the controller that the active queue item is changed to
+ // 'newItemIndex'.
+ int newItemIndex = 1;
+ builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
+ session.setPlaybackState(builder.build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ MediaTestUtils.assertMediaIdEquals(testList.get(newItemIndex), itemRef.get());
+ }
+
+ @Test
+ public void
+ setPlaybackState_withAdjacentQueueItemWhilePlaying_notifiesPositionDiscontinuityBySeek()
+ throws Exception {
+ long testDuration = 3000;
+ List testQueue = MediaTestUtils.createQueueItems(/* size= */ 2);
+ session.setQueue(testQueue);
+ session.setMetadata(
+ new MediaMetadataCompat.Builder()
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, testDuration)
+ .build());
+
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+ // Set the current active queue item to index 'oldItemIndex'.
+ int oldItemIndex = 0;
+ builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
+ builder.setState(
+ PlaybackStateCompat.STATE_PLAYING, /* position= */ 0L, /* playbackSpeed= */ 1.0f);
+ session.setPlaybackState(builder.build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference oldPositionRef = new AtomicReference<>();
+ AtomicReference newPositionRef = new AtomicReference<>();
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ oldPositionRef.set(oldPosition);
+ newPositionRef.set(newPosition);
+ positionDiscontinuityReasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int newItemIndex = 1;
+ builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
+ session.setPlaybackState(builder.build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
+ assertThat(oldPositionRef.get().windowIndex).isEqualTo(oldItemIndex);
+ assertThat(newPositionRef.get().windowIndex).isEqualTo(newItemIndex);
+ }
+
+ @Test
+ public void
+ setPlaybackState_withAdjacentQueueItemAfterPlaybackDone_notifiesPositionDiscontinuityByTransition()
+ throws Exception {
+ long testDuration = 3000;
+ List testQueue = MediaTestUtils.createQueueItems(/* size= */ 2);
+ session.setQueue(testQueue);
+ session.setMetadata(
+ new MediaMetadataCompat.Builder()
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, testDuration)
+ .build());
+
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+ // Set the current active queue item to index 'oldItemIndex'.
+ int oldItemIndex = 0;
+ builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
+ builder.setState(
+ PlaybackStateCompat.STATE_PLAYING, /* position= */ testDuration, /* playbackSpeed= */ 1.0f);
+ session.setPlaybackState(builder.build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference oldPositionRef = new AtomicReference<>();
+ AtomicReference newPositionRef = new AtomicReference<>();
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ oldPositionRef.set(oldPosition);
+ newPositionRef.set(newPosition);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int newItemIndex = 1;
+ builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
+ builder.setState(
+ PlaybackStateCompat.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1.0f);
+ session.setPlaybackState(builder.build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get())
+ .isEqualTo(Player.DISCONTINUITY_REASON_AUTO_TRANSITION);
+ assertThat(oldPositionRef.get().windowIndex).isEqualTo(oldItemIndex);
+ assertThat(newPositionRef.get().windowIndex).isEqualTo(newItemIndex);
+ }
+
+ @Test
+ public void setPlaybackState_withDistantQueueItem_notifiesPositionDiscontinuityBySeek()
+ throws Exception {
+ List testQueue = MediaTestUtils.createQueueItems(/* size= */ 3);
+ session.setQueue(testQueue);
+ session.setMetadata(new MediaMetadataCompat.Builder().build());
+
+ PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+ // Set the current active queue item to index 'oldItemIndex'.
+ int oldItemIndex = 0;
+ builder.setActiveQueueItemId(testQueue.get(oldItemIndex).getQueueId());
+ session.setPlaybackState(builder.build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference oldPositionRef = new AtomicReference<>();
+ AtomicReference newPositionRef = new AtomicReference<>();
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ oldPositionRef.set(oldPosition);
+ newPositionRef.set(newPosition);
+ positionDiscontinuityReasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ int newItemIndex = 2;
+ builder.setActiveQueueItemId(testQueue.get(newItemIndex).getQueueId());
+ session.setPlaybackState(builder.build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
+ assertThat(oldPositionRef.get().windowIndex).isEqualTo(oldItemIndex);
+ assertThat(newPositionRef.get().windowIndex).isEqualTo(newItemIndex);
+ }
+
+ @Test
+ public void setPlaybackState_withNewPosition_notifiesOnPositionDiscontinuity() throws Exception {
+ long testOldCurrentPositionMs = 300L;
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_PAUSED, testOldCurrentPositionMs, /* playbackSpeed= */ 1f)
+ .build());
+ session.setMetadata(new MediaMetadataCompat.Builder().build());
+
+ AtomicReference oldPositionRef = new AtomicReference<>();
+ AtomicReference newPositionRef = new AtomicReference<>();
+ AtomicInteger positionDiscontinuityReasonRef = new AtomicInteger();
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPositionDiscontinuity(
+ PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {
+ oldPositionRef.set(oldPosition);
+ newPositionRef.set(newPosition);
+ positionDiscontinuityReasonRef.set(reason);
+ latch.countDown();
+ }
+ };
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ controller.addListener(callback);
+
+ long testNewCurrentPositionMs = 900L;
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_PAUSED, testNewCurrentPositionMs, /* playbackSpeed= */ 1f)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(positionDiscontinuityReasonRef.get()).isEqualTo(Player.DISCONTINUITY_REASON_SEEK);
+ assertThat(oldPositionRef.get().positionMs).isEqualTo(testOldCurrentPositionMs);
+ assertThat(newPositionRef.get().positionMs).isEqualTo(testNewCurrentPositionMs);
+ }
+
+ @Test
+ public void setPlaybackState_fromStateBufferingToPlaying_notifiesReadyState() throws Exception {
+ List testPlaylist = MediaTestUtils.createConvergedMediaItems(/* size= */ 1);
+ MediaMetadataCompat metadata =
+ MediaUtils.convertToMediaMetadataCompat(testPlaylist.get(0), /* durationMs= */ 1_000);
+ long testBufferedPosition = 500;
+ session.setMetadata(metadata);
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_BUFFERING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .setBufferedPosition(0)
+ .build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger stateFromParamRef = new AtomicInteger();
+ AtomicInteger stateFromGetterRef = new AtomicInteger();
+ AtomicLong bufferedPositionFromGetterRef = new AtomicLong();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(@State int state) {
+ stateFromParamRef.set(state);
+ stateFromGetterRef.set(controller.getPlaybackState());
+ bufferedPositionFromGetterRef.set(controller.getBufferedPosition());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .setBufferedPosition(testBufferedPosition)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(stateFromParamRef.get()).isEqualTo(STATE_READY);
+ assertThat(stateFromGetterRef.get()).isEqualTo(STATE_READY);
+ assertThat(bufferedPositionFromGetterRef.get()).isEqualTo(testBufferedPosition);
+ }
+
+ @Test
+ public void setPlaybackState_fromStatePlayingToBuffering_notifiesBufferingState()
+ throws Exception {
+ List testPlaylist = MediaTestUtils.createConvergedMediaItems(1);
+ MediaMetadataCompat metadata =
+ MediaUtils.convertToMediaMetadataCompat(testPlaylist.get(0), /* durationMs= */ 1_000);
+ long testBufferingPosition = 0;
+ session.setMetadata(metadata);
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_PLAYING, /* position= */ 100, /* playbackSpeed= */ 1f)
+ .setBufferedPosition(500)
+ .build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger stateFromParamRef = new AtomicInteger();
+ AtomicInteger stateFromGetterRef = new AtomicInteger();
+ AtomicLong bufferedPositionFromGetterRef = new AtomicLong();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int state) {
+ stateFromParamRef.set(state);
+ stateFromGetterRef.set(controller.getPlaybackState());
+ bufferedPositionFromGetterRef.set(controller.getBufferedPosition());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_BUFFERING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .setBufferedPosition(testBufferingPosition)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(stateFromParamRef.get()).isEqualTo(STATE_BUFFERING);
+ assertThat(stateFromGetterRef.get()).isEqualTo(STATE_BUFFERING);
+ assertThat(bufferedPositionFromGetterRef.get()).isEqualTo(testBufferingPosition);
+ }
+
+ @Test
+ public void setPlaybackState_fromStateNoneToPlaying_notifiesReadyState() throws Exception {
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_NONE, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger stateFromParamRef = new AtomicInteger();
+ AtomicInteger stateFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int state) {
+ stateFromParamRef.set(state);
+ stateFromGetterRef.set(controller.getPlaybackState());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(stateFromParamRef.get()).isEqualTo(STATE_READY);
+ assertThat(stateFromGetterRef.get()).isEqualTo(STATE_READY);
+ }
+
+ @Test
+ public void setPlaybackState_fromStatePausedToPlaying_notifiesPlayWhenReady() throws Exception {
+ boolean testPlayWhenReady = true;
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PAUSED, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean playWhenReadyFromParamRef = new AtomicBoolean();
+ AtomicBoolean playWhenReadyFromGetterRef = new AtomicBoolean();
+ AtomicInteger playbackSuppressionReasonFromParamRef = new AtomicInteger();
+ AtomicInteger playbackSuppressionReasonFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlaybackSuppressionReason int reason) {
+ playWhenReadyFromParamRef.set(playWhenReady);
+ playWhenReadyFromGetterRef.set(controller.getPlayWhenReady());
+ playbackSuppressionReasonFromParamRef.set(reason);
+ playbackSuppressionReasonFromGetterRef.set(controller.getPlaybackSuppressionReason());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playWhenReadyFromParamRef.get()).isEqualTo(testPlayWhenReady);
+ assertThat(playWhenReadyFromGetterRef.get()).isEqualTo(testPlayWhenReady);
+ assertThat(playbackSuppressionReasonFromParamRef.get())
+ .isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ assertThat(playbackSuppressionReasonFromGetterRef.get())
+ .isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ }
+
+ @Test
+ public void setPlaybackState_toBuffering_notifiesPlaybackStateBuffering() throws Exception {
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PAUSED, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+ session.setMetadata(
+ new MediaMetadataCompat.Builder().putLong(METADATA_KEY_DURATION, 1_000).build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackStateFromParamRef = new AtomicInteger();
+ AtomicInteger playbackStateFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(@Player.State int state) {
+ playbackStateFromParamRef.set(state);
+ playbackStateFromGetterRef.set(controller.getPlaybackState());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_BUFFERING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateFromParamRef.get()).isEqualTo(Player.STATE_BUFFERING);
+ assertThat(playbackStateFromGetterRef.get()).isEqualTo(Player.STATE_BUFFERING);
+ }
+
+ @Test
+ public void setPlaybackState_toPausedWithEndPosition_notifiesPlaybackStateEnded()
+ throws Exception {
+ long testDuration = 1_000;
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, /* position= */ 0, /* playbackSpeed= */ 1f)
+ .build());
+ session.setMetadata(
+ new MediaMetadataCompat.Builder().putLong(METADATA_KEY_DURATION, testDuration).build());
+
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger playbackStateFromParamRef = new AtomicInteger();
+ AtomicInteger playbackStateFromGetterRef = new AtomicInteger();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(@Player.State int state) {
+ playbackStateFromParamRef.set(state);
+ playbackStateFromGetterRef.set(controller.getPlaybackState());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_PAUSED,
+ /* position= */ testDuration,
+ /* playbackSpeed= */ 1f)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateFromParamRef.get()).isEqualTo(Player.STATE_ENDED);
+ assertThat(playbackStateFromGetterRef.get()).isEqualTo(Player.STATE_ENDED);
+ }
+
+ @Test
+ public void setPlaybackState_withSpeed_notifiesOnPlaybackParametersChanged() throws Exception {
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference playbackParametersFromParamRef = new AtomicReference<>();
+ AtomicReference playbackParametersFromGetterRef = new AtomicReference<>();
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ playbackParametersFromParamRef.set(playbackParameters);
+ playbackParametersFromGetterRef.set(controller.getPlaybackParameters());
+ latch.countDown();
+ }
+ };
+ controller.addListener(callback);
+
+ float testSpeed = 3.0f;
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_PLAYING,
+ /* position= */ 0,
+ /* playbackSpeed= */ testSpeed)
+ .build());
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackParametersFromParamRef.get().speed).isEqualTo(testSpeed);
+ assertThat(playbackParametersFromGetterRef.get().speed).isEqualTo(testSpeed);
+ }
+
+ @Test
+ public void setPlaybackToRemote_notifiesDeviceInfoAndVolume() throws Exception {
+ int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ int maxVolume = 100;
+ int currentVolume = 45;
+
+ AtomicReference deviceInfoRef = new AtomicReference<>();
+ CountDownLatch latchForDeviceInfo = new CountDownLatch(1);
+ CountDownLatch latchForDeviceVolume = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceInfoChanged(@NonNull DeviceInfo deviceInfo) {
+ if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
+ deviceInfoRef.set(deviceInfo);
+ latchForDeviceInfo.countDown();
+ }
+ }
+
+ @Override
+ public void onDeviceVolumeChanged(int volume, boolean muted) {
+ if (volume == currentVolume) {
+ latchForDeviceVolume.countDown();
+ }
+ }
+ };
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ controller.addListener(callback);
+
+ session.setPlaybackToRemote(volumeControlType, maxVolume, currentVolume);
+
+ assertThat(latchForDeviceInfo.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(latchForDeviceVolume.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceInfoRef.get().maxVolume).isEqualTo(maxVolume);
+ }
+
+ @Test
+ public void setPlaybackToLocal_notifiesDeviceInfoAndVolume() throws Exception {
+ if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
+ // In API 21 and 22, onAudioInfoChanged is not called.
+ return;
+ }
+ session.setPlaybackToRemote(
+ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
+ /* maxVolume= */ 100,
+ /* currentVolume= */ 45);
+
+ int testLocalStreamType = AudioManager.STREAM_ALARM;
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ int maxVolume = audioManager.getStreamMaxVolume(testLocalStreamType);
+ int currentVolume = audioManager.getStreamVolume(testLocalStreamType);
+
+ AtomicReference deviceInfoRef = new AtomicReference<>();
+ CountDownLatch latchForDeviceInfo = new CountDownLatch(1);
+ CountDownLatch latchForDeviceVolume = new CountDownLatch(1);
+ SessionPlayer.PlayerCallback callback =
+ new SessionPlayer.PlayerCallback() {
+ @Override
+ public void onDeviceInfoChanged(@NonNull DeviceInfo deviceInfo) {
+ if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
+ deviceInfoRef.set(deviceInfo);
+ latchForDeviceInfo.countDown();
+ }
+ }
+
+ @Override
+ public void onDeviceVolumeChanged(int volume, boolean muted) {
+ if (volume == currentVolume) {
+ latchForDeviceVolume.countDown();
+ }
+ }
+ };
+ MediaController controller = controllerTestRule.createController(session.getSessionToken());
+ controller.addListener(callback);
+
+ session.setPlaybackToLocal(testLocalStreamType);
+
+ assertThat(latchForDeviceInfo.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(latchForDeviceVolume.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(deviceInfoRef.get().maxVolume).isEqualTo(maxVolume);
+ }
+
+ @Test
+ public void sendSessionEvent_notifiesCustomCommand() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference commandRef = new AtomicReference<>();
+ AtomicReference argsRef = new AtomicReference<>();
+ ControllerCallback callback =
+ new ControllerCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onCustomCommand(
+ @NonNull MediaController controller, @NonNull SessionCommand command, Bundle args) {
+ commandRef.set(command);
+ argsRef.set(args);
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ controllerTestRule.createController(
+ session.getSessionToken(), /* waitForConnect= */ true, callback);
+
+ String event = "customCommand";
+ Bundle extras = TestUtils.createTestBundle();
+ session.sendSessionEvent(event, extras);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(commandRef.get().customAction).isEqualTo(event);
+ assertThat(TestUtils.equals(extras, argsRef.get())).isTrue();
+ }
+
+ @Test
+ public void setPlaybackState_withCustomAction_notifiesCustomLayout() throws Exception {
+ CustomAction testCustomAction1 =
+ new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
+ CustomAction testCustomAction2 =
+ new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
+ CountDownLatch latch = new CountDownLatch(2);
+ ControllerCallback callback =
+ new ControllerCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onSetCustomLayout(
+ @NonNull MediaController controller, @NonNull List layout) {
+ assertThat(layout).hasSize(1);
+ CommandButton button = layout.get(0);
+
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertThat(button.sessionCommand.customAction)
+ .isEqualTo(testCustomAction1.getAction());
+ assertThat(button.displayName.toString())
+ .isEqualTo(testCustomAction1.getName().toString());
+ assertThat(button.iconResId).isEqualTo(testCustomAction1.getIcon());
+ break;
+ case 1:
+ assertThat(button.sessionCommand.customAction)
+ .isEqualTo(testCustomAction2.getAction());
+ assertThat(button.displayName.toString())
+ .isEqualTo(testCustomAction2.getName().toString());
+ assertThat(button.iconResId).isEqualTo(testCustomAction2.getIcon());
+ break;
+ }
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder().addCustomAction(testCustomAction1).build());
+ // onSetCustomLayout will be called when its connected
+ controllerTestRule.createController(
+ session.getSessionToken(), /* waitForConnect= */ true, callback);
+ // onSetCustomLayout will be called again when the custom action in the playback state is
+ // changed.
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder().addCustomAction(testCustomAction2).build());
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void setPlaybackState_withCustomAction_notifiesAvailableCommands() throws Exception {
+ CustomAction testCustomAction1 =
+ new CustomAction.Builder("testCustomAction1", "testName1", 1).build();
+ CustomAction testCustomAction2 =
+ new CustomAction.Builder("testCustomAction2", "testName2", 2).build();
+ CountDownLatch latch = new CountDownLatch(1);
+ ControllerCallback callback =
+ new ControllerCallback() {
+ @Override
+ public void onAvailableSessionCommandsChanged(
+ MediaController controller, SessionCommands commands) {
+ assertThat(
+ commands.contains(
+ new SessionCommand(
+ testCustomAction1.getAction(), testCustomAction1.getExtras())))
+ .isFalse();
+ assertThat(
+ commands.contains(
+ new SessionCommand(
+ testCustomAction2.getAction(), testCustomAction2.getExtras())))
+ .isTrue();
+ latch.countDown();
+ }
+ };
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder().addCustomAction(testCustomAction1).build());
+ controllerTestRule.createController(
+ session.getSessionToken(), /* waitForConnect= */ true, callback);
+ session.setPlaybackState(
+ new PlaybackStateCompat.Builder().addCustomAction(testCustomAction2).build());
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void setCaptioningEnabled_notifiesCustomCommand() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference commandRef = new AtomicReference<>();
+ AtomicReference argsRef = new AtomicReference<>();
+ ControllerCallback callback =
+ new ControllerCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onCustomCommand(
+ @NonNull MediaController controller,
+ @NonNull SessionCommand command,
+ @Nullable Bundle args) {
+ commandRef.set(command);
+ argsRef.set(args);
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ controllerTestRule.createController(session.getSessionToken(), true, callback);
+
+ session.setCaptioningEnabled(true);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(commandRef.get().customAction)
+ .isEqualTo(SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED);
+ BundleSubject.assertThat(argsRef.get()).bool(ARGUMENT_CAPTIONING_ENABLED).isTrue();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaLibrarySessionCallbackTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaLibrarySessionCallbackTest.java
new file mode 100644
index 0000000000..ad64a9def8
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaLibrarySessionCallbackTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.LibraryResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import com.google.android.exoplayer2.session.MediaLibraryService.LibraryParams;
+import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.CountDownLatch;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link MediaLibrarySession.MediaLibrarySessionCallback}.
+ *
+ * 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 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 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionAndControllerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionAndControllerTest.java
new file mode 100644
index 0000000000..086006493b
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionAndControllerTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.STATE_IDLE;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Looper;
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaSession} and {@link MediaController} in the same process. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionAndControllerTest {
+
+ private Context context;
+ private TestHandler handler;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+
+ HandlerThread handlerThread = new HandlerThread("MediaSessionAndControllerTest");
+ handlerThread.start();
+ handler = new TestHandler(handlerThread.getLooper());
+ }
+
+ @After
+ public void cleanUp() {
+ if (Build.VERSION.SDK_INT >= 18) {
+ handler.getLooper().quitSafely();
+ } else {
+ handler.getLooper().quit();
+ }
+ }
+
+ /** Test potential deadlock for calls between controller and session. */
+ @Test
+ public void deadlock() throws Exception {
+ HandlerThread testThread = new HandlerThread("deadlock");
+ testThread.start();
+ TestHandler testHandler = new TestHandler(testThread.getLooper());
+ AtomicReference sessionRef = new AtomicReference<>();
+ AtomicReference 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 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackTest.java
new file mode 100644
index 0000000000..8b847aaa13
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackTest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.StarRating;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaSession.SessionCallback}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionCallbackTest {
+ private static final String TAG = "MediaSessionCallbackTest";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ @Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
+
+ private Context context;
+ private MockPlayer player;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ player =
+ new MockPlayer.Builder()
+ .setLatchCount(1)
+ .setApplicationLooper(threadTestRule.getHandler().getLooper())
+ .build();
+ }
+
+ @Test
+ public void onPostConnect_afterConnected() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession.SessionCallback callback =
+ new MediaSession.SessionCallback() {
+ @Override
+ public void onPostConnect(
+ @NonNull MediaSession session, @NonNull ControllerInfo controller) {
+ latch.countDown();
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnPostConnect_afterConnected")
+ .build());
+ controllerTestRule.createRemoteController(session.getToken());
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onPostConnect_afterConnectionRejected() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession.SessionCallback callback =
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ return null;
+ }
+
+ @Override
+ public void onPostConnect(
+ @NonNull MediaSession session, @NonNull ControllerInfo controller) {
+ latch.countDown();
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnPostConnect_afterConnectionRejected")
+ .build());
+ controllerTestRule.createRemoteController(session.getToken());
+ assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ public void onCommandRequest() throws Exception {
+ MockOnCommandCallback callback = new MockOnCommandCallback();
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnCommandRequest")
+ .build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.prepare();
+ assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(player.prepareCalled).isFalse();
+ assertThat(callback.commands).hasSize(1);
+ assertThat(callback.commands.get(0)).isEqualTo(Player.COMMAND_PREPARE_STOP);
+
+ controller.play();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ assertThat(player.prepareCalled).isFalse();
+ assertThat(callback.commands).hasSize(2);
+ assertThat(callback.commands.get(1)).isEqualTo(Player.COMMAND_PLAY_PAUSE);
+ }
+
+ @Test
+ public void onCustomCommand() throws Exception {
+ // TODO(jaewan): Need to revisit with the permission.
+ SessionCommand testCommand = new SessionCommand("testCustomCommand", null);
+ Bundle testArgs = new Bundle();
+ testArgs.putString("args", "testOnCustomCommand");
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession.SessionCallback callback =
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ SessionCommands commands =
+ new SessionCommands.Builder()
+ .addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1)
+ .add(testCommand)
+ .build();
+ return new MediaSession.ConnectResult(commands, Player.Commands.EMPTY);
+ }
+
+ @Override
+ @NonNull
+ public ListenableFuture onCustomCommand(
+ @NonNull MediaSession session,
+ @NonNull MediaSession.ControllerInfo controller,
+ @NonNull SessionCommand sessionCommand,
+ Bundle args) {
+ assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(sessionCommand).isEqualTo(testCommand);
+ assertThat(TestUtils.equals(testArgs, args)).isTrue();
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnCustomCommand")
+ .build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+ SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onSetMediaUri() throws Exception {
+ Uri testUri = Uri.parse("foo://boo");
+ Bundle testExtras = TestUtils.createTestBundle();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession.SessionCallback callback =
+ new MediaSession.SessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ @Nullable Bundle extras) {
+ assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(uri).isEqualTo(testUri);
+ assertThat(TestUtils.equals(testExtras, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnSetMediaUri")
+ .build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.setMediaUri(testUri, testExtras);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onSetRating() throws Exception {
+ float ratingValue = 3.5f;
+ Rating testRating = new StarRating(5, ratingValue);
+ String testMediaId = "media_id";
+
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession.SessionCallback callback =
+ new MediaSession.SessionCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onSetRating(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull String mediaId,
+ @NonNull Rating rating) {
+ assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(mediaId).isEqualTo(testMediaId);
+ assertThat(rating).isEqualTo(testRating);
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(callback)
+ .setId("testOnSetRating")
+ .build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+ SessionResult result = controller.setRating(testMediaId, testRating);
+ assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onConnect() throws Exception {
+ AtomicReference connectionHints = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setId("testOnConnect")
+ .setSessionCallback(
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ // TODO: Get uid of client app's and compare.
+ if (!SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
+ return null;
+ }
+ connectionHints.set(controller.getConnectionHints());
+ latch.countDown();
+ return super.onConnect(session, controller);
+ }
+ })
+ .build());
+ Bundle testConnectionHints = new Bundle();
+ testConnectionHints.putString("test_key", "test_value");
+
+ controllerTestRule.createRemoteController(
+ session.getToken(), /* waitForConnection= */ false, testConnectionHints);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
+ }
+
+ @Test
+ public void onDisconnected() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setId("testOnDisconnected")
+ .setSessionCallback(
+ new MediaSession.SessionCallback() {
+ @Override
+ public void onDisconnected(
+ @NonNull MediaSession session, @NonNull ControllerInfo controller) {
+ assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ // TODO: Get uid of client app's and compare.
+ latch.countDown();
+ }
+ })
+ .build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+ controller.release();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ static class MockOnCommandCallback extends MediaSession.SessionCallback {
+ public final ArrayList commands = new ArrayList<>();
+
+ @Override
+ public int onPlayerCommandRequest(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controllerInfo,
+ @Player.Command int command) {
+ // TODO: Get uid of client app's and compare.
+ assertThat(controllerInfo.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(controllerInfo.isTrusted()).isFalse();
+ commands.add(command);
+ if (command == Player.COMMAND_PREPARE_STOP) {
+ return RESULT_ERROR_INVALID_STATE;
+ }
+ return RESULT_SUCCESS;
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackWithMediaControllerCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackWithMediaControllerCompatTest.java
new file mode 100644
index 0000000000..e37f9bbdc1
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCallbackWithMediaControllerCompatTest.java
@@ -0,0 +1,1116 @@
+/*
+ * 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.Player.COMMAND_PLAY_PAUSE;
+import static com.google.android.exoplayer2.Player.COMMAND_PREPARE_STOP;
+import static com.google.android.exoplayer2.Player.STATE_IDLE;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_ERROR_INVALID_STATE;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.VOLUME_CHANGE_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.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.AudioAttributesCompat;
+import androidx.media.AudioManagerCompat;
+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.Player;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.MediaSession.SessionCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.PollingCheck;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.Util;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SessionCallback} working with {@link MediaControllerCompat}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionCallbackWithMediaControllerCompatTest {
+
+ private static final String TAG = "MSCallbackWithMCCTest";
+
+ private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
+ (Build.VERSION.SDK_INT < 21 || Build.VERSION.SDK_INT >= 24)
+ ? SUPPORT_APP_PACKAGE_NAME
+ : LEGACY_CONTROLLER;
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+
+ private Context context;
+ private TestHandler handler;
+ private MediaSession session;
+ private RemoteMediaControllerCompat controller;
+ private MockPlayer player;
+ private AudioManager audioManager;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ handler = threadTestRule.getHandler();
+ player =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ @After
+ public void cleanUp() {
+ if (session != null) {
+ session.release();
+ session = null;
+ }
+ if (controller != null) {
+ controller.cleanUp();
+ controller = null;
+ }
+ }
+
+ @Test
+ public void onDisconnected_afterTimeout_isCalled() throws Exception {
+ CountDownLatch disconnectedLatch = new CountDownLatch(1);
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("onDisconnected_afterTimeout_isCalled")
+ .setSessionCallback(
+ new SessionCallback() {
+ private ControllerInfo connectedController;
+
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
+ connectedController = controller;
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+
+ @Override
+ public void onDisconnected(
+ @NonNull MediaSession session, @NonNull ControllerInfo controller) {
+ if (Util.areEqual(connectedController, controller)) {
+ disconnectedLatch.countDown();
+ }
+ }
+ })
+ .build();
+ // Make onDisconnected() to be called immediately after the connection.
+ session.setLegacyControllerConnectionTimeoutMs(0);
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ // Invoke any command for session to recognize the controller compat.
+ controller.getTransportControls().seekTo(111);
+ assertThat(disconnectedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onConnected_afterDisconnectedByTimeout_isCalled() throws Exception {
+ CountDownLatch connectedLatch = new CountDownLatch(2);
+ CountDownLatch disconnectedLatch = new CountDownLatch(1);
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("onConnected_afterDisconnectedByTimeout_isCalled")
+ .setSessionCallback(
+ new SessionCallback() {
+ private ControllerInfo connectedController;
+
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
+ connectedController = controller;
+ connectedLatch.countDown();
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+
+ @Override
+ public void onDisconnected(
+ @NonNull MediaSession session, @NonNull ControllerInfo controller) {
+ if (Util.areEqual(connectedController, controller)) {
+ disconnectedLatch.countDown();
+ }
+ }
+ })
+ .build();
+ // Make onDisconnected() to be called immediately after the connection.
+ session.setLegacyControllerConnectionTimeoutMs(0);
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ // Invoke any command for session to recognize the controller compat.
+ controller.getTransportControls().seekTo(111);
+ assertThat(disconnectedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ // Test whenter onConnect() is called again after the onDisconnected().
+ controller.getTransportControls().seekTo(111);
+
+ assertThat(connectedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void play() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("play")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().play();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void pause() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("pause")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().pause();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.pauseCalled).isTrue();
+ }
+
+ @Test
+ public void stop() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("stop")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().stop();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.stopCalled).isTrue();
+ }
+
+ @Test
+ public void prepare() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("prepare")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().prepare();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void seekTo() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("seekTo")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ long seekPosition = 12125L;
+ controller.getTransportControls().seekTo(seekPosition);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToCalled).isTrue();
+ assertThat(player.seekPositionMs).isEqualTo(seekPosition);
+ }
+
+ @Test
+ public void setPlaybackSpeed_callsSetPlaybackSpeed() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setPlaybackSpeed")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ float testSpeed = 2.0f;
+ controller.getTransportControls().setPlaybackSpeed(testSpeed);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setPlaybackSpeedCalled).isTrue();
+ assertThat(player.playbackParameters.speed).isEqualTo(testSpeed);
+ }
+
+ @Test
+ public void addQueueItem() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("addQueueItem")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ handler.postAndSync(
+ () -> {
+ player.timeline = MediaTestUtils.createTimeline(/* windowCount= */ 10);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ });
+
+ // Prepare an item to add.
+ String mediaId = "newMediaItemId";
+ MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder().setMediaId(mediaId).build();
+ controller.addQueueItem(desc);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.addMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems.get(0).mediaId).isEqualTo(mediaId);
+ }
+
+ @Test
+ public void addQueueItemWithIndex() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("addQueueItemWithIndex")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ handler.postAndSync(
+ () -> {
+ player.timeline = MediaTestUtils.createTimeline(/* windowCount= */ 10);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ });
+
+ // Prepare an item to add.
+ int testIndex = 1;
+ String mediaId = "media_id";
+ MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder().setMediaId(mediaId).build();
+ controller.addQueueItem(desc, testIndex);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.addMediaItemsCalled).isTrue();
+ assertThat(player.index).isEqualTo(testIndex);
+ assertThat(player.mediaItems.get(0).mediaId).isEqualTo(mediaId);
+ }
+
+ @Test
+ public void removeQueueItem() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("removeQueueItem")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ List mediaItems = MediaTestUtils.createConvergedMediaItems(/* size= */ 10);
+ handler.postAndSync(
+ () -> {
+ player.timeline = new PlaylistTimeline(mediaItems);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ });
+
+ // Select an item to remove.
+ int targetIndex = 3;
+ MediaItem targetItem = mediaItems.get(targetIndex);
+ MediaDescriptionCompat desc =
+ new MediaDescriptionCompat.Builder().setMediaId(targetItem.mediaId).build();
+ controller.removeQueueItem(desc);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.removeMediaItemsCalled).isTrue();
+ assertThat(player.fromIndex).isEqualTo(targetIndex);
+ assertThat(player.toIndex).isEqualTo(targetIndex + 1);
+ }
+
+ @Test
+ public void skipToPrevious() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("skipToPrevious")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().skipToPrevious();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.previousCalled).isTrue();
+ }
+
+ @Test
+ public void skipToNext() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("skipToNext")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().skipToNext();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.nextCalled).isTrue();
+ }
+
+ @Test
+ public void skipToQueueItem() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("skipToQueueItem")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ handler.postAndSync(
+ () -> {
+ player.timeline = MediaTestUtils.createTimeline(/* windowCount= */ 10);
+ player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
+ });
+
+ // Get Queue from local MediaControllerCompat.
+ List queue = session.getSessionCompat().getController().getQueue();
+ int targetIndex = 3;
+ controller.getTransportControls().skipToQueueItem(queue.get(targetIndex).getQueueId());
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToDefaultPositionWithWindowIndexCalled).isTrue();
+ assertThat(player.seekWindowIndex).isEqualTo(targetIndex);
+ }
+
+ @Test
+ public void setShuffleMode() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setShuffleMode")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ @PlaybackStateCompat.ShuffleMode int testShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_GROUP;
+ controller.getTransportControls().setShuffleMode(testShuffleMode);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(player.setShuffleModeCalled).isTrue();
+ assertThat(player.shuffleModeEnabled).isTrue();
+ }
+
+ @Test
+ public void setRepeatMode() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setRepeatMode")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ int testRepeatMode = Player.REPEAT_MODE_ALL;
+ controller.getTransportControls().setRepeatMode(testRepeatMode);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(player.setRepeatModeCalled).isTrue();
+ assertThat(player.repeatMode).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void setVolumeTo_setsDeviceVolume() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setVolumeTo_setsDeviceVolume")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ MockPlayer remotePlayer =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+ handler.postAndSync(
+ () -> {
+ remotePlayer.deviceInfo =
+ new DeviceInfo(
+ DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ remotePlayer.deviceVolume = 23;
+ session.setPlayer(remotePlayer);
+ });
+
+ int targetVolume = 50;
+ controller.setVolumeTo(targetVolume, /* flags= */ 0);
+
+ assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(remotePlayer.setDeviceVolumeCalled).isTrue();
+ assertThat(remotePlayer.deviceVolume).isEqualTo(targetVolume);
+ }
+
+ @Test
+ public void adjustVolume_raise_increasesDeviceVolume() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("adjustVolume_raise_increasesDeviceVolume")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ MockPlayer remotePlayer =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+ handler.postAndSync(
+ () -> {
+ remotePlayer.deviceInfo =
+ new DeviceInfo(
+ DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ remotePlayer.deviceVolume = 23;
+ session.setPlayer(remotePlayer);
+ });
+
+ controller.adjustVolume(AudioManager.ADJUST_RAISE, /* flags= */ 0);
+
+ assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(remotePlayer.increaseDeviceVolumeCalled).isTrue();
+ }
+
+ @Test
+ public void adjustVolume_lower_decreasesDeviceVolume() throws Exception {
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("adjustVolume_lower_decreasesDeviceVolume")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ MockPlayer remotePlayer =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+ handler.postAndSync(
+ () -> {
+ remotePlayer.deviceInfo =
+ new DeviceInfo(
+ DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ remotePlayer.deviceVolume = 23;
+ session.setPlayer(remotePlayer);
+ });
+
+ controller.adjustVolume(AudioManager.ADJUST_LOWER, /* flags= */ 0);
+
+ assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(remotePlayer.decreaseDeviceVolumeCalled).isTrue();
+ }
+
+ @Test
+ public void setVolumeWithLocalVolume() throws Exception {
+ if (Build.VERSION.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setVolumeWithLocalVolume")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ // Here, we intentionally choose STREAM_ALARM in order not to consider
+ // 'Do Not Disturb' or 'Volume limit'.
+ int stream = AudioManager.STREAM_ALARM;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+
+ handler.postAndSync(
+ () -> {
+ // Set stream of the session.
+ AudioAttributes attrs =
+ MediaUtils.convertToAudioAttributes(
+ new AudioAttributesCompat.Builder().setLegacyStreamType(stream).build());
+ player.audioAttributes = attrs;
+ player.notifyAudioAttributesChanged(attrs);
+ });
+
+ int originalVolume = audioManager.getStreamVolume(stream);
+ int targetVolume = originalVolume == minVolume ? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
+
+ controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
+ PollingCheck.waitFor(
+ VOLUME_CHANGE_TIMEOUT_MS, () -> targetVolume == audioManager.getStreamVolume(stream));
+
+ // Set back to original volume.
+ audioManager.setStreamVolume(stream, originalVolume, /* flags= */ 0);
+ }
+
+ @Test
+ public void adjustVolumeWithLocalVolume() throws Exception {
+ if (Build.VERSION.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("adjustVolumeWithLocalVolume")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ // Here, we intentionally choose STREAM_ALARM in order not to consider
+ // 'Do Not Disturb' or 'Volume limit'.
+ int stream = AudioManager.STREAM_ALARM;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+
+ handler.postAndSync(
+ () -> {
+ // Set stream of the session.
+ AudioAttributes attrs =
+ MediaUtils.convertToAudioAttributes(
+ new AudioAttributesCompat.Builder().setLegacyStreamType(stream).build());
+ player.audioAttributes = attrs;
+ player.notifyAudioAttributesChanged(attrs);
+ });
+
+ int originalVolume = audioManager.getStreamVolume(stream);
+ int direction =
+ originalVolume == minVolume ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
+ int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
+
+ controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
+ PollingCheck.waitFor(
+ VOLUME_CHANGE_TIMEOUT_MS, () -> targetVolume == audioManager.getStreamVolume(stream));
+
+ // Set back to original volume.
+ audioManager.setStreamVolume(stream, originalVolume, /* flags= */ 0);
+ }
+
+ @Test
+ public void sendCommand() throws Exception {
+ // TODO(jaewan): Need to revisit with the permission.
+ String testCommand = "test_command";
+ Bundle testArgs = new Bundle();
+ testArgs.putString("args", "test_args");
+
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
+ MediaSession.ConnectResult commands = super.onConnect(session, controller);
+ SessionCommands.Builder builder =
+ new SessionCommands.Builder(commands.availableSessionCommands);
+ builder.add(new SessionCommand(testCommand, /* extras= */ null));
+ return new MediaSession.ConnectResult(
+ builder.build(), commands.availablePlayerCommands);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ @NonNull
+ public ListenableFuture onCustomCommand(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull SessionCommand sessionCommand,
+ Bundle args) {
+ assertThat(sessionCommand.customAction).isEqualTo(testCommand);
+ assertThat(TestUtils.equals(testArgs, args)).isTrue();
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("sendCommand")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.sendCommand(testCommand, testArgs, /* cb= */ null);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void controllerCallback_sessionRejects() throws Exception {
+ SessionCallback sessionCallback =
+ new SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ return null;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("controllerCallback_sessionRejects")
+ .setSessionCallback(sessionCallback)
+ .build();
+
+ // Session will not accept the controller's commands.
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().play();
+ assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ public void prepareFromMediaUri() throws Exception {
+ Uri mediaUri = Uri.parse("foo://bar");
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri).isEqualTo(mediaUri);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("prepareFromMediaUri")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().prepareFromUri(mediaUri, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void playFromMediaUri() throws Exception {
+ Uri request = Uri.parse("foo://bar");
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri).isEqualTo(request);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("playFromMediaUri")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().playFromUri(request, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void prepareFromMediaId() throws Exception {
+ String request = "media_id";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri.toString())
+ .isEqualTo("androidx://media2-session/prepareFromMediaId?id=" + request);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("prepareFromMediaId")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().prepareFromMediaId(request, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void playFromMediaId() throws Exception {
+ String mediaId = "media_id";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri.toString())
+ .isEqualTo("androidx://media2-session/playFromMediaId?id=" + mediaId);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("playFromMediaId")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().playFromMediaId(mediaId, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void prepareFromSearch() throws Exception {
+ String query = "test_query";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri.toString())
+ .isEqualTo("androidx://media2-session/prepareFromSearch?query=" + query);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("prepareFromSearch")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().prepareFromSearch(query, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void playFromSearch() throws Exception {
+ String query = "test_query";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ Bundle extras) {
+ assertThat(uri.toString())
+ .isEqualTo("androidx://media2-session/playFromSearch?query=" + query);
+ assertThat(TestUtils.equals(bundle, extras)).isTrue();
+ latch.countDown();
+ return RESULT_SUCCESS;
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("playFromSearch")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().playFromSearch(query, bundle);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void setRating() throws Exception {
+ int ratingType = RatingCompat.RATING_5_STARS;
+ float ratingValue = 3.5f;
+ RatingCompat rating = RatingCompat.newStarRating(ratingType, ratingValue);
+ String mediaId = "media_id";
+
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ @NonNull
+ public ListenableFuture onSetRating(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull String mediaIdOut,
+ @NonNull Rating ratingOut) {
+ assertThat(mediaIdOut).isEqualTo(mediaId);
+ assertThat(ratingOut).isEqualTo(MediaUtils.convertToRating(rating));
+ latch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ };
+
+ handler.postAndSync(
+ () -> {
+ player.currentMediaItem = MediaTestUtils.createConvergedMediaItem(mediaId);
+ });
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("setRating")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ controller.getTransportControls().setRating(rating);
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void onCommandRequest() throws Exception {
+ ArrayList commands = new ArrayList<>();
+ CountDownLatch latchForPause = new CountDownLatch(1);
+ SessionCallback callback =
+ new TestSessionCallback() {
+ @Override
+ public int onPlayerCommandRequest(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controllerInfo,
+ @Player.Command int command) {
+ assertThat(controllerInfo.isTrusted()).isFalse();
+ commands.add(command);
+ if (command == COMMAND_PLAY_PAUSE) {
+ latchForPause.countDown();
+ return RESULT_ERROR_INVALID_STATE;
+ }
+ return RESULT_SUCCESS;
+ }
+ };
+
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("onPlayerCommandRequest")
+ .setSessionCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().pause();
+ assertThat(latchForPause.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(player.pauseCalled).isFalse();
+ assertThat(commands).hasSize(1);
+ assertThat(commands.get(0)).isEqualTo(COMMAND_PLAY_PAUSE);
+
+ controller.getTransportControls().prepare();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ assertThat(player.pauseCalled).isFalse();
+ assertThat(commands).hasSize(2);
+ assertThat(commands.get(1)).isEqualTo(COMMAND_PREPARE_STOP);
+ }
+
+ /** Test potential deadlock for calls between controller and session. */
+ @Test
+ public void deadlock() throws Exception {
+ MockPlayer player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build();
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("deadlock")
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ // This may hang if deadlock happens.
+ handler.postAndSync(
+ () -> {
+ int state = STATE_IDLE;
+ for (int i = 0; i < 100; i++) {
+ // triggers call from session to controller.
+ player.notifyPlaybackStateChanged(state);
+ // triggers call from controller to session.
+ controller.getTransportControls().play();
+
+ // Repeat above
+ player.notifyPlaybackStateChanged(state);
+ controller.getTransportControls().pause();
+ player.notifyPlaybackStateChanged(state);
+ controller.getTransportControls().stop();
+ player.notifyPlaybackStateChanged(state);
+ controller.getTransportControls().skipToNext();
+ player.notifyPlaybackStateChanged(state);
+ controller.getTransportControls().skipToPrevious();
+ }
+ },
+ LONG_TIMEOUT_MS);
+ }
+
+ @Test
+ public void closedSession_ignoresController() throws Exception {
+ String sessionId = "closedSession_ignoresController";
+ session =
+ new MediaSession.Builder(context, player)
+ .setId(sessionId)
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompatToken(), /* waitForConnection= */ true);
+ session.release();
+ session = null;
+
+ controller.getTransportControls().play();
+ assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+
+ // Ensure that the controller cannot use newly create session with the same ID.
+ // Recreated session has different session stub, so previously created controller
+ // shouldn't be available.
+ session =
+ new MediaSession.Builder(context, player)
+ .setId(sessionId)
+ .setSessionCallback(new TestSessionCallback())
+ .build();
+
+ controller.getTransportControls().play();
+ assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ private static class TestSessionCallback extends SessionCallback {
+
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(MediaSession session, ControllerInfo controller) {
+ if (EXPECTED_CONTROLLER_PACKAGE_NAME.equals(controller.getPackageName())) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCompatCallbackWithMediaControllerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCompatCallbackWithMediaControllerTest.java
new file mode 100644
index 0000000000..42927146da
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionCompatCallbackWithMediaControllerTest.java
@@ -0,0 +1,1169 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_AUTHORITY;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_PATH_SET_MEDIA_URI;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_QUERY_ID;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_QUERY_URI;
+import static com.google.android.exoplayer2.session.MediaConstants.MEDIA_URI_SCHEME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.VOLUME_CHANGE_TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.PlaybackStateCompat.RepeatMode;
+import android.support.v4.media.session.PlaybackStateCompat.ShuffleMode;
+import androidx.media.AudioManagerCompat;
+import androidx.media.VolumeProviderCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.StarRating;
+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.MockActivity;
+import com.google.android.exoplayer2.session.vct.common.PollingCheck;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.ArrayList;
+import java.util.List;
+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.runner.RunWith;
+
+/** Tests for {@link MediaSessionCompat.Callback} with {@link MediaController}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSessionCompatCallbackWithMediaControllerTest {
+ private static final String TAG = "MediaControllerTest";
+
+ // The maximum time to wait for an operation.
+ private static final long TIMEOUT_MS = 3000L;
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ private Context context;
+ private MediaSessionCompat session;
+ private MediaSessionCallback sessionCallback;
+ private AudioManager audioManager;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ Intent sessionActivity = new Intent(context, MockActivity.class);
+ // Create this test specific MediaSession to use our own Handler.
+ PendingIntent intent = PendingIntent.getActivity(context, 0, sessionActivity, 0);
+
+ sessionCallback = new MediaSessionCallback();
+ session = new MediaSessionCompat(context, TAG + "Compat");
+ session.setCallback(sessionCallback, threadTestRule.getHandler());
+ session.setSessionActivity(intent);
+ session.setActive(true);
+
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ @After
+ public void cleanUp() {
+ if (session != null) {
+ session.release();
+ session = null;
+ }
+ }
+
+ private RemoteMediaController createControllerAndWaitConnection() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference sessionToken2 = new AtomicReference<>();
+ SessionToken.createSessionToken(
+ context,
+ session.getSessionToken(),
+ (compatToken, sessionToken) -> {
+ assertThat(sessionToken.isLegacySession()).isTrue();
+ sessionToken2.set(sessionToken);
+ latch.countDown();
+ });
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ return controllerTestRule.createRemoteController(sessionToken2.get(), true, null);
+ }
+
+ @Test
+ public void play() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.play();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(1);
+ }
+
+ @Test
+ public void pause() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.pause();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPauseCalled).isEqualTo(true);
+ }
+
+ @Test
+ public void prepare() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.prepare();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareCalled).isEqualTo(true);
+ }
+
+ @Test
+ public void stop() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.stop();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onStopCalled).isEqualTo(true);
+ }
+
+ @Test
+ public void seekToDefaultPosition() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.seekToDefaultPosition();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSeekToCalled).isTrue();
+ assertThat(sessionCallback.seekPosition).isEqualTo(/* pos= */ 0);
+ }
+
+ @Test
+ public void seekToDefaultPosition_withWindowIndex() throws Exception {
+ int testWindowIndex = 1;
+ List testQueue = MediaTestUtils.createQueueItems(/* size= */ 3);
+
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+
+ sessionCallback.reset(2);
+
+ controller.seekToDefaultPosition(testWindowIndex);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSkipToQueueItemCalled).isTrue();
+ assertThat(sessionCallback.queueItemId).isEqualTo(testQueue.get(testWindowIndex).getQueueId());
+ assertThat(sessionCallback.onSeekToCalled).isTrue();
+ assertThat(sessionCallback.seekPosition).isEqualTo(/* pos= */ 0);
+ }
+
+ @Test
+ public void seekTo() throws Exception {
+ long testPositionMs = 12125L;
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.seekTo(testPositionMs);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSeekToCalled).isTrue();
+ assertThat(sessionCallback.seekPosition).isEqualTo(testPositionMs);
+ }
+
+ @Test
+ public void seekTo_withWindowIndex() throws Exception {
+ int testWindowIndex = 1;
+ long testPositionMs = 12L;
+
+ List testQueue = MediaTestUtils.createQueueItems(/* size= */ 3);
+
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+
+ sessionCallback.reset(2);
+
+ controller.seekTo(testWindowIndex, testPositionMs);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSkipToQueueItemCalled).isTrue();
+ assertThat(sessionCallback.queueItemId).isEqualTo(testQueue.get(testWindowIndex).getQueueId());
+ assertThat(sessionCallback.onSeekToCalled).isTrue();
+ assertThat(sessionCallback.seekPosition).isEqualTo(testPositionMs);
+ }
+
+ @Test
+ public void setPlaybackSpeed_notifiesOnSetPlaybackSpeed() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ float testSpeed = 2.0f;
+ controller.setPlaybackSpeed(testSpeed);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetPlaybackSpeedCalled).isTrue();
+ assertThat(sessionCallback.speed).isEqualTo(testSpeed);
+ }
+
+ @Test
+ public void setPlaybackParameters_notifiesOnSetPlaybackSpeed() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.2f);
+ controller.setPlaybackParameters(playbackParameters);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetPlaybackSpeedCalled).isTrue();
+ assertThat(sessionCallback.speed).isEqualTo(playbackParameters.speed);
+ }
+
+ @Test
+ public void setPlaybackParameters_withDefault_notifiesOnSetPlaybackSpeedWithDefault()
+ throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setPlaybackParameters(PlaybackParameters.DEFAULT);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetPlaybackSpeedCalled).isTrue();
+ assertThat(sessionCallback.speed).isEqualTo(PlaybackParameters.DEFAULT.speed);
+ }
+
+ @Test
+ public void addMediaItems() throws Exception {
+ int size = 2;
+ List testList = MediaTestUtils.createConvergedMediaItems(size);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(size);
+
+ int testIndex = 1;
+ controller.addMediaItems(testIndex, testList);
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onAddQueueItemAtCalledCount).isEqualTo(size);
+ for (int i = 0; i < size; i++) {
+ assertThat(sessionCallback.queueIndices.get(i)).isEqualTo(testIndex + i);
+ assertThat(sessionCallback.queueDescriptionListForAdd.get(i).getMediaId())
+ .isEqualTo(testList.get(i).mediaId);
+ }
+ }
+
+ @Test
+ public void removeMediaItems() throws Exception {
+ List testList = MediaTestUtils.createConvergedMediaItems(/* size= */ 4);
+ int fromIndex = 1;
+ int toIndex = 3;
+ int count = toIndex - fromIndex;
+
+ session.setQueue(MediaUtils.convertToQueueItemList(testList));
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(count);
+
+ controller.removeMediaItems(fromIndex, toIndex);
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onRemoveQueueItemCalledCount).isEqualTo(count);
+ for (int i = 0; i < count; i++) {
+ assertThat(sessionCallback.queueDescriptionListForRemove.get(i).getMediaId())
+ .isEqualTo(testList.get(fromIndex + i).mediaId);
+ }
+ }
+
+ @Test
+ public void previous() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.previous();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSkipToPreviousCalled).isTrue();
+ }
+
+ @Test
+ public void next() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.next();
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSkipToNextCalled).isTrue();
+ }
+
+ @Test
+ public void setShuffleMode() throws Exception {
+ session.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setShuffleModeEnabled(true);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetShuffleModeCalled).isTrue();
+ assertThat(sessionCallback.shuffleMode).isEqualTo(PlaybackStateCompat.SHUFFLE_MODE_ALL);
+ }
+
+ @Test
+ public void setRepeatMode() throws Exception {
+ int testRepeatMode = Player.REPEAT_MODE_ALL;
+
+ session.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setRepeatMode(testRepeatMode);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetRepeatModeCalled).isTrue();
+ assertThat(sessionCallback.repeatMode).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void setDeviceVolume_forRemotePlayback_callsSetVolumeTo() throws Exception {
+ int maxVolume = 100;
+ int currentVolume = 23;
+ int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ TestVolumeProvider volumeProvider =
+ new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
+ session.setPlaybackToRemote(volumeProvider);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+
+ int targetVolume = 50;
+ controller.setDeviceVolume(targetVolume);
+ assertThat(volumeProvider.latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(volumeProvider.setVolumeToCalled).isTrue();
+ assertThat(volumeProvider.volume).isEqualTo(targetVolume);
+ }
+
+ @Test
+ public void increaseDeviceVolume_forRemotePlayback_callsAdjustVolumeWithDirectionRaise()
+ throws Exception {
+ int maxVolume = 100;
+ int currentVolume = 23;
+ int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ TestVolumeProvider volumeProvider =
+ new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
+ session.setPlaybackToRemote(volumeProvider);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+
+ controller.increaseDeviceVolume();
+ assertThat(volumeProvider.latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(volumeProvider.adjustVolumeCalled).isTrue();
+ assertThat(volumeProvider.direction).isEqualTo(AudioManager.ADJUST_RAISE);
+ }
+
+ @Test
+ public void decreaseDeviceVolume_forRemotePlayback_callsAdjustVolumeWithDirectionLower()
+ throws Exception {
+ int maxVolume = 100;
+ int currentVolume = 23;
+ int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ TestVolumeProvider volumeProvider =
+ new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
+ session.setPlaybackToRemote(volumeProvider);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+
+ controller.decreaseDeviceVolume();
+ assertThat(volumeProvider.latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(volumeProvider.adjustVolumeCalled).isTrue();
+ assertThat(volumeProvider.direction).isEqualTo(AudioManager.ADJUST_LOWER);
+ }
+
+ @Test
+ public void setDeviceVolume_forLocalPlayback_setsStreamVolume() throws Exception {
+ if (Build.VERSION.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ // STREAM_ALARM in order not to consider 'Do Not Disturb' or 'Volume limit'.
+ int stream = AudioManager.STREAM_ALARM;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+ session.setPlaybackToLocal(stream);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ int originalVolume = audioManager.getStreamVolume(stream);
+ int targetVolume = originalVolume == minVolume ? originalVolume + 1 : originalVolume - 1;
+
+ controller.setDeviceVolume(targetVolume);
+ PollingCheck.waitFor(
+ VOLUME_CHANGE_TIMEOUT_MS, () -> targetVolume == audioManager.getStreamVolume(stream));
+
+ // Set back to original volume.
+ audioManager.setStreamVolume(stream, originalVolume, /* flags= */ 0);
+ }
+
+ @Test
+ public void increaseDeviceVolume_forLocalPlayback_increasesStreamVolume() throws Exception {
+ if (Build.VERSION.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ // STREAM_ALARM in order not to consider 'Do Not Disturb' or 'Volume limit'.
+ int stream = AudioManager.STREAM_ALARM;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+ session.setPlaybackToLocal(stream);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ int originalVolume = audioManager.getStreamVolume(stream);
+ audioManager.setStreamVolume(stream, minVolume, /* flags= */ 0);
+ int targetVolume = minVolume + 1;
+
+ controller.increaseDeviceVolume();
+ PollingCheck.waitFor(
+ VOLUME_CHANGE_TIMEOUT_MS, () -> targetVolume == audioManager.getStreamVolume(stream));
+
+ // Set back to original volume.
+ audioManager.setStreamVolume(stream, originalVolume, /* flags= */ 0);
+ }
+
+ @Test
+ public void decreaseDeviceVolume_forLocalPlayback_decreasesStreamVolume() throws Exception {
+ if (Build.VERSION.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ // STREAM_ALARM in order not to consider 'Do Not Disturb' or 'Volume limit'.
+ int stream = AudioManager.STREAM_ALARM;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+ session.setPlaybackToLocal(stream);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ int originalVolume = audioManager.getStreamVolume(stream);
+ audioManager.setStreamVolume(stream, maxVolume, /* flags= */ 0);
+ int targetVolume = maxVolume - 1;
+
+ controller.decreaseDeviceVolume();
+ PollingCheck.waitFor(
+ VOLUME_CHANGE_TIMEOUT_MS, () -> targetVolume == audioManager.getStreamVolume(stream));
+
+ // Set back to original volume.
+ audioManager.setStreamVolume(stream, originalVolume, /* flags= */ 0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ public void setDeviceMuted_mute_forLocalPlayback_mutesStreamVolume() throws Exception {
+ if (audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ int stream = AudioManager.STREAM_MUSIC;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+ session.setPlaybackToLocal(stream);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ boolean wasMuted = audioManager.isStreamMute(stream);
+ audioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, /* flags= */ 0);
+
+ controller.setDeviceMuted(true);
+ PollingCheck.waitFor(VOLUME_CHANGE_TIMEOUT_MS, () -> audioManager.isStreamMute(stream));
+
+ // Set back to original mute state.
+ audioManager.adjustStreamVolume(
+ stream, wasMuted ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, /* flags= */ 0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ public void setDeviceMuted_unmute_forLocalPlayback_unmutesStreamVolume() throws Exception {
+ if (audioManager.isVolumeFixed()) {
+ // This test is not eligible for this device.
+ return;
+ }
+
+ int stream = AudioManager.STREAM_MUSIC;
+ int maxVolume = AudioManagerCompat.getStreamMaxVolume(audioManager, stream);
+ int minVolume = AudioManagerCompat.getStreamMinVolume(audioManager, stream);
+ if (maxVolume <= minVolume) {
+ return;
+ }
+ session.setPlaybackToLocal(stream);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ boolean wasMuted = audioManager.isStreamMute(stream);
+ audioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, /* flags= */ 0);
+
+ controller.setDeviceMuted(false);
+ PollingCheck.waitFor(VOLUME_CHANGE_TIMEOUT_MS, () -> !audioManager.isStreamMute(stream));
+
+ // Set back to original mute state.
+ audioManager.adjustStreamVolume(
+ stream, wasMuted ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, /* flags= */ 0);
+ }
+
+ @Test
+ public void sendCustomCommand() throws Exception {
+ String command = "test_custom_command";
+ Bundle testArgs = new Bundle();
+ testArgs.putString("args", "test_args");
+ SessionCommand testCommand = new SessionCommand(command, null);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ SessionResult result = controller.sendCustomCommand(testCommand, testArgs);
+ assertThat(result.resultCode).isEqualTo(SessionResult.RESULT_SUCCESS);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onCommandCalled).isTrue();
+ assertThat(sessionCallback.command).isEqualTo(command);
+ assertThat(TestUtils.equals(testArgs, sessionCallback.extras)).isTrue();
+ }
+
+ @Test
+ public void setRating() throws Exception {
+ float ratingValue = 3.5f;
+ Rating rating2 = new StarRating(5, ratingValue);
+ String mediaId = "media_id";
+ MediaMetadataCompat metadata =
+ new MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
+ .build();
+ session.setMetadata(metadata);
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setRating(mediaId, rating2);
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onSetRatingCalled).isTrue();
+ assertThat(MediaUtils.convertToRating(sessionCallback.rating)).isEqualTo(rating2);
+ }
+
+ @Test
+ public void setMediaUri_ignored() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(Uri.parse("androidx://test?test=xx"), /* extras= */ null);
+
+ assertThat(sessionCallback.await(NO_RESPONSE_TIMEOUT_MS)).isFalse();
+ }
+
+ @Test
+ public void setMediaUri_followedByPrepare_callsPrepareFromMediaId() throws Exception {
+ String testMediaId = "anyMediaId";
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId)
+ .build(),
+ testExtras);
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareFromMediaIdCalled).isTrue();
+ assertThat(sessionCallback.mediaId).isEqualTo(testMediaId);
+ assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.uri).isNull();
+ assertThat(sessionCallback.onPrepareCalled).isFalse();
+ }
+
+ @Test
+ public void setMediaUri_followedByPrepare_callsPrepareFromSearch() throws Exception {
+ String testSearchQuery = "anyQuery";
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery)
+ .build(),
+ testExtras);
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareFromSearchCalled).isTrue();
+ assertThat(sessionCallback.query).isEqualTo(testSearchQuery);
+ assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.uri).isNull();
+ assertThat(sessionCallback.onPrepareCalled).isFalse();
+ }
+
+ @Test
+ public void setMediaUri_followedByPrepare_callsPrepareFromUri() throws Exception {
+ Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_URI, testMediaUri.toString())
+ .build(),
+ testExtras);
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
+ assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.onPrepareCalled).isFalse();
+ }
+
+ @Test
+ public void setMediaUri_withoutFormattingFollowedByPrepare_callsPrepareFromUri()
+ throws Exception {
+ Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(testMediaUri, testExtras);
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
+ assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.onPrepareCalled).isFalse();
+ }
+
+ @Test
+ public void setMediaUri_followedByPlay_callsPlayFromMediaId() throws Exception {
+ String testMediaId = "anyMediaId";
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId)
+ .build(),
+ testExtras);
+ controller.play();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayFromMediaIdCalled).isTrue();
+ assertThat(sessionCallback.mediaId).isEqualTo(testMediaId);
+ assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.uri).isNull();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
+ }
+
+ @Test
+ public void setMediaUri_followedByPlay_callsPlayFromSearch() throws Exception {
+ String testSearchQuery = "anyQuery";
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery)
+ .build(),
+ testExtras);
+ controller.play();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayFromSearchCalled).isTrue();
+ assertThat(sessionCallback.query).isEqualTo(testSearchQuery);
+ assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.uri).isNull();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
+ }
+
+ @Test
+ public void setMediaUri_followedByPlay_callsPlayFromUri() throws Exception {
+ Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(
+ new Uri.Builder()
+ .scheme(MEDIA_URI_SCHEME)
+ .authority(MEDIA_URI_AUTHORITY)
+ .path(MEDIA_URI_PATH_SET_MEDIA_URI)
+ .appendQueryParameter(MEDIA_URI_QUERY_URI, testMediaUri.toString())
+ .build(),
+ testExtras);
+ controller.play();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
+ assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
+ }
+
+ @Test
+ public void setMediaUri_withoutFormattingFollowedByPlay_callsPlayFromUri() throws Exception {
+ Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
+ Bundle testExtras = new Bundle();
+ testExtras.putString("testKey", "testValue");
+
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(1);
+
+ controller.setMediaUri(testMediaUri, testExtras);
+ controller.play();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
+ assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
+ assertThat(sessionCallback.mediaId).isNull();
+ assertThat(sessionCallback.query).isNull();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
+ }
+
+ @Test
+ public void setMediaUri_followedByPrepareTwice_callsPrepareFromUriAndPrepare() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(2);
+
+ controller.setMediaUri(Uri.parse("androidx://test"), null);
+
+ controller.prepare();
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
+ assertThat(sessionCallback.onPrepareCalled).isTrue();
+ }
+
+ @Test
+ public void setMediaUri_followedByPlayTwice_callsPlayFromUriAndPlay() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(2);
+
+ controller.setMediaUri(Uri.parse("androidx://test"), /* extras= */ null);
+
+ controller.play();
+ controller.play();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
+ assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
+ assertThat(sessionCallback.onPlayCalledCount).isEqualTo(1);
+ }
+
+ @Test
+ public void setMediaUri_multipleCalls_skipped() throws Exception {
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ sessionCallback.reset(2);
+
+ Uri testUri1 = Uri.parse("androidx://test1");
+ Uri testUri2 = Uri.parse("androidx://test2");
+ controller.setMediaUri(testUri1, /* extras= */ null);
+ controller.setMediaUri(testUri2, /* extras= */ null);
+ controller.prepare();
+
+ assertThat(sessionCallback.await(TIMEOUT_MS)).isFalse();
+ assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
+ assertThat(sessionCallback.uri).isEqualTo(testUri2);
+ }
+
+ private void setPlaybackState(int state) {
+ long allActions =
+ PlaybackStateCompat.ACTION_PLAY
+ | PlaybackStateCompat.ACTION_PAUSE
+ | PlaybackStateCompat.ACTION_PLAY_PAUSE
+ | PlaybackStateCompat.ACTION_STOP
+ | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+ | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackStateCompat.ACTION_FAST_FORWARD
+ | PlaybackStateCompat.ACTION_REWIND;
+ PlaybackStateCompat playbackState =
+ new PlaybackStateCompat.Builder().setActions(allActions).setState(state, 0L, 0.0f).build();
+ session.setPlaybackState(playbackState);
+ }
+
+ class TestVolumeProvider extends VolumeProviderCompat {
+ CountDownLatch latch = new CountDownLatch(1);
+ boolean setVolumeToCalled;
+ boolean adjustVolumeCalled;
+ int volume;
+ int direction;
+
+ TestVolumeProvider(int controlType, int maxVolume, int currentVolume) {
+ super(controlType, maxVolume, currentVolume);
+ }
+
+ @Override
+ public void onSetVolumeTo(int volume) {
+ setVolumeToCalled = true;
+ this.volume = volume;
+ latch.countDown();
+ }
+
+ @Override
+ public void onAdjustVolume(int direction) {
+ adjustVolumeCalled = true;
+ this.direction = direction;
+ latch.countDown();
+ }
+ }
+
+ private class MediaSessionCallback extends MediaSessionCompat.Callback {
+ public CountDownLatch latch = new CountDownLatch(1);
+ public long seekPosition;
+ public float speed;
+ public long queueItemId;
+ public RatingCompat rating;
+ public String mediaId;
+ public String query;
+ public Uri uri;
+ public String action;
+ public String command;
+ public Bundle extras;
+ public ResultReceiver commandCallback;
+ public boolean captioningEnabled;
+ @RepeatMode public int repeatMode;
+ @ShuffleMode public int shuffleMode;
+ public final List queueIndices = new ArrayList<>();
+ public final List queueDescriptionListForAdd = new ArrayList<>();
+ public final List queueDescriptionListForRemove = new ArrayList<>();
+
+ public int onPlayCalledCount;
+ public boolean onPauseCalled;
+ public boolean onStopCalled;
+ public boolean onSkipToPreviousCalled;
+ public boolean onSkipToNextCalled;
+ public boolean onSeekToCalled;
+ public boolean onSetPlaybackSpeedCalled;
+ public boolean onSkipToQueueItemCalled;
+ public boolean onSetRatingCalled;
+ public boolean onPlayFromMediaIdCalled;
+ public boolean onPlayFromSearchCalled;
+ public boolean onPlayFromUriCalled;
+ public boolean onCustomActionCalled;
+ public boolean onCommandCalled;
+ public boolean onPrepareCalled;
+ public boolean onPrepareFromMediaIdCalled;
+ public boolean onPrepareFromSearchCalled;
+ public boolean onPrepareFromUriCalled;
+ public boolean onSetCaptioningEnabledCalled;
+ public boolean onSetRepeatModeCalled;
+ public boolean onSetShuffleModeCalled;
+ public boolean onAddQueueItemCalled;
+ public int onAddQueueItemAtCalledCount;
+ public int onRemoveQueueItemCalledCount;
+
+ public void reset(int count) {
+ latch = new CountDownLatch(count);
+ seekPosition = -1;
+ speed = -1.0f;
+ queueItemId = -1;
+ rating = null;
+ mediaId = null;
+ query = null;
+ uri = null;
+ action = null;
+ extras = null;
+ command = null;
+ commandCallback = null;
+ captioningEnabled = false;
+ repeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+ shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
+ queueIndices.clear();
+ queueDescriptionListForAdd.clear();
+ queueDescriptionListForRemove.clear();
+
+ onPlayCalledCount = 0;
+ onPauseCalled = false;
+ onStopCalled = false;
+ onSkipToPreviousCalled = false;
+ onSkipToNextCalled = false;
+ onSkipToQueueItemCalled = false;
+ onSeekToCalled = false;
+ onSetPlaybackSpeedCalled = false;
+ onSetRatingCalled = false;
+ onPlayFromMediaIdCalled = false;
+ onPlayFromSearchCalled = false;
+ onPlayFromUriCalled = false;
+ onCustomActionCalled = false;
+ onCommandCalled = false;
+ onPrepareCalled = false;
+ onPrepareFromMediaIdCalled = false;
+ onPrepareFromSearchCalled = false;
+ onPrepareFromUriCalled = false;
+ onSetCaptioningEnabledCalled = false;
+ onSetRepeatModeCalled = false;
+ onSetShuffleModeCalled = false;
+ onAddQueueItemCalled = false;
+ onAddQueueItemAtCalledCount = 0;
+ onRemoveQueueItemCalledCount = 0;
+ }
+
+ public boolean await(long timeoutMs) {
+ try {
+ return latch.await(timeoutMs, MILLISECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void onPlay() {
+ onPlayCalledCount++;
+ setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPause() {
+ onPauseCalled = true;
+ setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+ latch.countDown();
+ }
+
+ @Override
+ public void onStop() {
+ onStopCalled = true;
+ setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
+ latch.countDown();
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ onSkipToPreviousCalled = true;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSkipToNext() {
+ onSkipToNextCalled = true;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ onSeekToCalled = true;
+ seekPosition = pos;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSetPlaybackSpeed(float speed) {
+ onSetPlaybackSpeedCalled = true;
+ this.speed = speed;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSetRating(RatingCompat rating) {
+ onSetRatingCalled = true;
+ this.rating = rating;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ onPlayFromMediaIdCalled = true;
+ this.mediaId = mediaId;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlayFromSearch(String query, Bundle extras) {
+ onPlayFromSearchCalled = true;
+ this.query = query;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ onPlayFromUriCalled = true;
+ this.uri = uri;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+ onCustomActionCalled = true;
+ this.action = action;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSkipToQueueItem(long id) {
+ onSkipToQueueItemCalled = true;
+ queueItemId = id;
+ latch.countDown();
+ }
+
+ @Override
+ public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+ onCommandCalled = true;
+ this.command = command;
+ this.extras = extras;
+ commandCallback = cb;
+ cb.send(SessionResult.RESULT_SUCCESS, /* resultData= */ null);
+ latch.countDown();
+ }
+
+ @Override
+ public void onPrepare() {
+ onPrepareCalled = true;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ onPrepareFromMediaIdCalled = true;
+ this.mediaId = mediaId;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ onPrepareFromSearchCalled = true;
+ this.query = query;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ onPrepareFromUriCalled = true;
+ this.uri = uri;
+ this.extras = extras;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSetRepeatMode(@RepeatMode int repeatMode) {
+ onSetRepeatModeCalled = true;
+ this.repeatMode = repeatMode;
+ session.setRepeatMode(repeatMode);
+ latch.countDown();
+ }
+
+ @Override
+ public void onAddQueueItem(MediaDescriptionCompat description) {
+ onAddQueueItemCalled = true;
+ queueDescriptionListForAdd.add(description);
+ latch.countDown();
+ }
+
+ @Override
+ public void onAddQueueItem(MediaDescriptionCompat description, int index) {
+ onAddQueueItemAtCalledCount++;
+ queueIndices.add(index);
+ queueDescriptionListForAdd.add(description);
+ latch.countDown();
+ }
+
+ @Override
+ public void onRemoveQueueItem(MediaDescriptionCompat description) {
+ onRemoveQueueItemCalledCount++;
+ queueDescriptionListForRemove.add(description);
+ latch.countDown();
+ }
+
+ @Override
+ public void onSetCaptioningEnabled(boolean enabled) {
+ onSetCaptioningEnabledCalled = true;
+ captioningEnabled = enabled;
+ latch.countDown();
+ }
+
+ @Override
+ public void onSetShuffleMode(@ShuffleMode int shuffleMode) {
+ onSetShuffleModeCalled = true;
+ this.shuffleMode = shuffleMode;
+ session.setShuffleMode(shuffleMode);
+ latch.countDown();
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionKeyEventTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionKeyEventTest.java
new file mode 100644
index 0000000000..f06cdb86d4
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionKeyEventTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.view.KeyEvent;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.R;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for key event handling of {@link MediaSession}. In order to get the media key events, the
+ * player state is set to 'Playing' before every test method.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) // For AudioManager#dispatchMediaKeyEvent()
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionKeyEventTest {
+
+ private static String expectedControllerPackageName;
+
+ static {
+ if (Build.VERSION.SDK_INT >= 28 || Build.VERSION.SDK_INT < 21) {
+ expectedControllerPackageName = SUPPORT_APP_PACKAGE_NAME;
+ } else if (Build.VERSION.SDK_INT >= 24) {
+ // KeyEvent from system service has the package name "android".
+ expectedControllerPackageName = "android";
+ } else {
+ // In API 21+, MediaSessionCompat#getCurrentControllerInfo always returns fake info.
+ expectedControllerPackageName = LEGACY_CONTROLLER;
+ }
+ }
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule
+ public final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaSessionKeyEventTest");
+
+ // Intentionally member variable to prevent GC while playback is running.
+ // Should be only used on the sHandler.
+ private MediaPlayer mediaPlayer;
+
+ private AudioManager audioManager;
+ private TestHandler handler;
+ private MediaSession session;
+ private MockPlayer player;
+ private TestSessionCallback sessionCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ handler = threadTestRule.getHandler();
+ player =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+
+ sessionCallback = new TestSessionCallback();
+ session = new MediaSession.Builder(context, player).setSessionCallback(sessionCallback).build();
+
+ // Here's the requirement for an app to receive media key events via MediaSession.
+ // - SDK < 26: Player should be playing for receiving key events
+ // - SDK >= 26: Play a media item in the same process of the session for receiving key events.
+ handler.postAndSync(() -> player.notifyIsPlayingChanged(/* isPlaying= */ true));
+ if (Build.VERSION.SDK_INT >= 26) {
+ CountDownLatch latch = new CountDownLatch(1);
+ handler.postAndSync(
+ () -> {
+ // Pick the shortest media to finish within the timeout.
+ mediaPlayer = MediaPlayer.create(context, R.raw.camera_click);
+ mediaPlayer.setOnCompletionListener(
+ player -> {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ latch.countDown();
+ }
+ });
+ mediaPlayer.start();
+ });
+ assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ handler.postAndSync(
+ () -> {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ });
+ session.release();
+ }
+
+ private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) {
+ audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+ audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+ if (doubleTap) {
+ audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+ audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+ }
+ }
+
+ @Test
+ public void playKeyEvent() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void pauseKeyEvent() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.pauseCalled).isTrue();
+ }
+
+ @Test
+ public void nextKeyEvent() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.nextCalled).isTrue();
+ }
+
+ @Test
+ public void previousKeyEvent() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.previousCalled).isTrue();
+ }
+
+ @Test
+ public void stopKeyEvent() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.stopCalled).isTrue();
+ }
+
+ @Test
+ public void playPauseKeyEvent_play() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void playPauseKeyEvent_pause() throws Exception {
+ handler.postAndSync(
+ () -> {
+ player.playWhenReady = true;
+ });
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.pauseCalled).isTrue();
+ }
+
+ @Test
+ public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Exception {
+ dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.nextCalled).isTrue();
+ assertThat(player.playCalled).isFalse();
+ assertThat(player.pauseCalled).isFalse();
+ }
+
+ private static class TestSessionCallback extends MediaSession.SessionCallback {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(MediaSession session, ControllerInfo controller) {
+ if (expectedControllerPackageName.equals(controller.getPackageName())) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionManagerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionManagerTest.java
new file mode 100644
index 0000000000..9e9b628c98
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionManagerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaSessionManager}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSessionManagerTest {
+
+ private static final ComponentName MOCK_BROWSER_SERVICE_COMPAT_NAME =
+ new ComponentName(
+ SUPPORT_APP_PACKAGE_NAME, MockMediaBrowserServiceCompat.class.getCanonicalName());
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private Context context;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void getSessionServiceTokens() {
+ boolean hasMockBrowserServiceCompat = false;
+ boolean hasMockSessionService2 = false;
+ boolean hasMockLibraryService2 = false;
+ MediaSessionManager sessionManager = MediaSessionManager.getInstance(context);
+ Set 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPermissionTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPermissionTest.java
new file mode 100644
index 0000000000..de9a389391
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPermissionTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME;
+import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS;
+import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE;
+import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
+import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
+import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
+import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME;
+import static com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
+import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWith;
+import static com.google.android.exoplayer2.session.MediaUtils.createPlayerCommandsWithout;
+import static com.google.android.exoplayer2.session.SessionCommand.COMMAND_CODE_SESSION_SET_MEDIA_URI;
+import static com.google.android.exoplayer2.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING;
+import static com.google.android.exoplayer2.session.SessionResult.RESULT_SUCCESS;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.StarRating;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for permission handling of {@link MediaSession}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionPermissionTest {
+ private static final String SESSION_ID = "MediaSessionTest_permission";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule
+ public final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaSessionPermissionTest");
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ private Context context;
+ private MockPlayer player;
+ private MediaSession session;
+ private MySessionCallback callback;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @After
+ public void cleanUp() {
+ if (session != null) {
+ session.release();
+ session = null;
+ }
+ player = null;
+ callback = null;
+ }
+
+ private void createSessionWithAvailableCommands(
+ SessionCommands sessionCommands, Player.Commands playerCommands) {
+ player =
+ new MockPlayer.Builder()
+ .setLatchCount(1)
+ .setApplicationLooper(threadTestRule.getHandler().getLooper())
+ .build();
+ callback =
+ new MySessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ if (!TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) {
+ return null;
+ }
+ return new MediaSession.ConnectResult(sessionCommands, playerCommands);
+ }
+ };
+ if (this.session != null) {
+ this.session.release();
+ }
+ this.session =
+ new MediaSession.Builder(context, player)
+ .setId(SESSION_ID)
+ .setSessionCallback(callback)
+ .build();
+ }
+
+ private SessionCommands createSessionCommandsWith(SessionCommand command) {
+ return new SessionCommands.Builder().add(command).build();
+ }
+
+ private void testOnCommandRequest(int commandCode, PermissionTestTask runnable) throws Exception {
+ createSessionWithAvailableCommands(
+ SessionCommands.EMPTY, createPlayerCommandsWith(commandCode));
+ runnable.run(controllerTestRule.createRemoteController(session.getToken()));
+
+ assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(callback.onCommandRequestCalled).isTrue();
+ assertThat(callback.command).isEqualTo(commandCode);
+
+ createSessionWithAvailableCommands(
+ SessionCommands.EMPTY, createPlayerCommandsWithout(commandCode));
+ runnable.run(controllerTestRule.createRemoteController(session.getToken()));
+
+ assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(callback.onCommandRequestCalled).isFalse();
+ }
+
+ @Test
+ public void play() throws Exception {
+ testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::play);
+ }
+
+ @Test
+ public void pause() throws Exception {
+ testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::pause);
+ }
+
+ @Test
+ public void seekTo() throws Exception {
+ long position = 10;
+ testOnCommandRequest(
+ COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, controller -> controller.seekTo(position));
+ }
+
+ @Test
+ public void seekToNextMediaItem() throws Exception {
+ testOnCommandRequest(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, RemoteMediaController::next);
+ }
+
+ @Test
+ public void seekToPreviousMediaItem() throws Exception {
+ testOnCommandRequest(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, RemoteMediaController::previous);
+ }
+
+ @Test
+ public void setPlaylistMetadata() throws Exception {
+ testOnCommandRequest(
+ COMMAND_SET_MEDIA_ITEMS_METADATA,
+ controller -> controller.setPlaylistMetadata(MediaMetadata.EMPTY));
+ }
+
+ @Test
+ public void setMediaItems() throws Exception {
+ testOnCommandRequest(
+ COMMAND_CHANGE_MEDIA_ITEMS,
+ controller -> controller.setMediaItems(Collections.emptyList()));
+ }
+
+ @Test
+ public void addMediaItems() throws Exception {
+ testOnCommandRequest(
+ COMMAND_CHANGE_MEDIA_ITEMS,
+ controller -> controller.addMediaItems(/* index= */ 0, Collections.emptyList()));
+ }
+
+ @Test
+ public void removeMediaItems() throws Exception {
+ testOnCommandRequest(
+ COMMAND_CHANGE_MEDIA_ITEMS,
+ controller -> controller.removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1));
+ }
+
+ @Test
+ public void setDeviceVolume() throws Exception {
+ testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceVolume(0));
+ }
+
+ @Test
+ public void increaseDeviceVolume() throws Exception {
+ testOnCommandRequest(COMMAND_ADJUST_DEVICE_VOLUME, RemoteMediaController::increaseDeviceVolume);
+ }
+
+ @Test
+ public void decreaseDeviceVolume() throws Exception {
+ testOnCommandRequest(COMMAND_ADJUST_DEVICE_VOLUME, RemoteMediaController::decreaseDeviceVolume);
+ }
+
+ @Test
+ public void setDeviceMuted() throws Exception {
+ testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceMuted(true));
+ }
+
+ @Test
+ public void setMediaUri() throws Exception {
+ Uri uri = Uri.parse("media://uri");
+ createSessionWithAvailableCommands(
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
+ Player.Commands.EMPTY);
+ controllerTestRule
+ .createRemoteController(session.getToken())
+ .setMediaUri(uri, /* extras= */ null);
+
+ assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(callback.onSetMediaUriCalled).isTrue();
+ assertThat(callback.uri).isEqualTo(uri);
+ assertThat(callback.extras).isNull();
+
+ createSessionWithAvailableCommands(
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
+ Player.Commands.EMPTY);
+ controllerTestRule
+ .createRemoteController(session.getToken())
+ .setMediaUri(uri, /* extras= */ null);
+ assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(callback.onSetMediaUriCalled).isFalse();
+ }
+
+ @Test
+ public void setRating() throws Exception {
+ String mediaId = "testSetRating";
+ Rating rating = new StarRating(5, 3.5f);
+ createSessionWithAvailableCommands(
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
+ Player.Commands.EMPTY);
+ controllerTestRule.createRemoteController(session.getToken()).setRating(mediaId, rating);
+
+ assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(callback.onSetRatingCalled).isTrue();
+ assertThat(callback.mediaId).isEqualTo(mediaId);
+ assertThat(callback.rating).isEqualTo(rating);
+
+ createSessionWithAvailableCommands(
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
+ Player.Commands.EMPTY);
+ controllerTestRule.createRemoteController(session.getToken()).setRating(mediaId, rating);
+ assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(callback.onSetRatingCalled).isFalse();
+ }
+
+ @Test
+ public void changingPermissionForSessionCommandWithSetAvailableCommands() throws Exception {
+ String mediaId = "testSetRating";
+ Rating rating = new StarRating(5, 3.5f);
+ createSessionWithAvailableCommands(
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_RATING)),
+ Player.Commands.EMPTY);
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.setRating(mediaId, rating);
+ assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(callback.onSetRatingCalled).isTrue();
+ callback.reset();
+
+ // Change allowed commands.
+ session.setAvailableCommands(
+ getTestControllerInfo(),
+ createSessionCommandsWith(new SessionCommand(COMMAND_CODE_SESSION_SET_MEDIA_URI)),
+ Player.Commands.EMPTY);
+
+ controller.setRating(mediaId, rating);
+ assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ public void changingPermissionForPlayerCommandWithSetAvailableCommands() throws Exception {
+ int playPauseCommand = COMMAND_PLAY_PAUSE;
+ Player.Commands commandsWithPlayPause = createPlayerCommandsWith(playPauseCommand);
+ Player.Commands commandsWithoutPlayPause = createPlayerCommandsWithout(playPauseCommand);
+
+ // Create session with play/pause command.
+ createSessionWithAvailableCommands(SessionCommands.EMPTY, commandsWithPlayPause);
+ // Create player with play/pause command.
+ player.commands = commandsWithPlayPause;
+ player.notifyAvailableCommandsChanged(commandsWithPlayPause);
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.play();
+ assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(callback.onCommandRequestCalled).isTrue();
+ assertThat(callback.command).isEqualTo(playPauseCommand);
+ callback.reset();
+
+ // Change session to not have play/pause command.
+ session.setAvailableCommands(
+ getTestControllerInfo(), SessionCommands.EMPTY, commandsWithoutPlayPause);
+
+ controller.play();
+ assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+ assertThat(callback.onCommandRequestCalled).isFalse();
+ }
+
+ private ControllerInfo getTestControllerInfo() {
+ List controllers = session.getConnectedControllers();
+ assertThat(controllers).isNotNull();
+ for (int i = 0; i < controllers.size(); i++) {
+ if (TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controllers.get(i).getPackageName())) {
+ return controllers.get(i);
+ }
+ }
+ throw new IllegalStateException("Failed to get test controller info");
+ }
+
+ /* @FunctionalInterface */
+ private interface PermissionTestTask {
+ void run(@NonNull RemoteMediaController controller) throws Exception;
+ }
+
+ private static class MySessionCallback extends MediaSession.SessionCallback {
+ public CountDownLatch countDownLatch;
+
+ public @Player.Command int command;
+ public String mediaId;
+ public Uri uri;
+ public Bundle extras;
+ public Rating rating;
+
+ public boolean onCommandRequestCalled;
+ public boolean onSetMediaUriCalled;
+ public boolean onSetRatingCalled;
+
+ public MySessionCallback() {
+ countDownLatch = new CountDownLatch(1);
+ }
+
+ public void reset() {
+ countDownLatch = new CountDownLatch(1);
+
+ mediaId = null;
+
+ onCommandRequestCalled = false;
+ onSetMediaUriCalled = false;
+ onSetRatingCalled = false;
+ }
+
+ @Override
+ public int onPlayerCommandRequest(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @Player.Command int command) {
+ assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
+ onCommandRequestCalled = true;
+ this.command = command;
+ countDownLatch.countDown();
+ return super.onPlayerCommandRequest(session, controller, command);
+ }
+
+ @Override
+ public int onSetMediaUri(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull Uri uri,
+ @Nullable Bundle extras) {
+ assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
+ onSetMediaUriCalled = true;
+ this.uri = uri;
+ this.extras = extras;
+ countDownLatch.countDown();
+ return RESULT_SUCCESS;
+ }
+
+ @Override
+ @NonNull
+ public ListenableFuture onSetRating(
+ @NonNull MediaSession session,
+ @NonNull ControllerInfo controller,
+ @NonNull String mediaId,
+ @NonNull Rating rating) {
+ assertThat(TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())).isTrue();
+ onSetRatingCalled = true;
+ this.mediaId = mediaId;
+ this.rating = rating;
+ countDownLatch.countDown();
+ return new SessionResult(RESULT_SUCCESS).asFuture();
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPlayerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPlayerTest.java
new file mode 100644
index 0000000000..a982860416
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionPlayerTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for the underlying {@link SessionPlayer} of {@link MediaSession}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionPlayerTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule
+ public final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaSessionPlayerTest");
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ private MediaSession session;
+ private MockPlayer player;
+ private RemoteMediaController controller;
+
+ @Before
+ public void setUp() throws Exception {
+ player =
+ new MockPlayer.Builder()
+ .setLatchCount(1)
+ .setApplicationLooper(threadTestRule.getHandler().getLooper())
+ .build();
+ session =
+ new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
+ .setSessionCallback(
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, MediaSession.ControllerInfo controller) {
+ if (SUPPORT_APP_PACKAGE_NAME.equals(controller.getPackageName())) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ })
+ .build();
+
+ // Create a default MediaController in client app.
+ controller = controllerTestRule.createRemoteController(session.getToken());
+ }
+
+ @After
+ public void cleanUp() {
+ if (session != null) {
+ session.release();
+ }
+ }
+
+ @Test
+ public void play_isCalledByController() throws Exception {
+ controller.play();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void pause_isCalledByController() throws Exception {
+ controller.pause();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.pauseCalled).isTrue();
+ }
+
+ @Test
+ public void prepare_isCalledByController() throws Exception {
+ controller.prepare();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void stop_isCalledByController() throws Exception {
+ controller.stop();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.stopCalled).isTrue();
+ }
+
+ @Test
+ public void setPlayWhenReady_isCalledByController() throws Exception {
+ boolean testPlayWhenReady = true;
+ controller.setPlayWhenReady(testPlayWhenReady);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setPlayWhenReadyCalled).isTrue();
+ assertThat(player.playWhenReady).isEqualTo(testPlayWhenReady);
+ }
+
+ @Test
+ public void seekToDefaultPosition_isCalledByController() throws Exception {
+ controller.seekToDefaultPosition();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToDefaultPositionCalled).isTrue();
+ }
+
+ @Test
+ public void seekToDefaultPosition_withWindowIndex_isCalledByController() throws Exception {
+ int windowIndex = 33;
+ controller.seekToDefaultPosition(windowIndex);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToDefaultPositionWithWindowIndexCalled).isTrue();
+ assertThat(player.seekWindowIndex).isEqualTo(windowIndex);
+ }
+
+ @Test
+ public void seekTo_isCalledByController() throws Exception {
+ long seekPositionMs = 12125L;
+ controller.seekTo(seekPositionMs);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToCalled).isTrue();
+ assertThat(player.seekPositionMs).isEqualTo(seekPositionMs);
+ }
+
+ @Test
+ public void seekTo_withWindowIndex_isCalledByController() throws Exception {
+ int windowIndex = 33;
+ long seekPositionMs = 12125L;
+ controller.seekTo(windowIndex, seekPositionMs);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToWithWindowIndexCalled).isTrue();
+ assertThat(player.seekWindowIndex).isEqualTo(windowIndex);
+ assertThat(player.seekPositionMs).isEqualTo(seekPositionMs);
+ }
+
+ @Test
+ public void setPlaybackSpeed_isCalledByController() throws Exception {
+ float testSpeed = 1.5f;
+ controller.setPlaybackSpeed(testSpeed);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.playbackParameters.speed).isEqualTo(testSpeed);
+ }
+
+ @Test
+ public void setPlaybackParameters_isCalledByController() throws Exception {
+ PlaybackParameters testPlaybackParameters =
+ new PlaybackParameters(/* speed= */ 1.4f, /* pitch= */ 2.3f);
+ controller.setPlaybackParameters(testPlaybackParameters);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setPlaybackParametersCalled).isTrue();
+ assertThat(player.playbackParameters).isEqualTo(testPlaybackParameters);
+ }
+
+ @Test
+ public void setPlaybackParameters_withDefault_isCalledByController() throws Exception {
+ controller.setPlaybackParameters(PlaybackParameters.DEFAULT);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setPlaybackParametersCalled).isTrue();
+ assertThat(player.playbackParameters).isEqualTo(PlaybackParameters.DEFAULT);
+ }
+
+ @Test
+ public void setMediaItems_withResetPosition_isCalledByController() throws Exception {
+ List items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
+
+ controller.setMediaItems(items, /* resetPosition= */ true);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems).isEqualTo(items);
+ assertThat(player.resetPosition).isTrue();
+ }
+
+ @Test
+ public void setMediaItems_withoutResetPosition_isCalledByController() throws Exception {
+ List items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
+
+ controller.setMediaItems(items, /* resetPosition= */ false);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems).isEqualTo(items);
+ assertThat(player.resetPosition).isFalse();
+ }
+
+ @Test
+ public void setMediaItems_withStartWindowAndPosition_isCalledByController() throws Exception {
+ List items = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
+ int startWindowIndex = 1;
+ long startPositionMs = 1234;
+
+ controller.setMediaItems(items, startWindowIndex, startPositionMs);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems).isEqualTo(items);
+ assertThat(player.startWindowIndex).isEqualTo(startWindowIndex);
+ assertThat(player.startPositionMs).isEqualTo(startPositionMs);
+ }
+
+ @Test
+ public void setMediaItems_withDuplicatedItems_isCalledByController() throws Exception {
+ int listSize = 4;
+ List list = MediaTestUtils.createConvergedMediaItems(listSize);
+ list.set(2, list.get(1));
+ controller.setMediaItems(list);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems.size()).isEqualTo(listSize);
+ for (int i = 0; i < listSize; i++) {
+ assertThat(player.mediaItems.get(i).mediaId).isEqualTo(list.get(i).mediaId);
+ }
+ }
+
+ @Test
+ public void setMediaItems_withLongPlaylist_isCalledByController() throws Exception {
+ int listSize = 5000;
+ // Make client app to generate a long list, and call setMediaItems() with it.
+ controller.createAndSetFakeMediaItems(listSize);
+ assertThat(player.countDownLatch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems).isNotNull();
+ assertThat(player.mediaItems.size()).isEqualTo(listSize);
+ for (int i = 0; i < listSize; i++) {
+ // Each item's media ID will be same as its index.
+ assertThat(player.mediaItems.get(i).mediaId).isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
+ }
+ }
+
+ @Test
+ public void setPlaylistMetadata_isCalledByController() throws Exception {
+ MediaMetadata playlistMetadata = new MediaMetadata.Builder().setTitle("title").build();
+
+ controller.setPlaylistMetadata(playlistMetadata);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setPlaylistMetadataCalled).isTrue();
+ assertThat(player.playlistMetadata).isEqualTo(playlistMetadata);
+ }
+
+ @Test
+ public void addMediaItems_isCalledByController() throws Exception {
+ int index = 1;
+ int size = 2;
+ List mediaItems = MediaTestUtils.createConvergedMediaItems(size);
+
+ controller.addMediaItems(index, mediaItems);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.addMediaItemsCalled).isTrue();
+ assertThat(player.index).isEqualTo(index);
+ assertThat(player.mediaItems).isEqualTo(mediaItems);
+ }
+
+ @Test
+ public void removeMediaItems_isCalledByController() throws Exception {
+ int fromIndex = 1;
+ int toIndex = 3;
+
+ controller.removeMediaItems(fromIndex, toIndex);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.removeMediaItemsCalled).isTrue();
+ assertThat(player.fromIndex).isEqualTo(fromIndex);
+ assertThat(player.toIndex).isEqualTo(toIndex);
+ }
+
+ @Test
+ public void moveMediaItems_isCalledByController() throws Exception {
+ int fromIndex = 1;
+ int toIndex = 2;
+ int newIndex = 3;
+
+ controller.moveMediaItems(fromIndex, toIndex, newIndex);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.moveMediaItemsCalled).isTrue();
+ assertThat(player.fromIndex).isEqualTo(fromIndex);
+ assertThat(player.toIndex).isEqualTo(toIndex);
+ assertThat(player.newIndex).isEqualTo(newIndex);
+ }
+
+ @Test
+ public void skipToPreviousItem_isCalledByController() throws Exception {
+ controller.previous();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.previousCalled).isTrue();
+ }
+
+ @Test
+ public void skipToNextItem_isCalledByController() throws Exception {
+ controller.next();
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.nextCalled).isTrue();
+ }
+
+ @Test
+ public void setShuffleModeEnabled_isCalledByController() throws Exception {
+ boolean testShuffleModeEnabled = true;
+ controller.setShuffleModeEnabled(testShuffleModeEnabled);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(player.setShuffleModeCalled).isTrue();
+ assertThat(player.shuffleModeEnabled).isEqualTo(testShuffleModeEnabled);
+ }
+
+ @Test
+ public void setRepeatMode_isCalledByController() throws Exception {
+ int testRepeatMode = Player.REPEAT_MODE_ALL;
+ controller.setRepeatMode(testRepeatMode);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ assertThat(player.setRepeatModeCalled).isTrue();
+ assertThat(player.repeatMode).isEqualTo(testRepeatMode);
+ }
+
+ @Test
+ public void setVolume_isCalledByController() throws Exception {
+ float testVolume = .123f;
+ controller.setVolume(testVolume);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setVolumeCalled).isTrue();
+ assertThat(player.volume).isEqualTo(testVolume);
+ }
+
+ @Test
+ public void setDeviceVolume_isCalledByController() throws Exception {
+ changePlaybackTypeToRemote();
+
+ int testVolume = 12;
+ controller.setDeviceVolume(testVolume);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setDeviceVolumeCalled).isTrue();
+ assertThat(player.deviceVolume).isEqualTo(testVolume);
+ }
+
+ @Test
+ public void increaseDeviceVolume_isCalledByController() throws Exception {
+ changePlaybackTypeToRemote();
+
+ controller.increaseDeviceVolume();
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.increaseDeviceVolumeCalled).isTrue();
+ }
+
+ @Test
+ public void decreaseDeviceVolume_isCalledByController() throws Exception {
+ changePlaybackTypeToRemote();
+
+ controller.decreaseDeviceVolume();
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.decreaseDeviceVolumeCalled).isTrue();
+ }
+
+ @Test
+ public void setDeviceMuted_isCalledByController() throws Exception {
+ player.deviceMuted = false;
+ controller.setDeviceMuted(true);
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.setDeviceMutedCalled).isTrue();
+ assertThat(player.deviceMuted).isTrue();
+ }
+
+ private void changePlaybackTypeToRemote() throws Exception {
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ player.deviceInfo =
+ new DeviceInfo(
+ DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 100);
+ player.notifyDeviceInfoChanged();
+ });
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceNotificationTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceNotificationTest.java
new file mode 100644
index 0000000000..fd913af788
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceNotificationTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.STATE_READY;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_SESSION_SERVICE;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Looper;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.R;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Manual test of {@link MediaSessionService} for showing/removing notification when the playback is
+ * started/ended.
+ *
+ * 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);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceTest.java
new file mode 100644
index 0000000000..9a3b69310d
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionServiceTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Looper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import com.google.android.exoplayer2.session.MediaSession.ControllerInfo;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaSessionService}. */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class MediaSessionServiceTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule
+ public final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaSessionServiceTest");
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ @Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
+
+ private Context context;
+ private Looper looper;
+ private SessionToken token;
+
+ @Before
+ public void setUp() {
+ TestServiceRegistry.getInstance().cleanUp();
+ context = ApplicationProvider.getApplicationContext();
+ looper = threadTestRule.getHandler().getLooper();
+ token =
+ new SessionToken(context, new ComponentName(context, LocalMockMediaSessionService.class));
+ }
+
+ @After
+ public void cleanUp() {
+ TestServiceRegistry.getInstance().cleanUp();
+ }
+
+ /**
+ * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} is called when
+ * controller tries to connect, with the proper arguments.
+ */
+ @Test
+ public void onGetSessionIsCalled() throws Exception {
+ List controllerInfoList = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ TestServiceRegistry.getInstance()
+ .setOnGetSessionHandler(
+ new TestServiceRegistry.OnGetSessionHandler() {
+ @Override
+ public MediaSession onGetSession(ControllerInfo controllerInfo) {
+ controllerInfoList.add(controllerInfo);
+ latch.countDown();
+ return null;
+ }
+ });
+
+ Bundle testHints = new Bundle();
+ testHints.putString("test_key", "test_value");
+ controllerTestRule.createRemoteController(token, /* waitForConnection= */ false, testHints);
+
+ // onGetSession() should be called.
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(controllerInfoList.get(0).getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints))
+ .isTrue();
+ }
+
+ /**
+ * Tests whether the controller is connected to the session which is returned from {@link
+ * MediaSessionService#onGetSession(ControllerInfo)}. Also checks whether the connection hints are
+ * properly passed to {@link MediaSession.SessionCallback#onConnect(MediaSession,
+ * ControllerInfo)}.
+ */
+ @Test
+ public void onGetSession_returnsSession() throws Exception {
+ List controllerInfoList = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+
+ MediaSession testSession =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(
+ context, new MockPlayer.Builder().setApplicationLooper(looper).build())
+ .setId("testOnGetSession_returnsSession")
+ .setSessionCallback(
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ controllerInfoList.add(controller);
+ latch.countDown();
+ return new MediaSession.ConnectResult();
+ }
+ })
+ .build());
+
+ TestServiceRegistry.getInstance()
+ .setOnGetSessionHandler(
+ new TestServiceRegistry.OnGetSessionHandler() {
+ @Override
+ public MediaSession onGetSession(ControllerInfo controllerInfo) {
+ return testSession;
+ }
+ });
+
+ Bundle testHints = new Bundle();
+ testHints.putString("test_key", "test_value");
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(token, true, testHints);
+
+ // MediaSession.SessionCallback#onConnect() should be called.
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(controllerInfoList.get(0).getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints))
+ .isTrue();
+
+ // The controller should be connected to the right session.
+ assertThat(controller.getConnectedSessionToken()).isNotEqualTo(token);
+ assertThat(controller.getConnectedSessionToken()).isEqualTo(testSession.getToken());
+ }
+
+ /**
+ * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} can return different
+ * sessions for different controllers.
+ */
+ @Test
+ public void onGetSession_returnsDifferentSessions() throws Exception {
+ List tokens = new ArrayList<>();
+ TestServiceRegistry.getInstance()
+ .setOnGetSessionHandler(
+ new TestServiceRegistry.OnGetSessionHandler() {
+ @Override
+ public MediaSession onGetSession(ControllerInfo controllerInfo) {
+ MediaSession session =
+ createMediaSession(
+ "testOnGetSession_returnsDifferentSessions" + System.currentTimeMillis());
+ tokens.add(session.getToken());
+ return session;
+ }
+ });
+
+ RemoteMediaController controller1 =
+ controllerTestRule.createRemoteController(token, true, null);
+ RemoteMediaController controller2 =
+ controllerTestRule.createRemoteController(token, true, null);
+
+ assertThat(controller2.getConnectedSessionToken())
+ .isNotEqualTo(controller1.getConnectedSessionToken());
+ assertThat(controller1.getConnectedSessionToken()).isEqualTo(tokens.get(0));
+ assertThat(controller2.getConnectedSessionToken()).isEqualTo(tokens.get(1));
+ }
+
+ /**
+ * Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} can reject incoming
+ * connection by returning null.
+ */
+ @Test
+ public void onGetSession_rejectsConnection() throws Exception {
+ TestServiceRegistry.getInstance()
+ .setOnGetSessionHandler(
+ new TestServiceRegistry.OnGetSessionHandler() {
+ @Override
+ public MediaSession onGetSession(ControllerInfo controllerInfo) {
+ return null;
+ }
+ });
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionToken(token)
+ .setControllerCallback(
+ new MediaController.ControllerCallback() {
+ @Override
+ public void onDisconnected(@NonNull MediaController controller) {
+ latch.countDown();
+ }
+ })
+ .setApplicationLooper(looper)
+ .build();
+
+ // MediaController2.ControllerCallback#onDisconnected() should be called.
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(controller.getConnectedToken()).isNull();
+ threadTestRule.getHandler().postAndSync(controller::release);
+ }
+
+ @Test
+ public void allControllersDisconnected_oneSession() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ TestServiceRegistry.getInstance()
+ .setSessionServiceCallback(
+ new TestServiceRegistry.SessionServiceCallback() {
+ @Override
+ public void onCreated() {
+ // no-op
+ }
+
+ @Override
+ public void onDestroyed() {
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaController controller1 =
+ controllerTestRule.createRemoteController(token, true, null);
+ RemoteMediaController controller2 =
+ controllerTestRule.createRemoteController(token, true, null);
+ controller1.release();
+ controller2.release();
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void allControllersDisconnected_multipleSessions() throws Exception {
+ TestServiceRegistry.getInstance()
+ .setOnGetSessionHandler(
+ new TestServiceRegistry.OnGetSessionHandler() {
+ @Override
+ public MediaSession onGetSession(ControllerInfo controllerInfo) {
+ return createMediaSession(
+ "testAllControllersDisconnected" + System.currentTimeMillis());
+ }
+ });
+ CountDownLatch latch = new CountDownLatch(1);
+ TestServiceRegistry.getInstance()
+ .setSessionServiceCallback(
+ new TestServiceRegistry.SessionServiceCallback() {
+ @Override
+ public void onCreated() {
+ // no-op
+ }
+
+ @Override
+ public void onDestroyed() {
+ latch.countDown();
+ }
+ });
+
+ RemoteMediaController controller1 =
+ controllerTestRule.createRemoteController(token, true, null);
+ RemoteMediaController controller2 =
+ controllerTestRule.createRemoteController(token, true, null);
+
+ controller1.release();
+ assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
+
+ // Service should be closed only when all controllers are closed.
+ controller2.release();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ public void getSessions() throws Exception {
+ controllerTestRule.createRemoteController(
+ token, /* waitForConnection= */ true, /* connectionHints= */ null);
+ MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
+ MediaSession session = createMediaSession("testGetSessions");
+ service.addSession(session);
+ List sessions = service.getSessions();
+ assertThat(sessions.contains(session)).isTrue();
+ assertThat(sessions.size()).isEqualTo(2);
+
+ service.removeSession(session);
+ sessions = service.getSessions();
+ assertThat(sessions.contains(session)).isFalse();
+ }
+
+ @Test
+ public void addSessions_removedWhenClose() throws Exception {
+ controllerTestRule.createRemoteController(
+ token, /* waitForConnection= */ true, /* connectionHints= */ null);
+ MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
+ MediaSession session = createMediaSession("testAddSessions_removedWhenClose");
+ service.addSession(session);
+ List sessions = service.getSessions();
+ assertThat(sessions.contains(session)).isTrue();
+ assertThat(sessions.size()).isEqualTo(2);
+
+ session.release();
+ sessions = service.getSessions();
+ assertThat(sessions.contains(session)).isFalse();
+ }
+
+ private MediaSession createMediaSession(String id) {
+ return sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(
+ context, new MockPlayer.Builder().setApplicationLooper(looper).build())
+ .setId(id)
+ .build());
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTest.java
new file mode 100644
index 0000000000..4552ab9fe4
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.Player.STATE_IDLE;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.LONG_TIMEOUT_MS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.MediaSessionManager;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import com.google.android.exoplayer2.session.MediaSession.SessionCallback;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.util.Log;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaSession}. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaSessionTest {
+
+ private static final String TAG = "MediaSessionTest";
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
+
+ @Rule public final RemoteControllerTestRule controllerTestRule = new RemoteControllerTestRule();
+
+ @Rule public final MediaSessionTestRule sessionTestRule = new MediaSessionTestRule();
+
+ private Context context;
+ private TestHandler handler;
+ private MediaSession session;
+ private MockPlayer player;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ handler = threadTestRule.getHandler();
+ player =
+ new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build();
+
+ session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setId(TAG)
+ .setSessionCallback(
+ new MediaSession.SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, MediaSession.ControllerInfo controller) {
+ if (TextUtils.equals(
+ context.getPackageName(), controller.getPackageName())) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ })
+ .build());
+ }
+
+ @Test
+ public void builder() {
+ MediaSession.Builder builder;
+ try {
+ builder = new MediaSession.Builder(context, null);
+ assertWithMessage("null player shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+ try {
+ builder = new MediaSession.Builder(context, player);
+ builder.setId(null);
+ assertWithMessage("null id shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+ try {
+ builder = new MediaSession.Builder(context, player);
+ builder.setExtras(null);
+ assertWithMessage("null extras shouldn't be allowed").fail();
+ } catch (NullPointerException e) {
+ // expected. pass-through
+ }
+ // Empty string as ID is allowed.
+ new MediaSession.Builder(context, player).setId("").build().release();
+ }
+
+ @Test
+ public void getPlayer() throws Exception {
+ assertThat(session.getPlayer()).isEqualTo(player);
+ }
+
+ @Test
+ public void setPlayer() throws Exception {
+ MockPlayer player =
+ new MockPlayer.Builder().setApplicationLooper(this.player.getApplicationLooper()).build();
+ session.setPlayer(player);
+ assertThat(session.getPlayer()).isEqualTo(player);
+ }
+
+ @Test
+ public void setPlayer_withSamePlayerInstance() throws Exception {
+ session.setPlayer(player);
+ assertThat(session.getPlayer()).isEqualTo(player);
+ }
+
+ @Test
+ public void setPlayer_withDifferentLooper_throwsIAE() throws Exception {
+ MockPlayer player =
+ new MockPlayer.Builder().setApplicationLooper(Looper.getMainLooper()).build();
+ try {
+ session.setPlayer(player);
+ assertWithMessage("IAE is expected").fail();
+ } catch (IllegalArgumentException unused) {
+ // expected
+ }
+ }
+
+ /** Test potential deadlock for calls between controller and session. */
+ @Test
+ @LargeTest
+ public void deadlock() throws Exception {
+ handler.postAndSync(
+ () -> {
+ session.release();
+ session = null;
+ });
+
+ HandlerThread testThread = new HandlerThread("deadlock");
+ testThread.start();
+ TestHandler testHandler = new TestHandler(testThread.getLooper());
+ try {
+ MockPlayer player =
+ new MockPlayer.Builder().setApplicationLooper(testThread.getLooper()).build();
+ handler.postAndSync(
+ () -> {
+ session = new MediaSession.Builder(context, player).setId("testDeadlock").build();
+ });
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+ // This may hang if deadlock happens.
+ testHandler.postAndSync(
+ () -> {
+ int state = STATE_IDLE;
+ for (int i = 0; i < 100; i++) {
+ Log.d(TAG, "testDeadlock for-loop started: index=" + i);
+ long startTime = SystemClock.elapsedRealtime();
+
+ // triggers call from session to controller.
+ player.notifyPlaybackStateChanged(state);
+ long endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "1) Time spent on API call(ms): " + (endTime - startTime));
+
+ // triggers call from controller to session.
+ startTime = endTime;
+ controller.play();
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "2) Time spent on API call(ms): " + (endTime - startTime));
+
+ // Repeat above
+ startTime = endTime;
+ player.notifyPlaybackStateChanged(state);
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "3) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ controller.pause();
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "4) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ player.notifyPlaybackStateChanged(state);
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "5) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ controller.seekTo(0);
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "6) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ player.notifyPlaybackStateChanged(state);
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "7) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ controller.next();
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "8) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ player.notifyPlaybackStateChanged(state);
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "9) Time spent on API call(ms): " + (endTime - startTime));
+
+ startTime = endTime;
+ controller.previous();
+ endTime = SystemClock.elapsedRealtime();
+ Log.d(TAG, "10) Time spent on API call(ms): " + (endTime - startTime));
+ }
+ },
+ LONG_TIMEOUT_MS);
+ } finally {
+ if (session != null) {
+ handler.postAndSync(
+ () -> {
+ // Clean up here because sessionHandler will be removed afterwards.
+ session.release();
+ session = null;
+ });
+ }
+
+ if (Build.VERSION.SDK_INT >= 18) {
+ testThread.quitSafely();
+ } else {
+ testThread.quit();
+ }
+ }
+ }
+
+ @Test
+ public void creatingTwoSessionWithSameId() {
+ String sessionId = "testSessionId";
+ MediaSession session =
+ new MediaSession.Builder(
+ context, new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build())
+ .setId(sessionId)
+ .build();
+
+ MediaSession.Builder builderWithSameId =
+ new MediaSession.Builder(
+ context, new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build());
+ try {
+ builderWithSameId.setId(sessionId).build();
+ assertWithMessage(
+ "Creating a new session with the same ID in a process should not be allowed")
+ .fail();
+ } catch (IllegalStateException e) {
+ // expected. pass-through
+ }
+
+ session.release();
+ // Creating a new session with ID of the closed session is okay.
+ MediaSession sessionWithSameId = builderWithSameId.build();
+ sessionWithSameId.release();
+ }
+
+ @Test
+ public void sendCustomCommand_onConnect() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ SessionCommand testCommand = new SessionCommand("test", null);
+ SessionCallback testSessionCallback =
+ new SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, MediaSession.ControllerInfo controller) {
+ Future result = session.sendCustomCommand(controller, testCommand, null);
+ try {
+ // The controller is not connected yet.
+ assertThat(result.get(TIMEOUT_MS, MILLISECONDS).resultCode)
+ .isEqualTo(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ assertWithMessage("Fail to get result of the returned future.").fail();
+ }
+ return super.onConnect(session, controller);
+ }
+
+ @Override
+ public void onPostConnect(
+ @NonNull MediaSession session, @NonNull MediaSession.ControllerInfo controller) {
+ Future result = session.sendCustomCommand(controller, testCommand, null);
+ try {
+ // The controller is connected but doesn't implement onCustomCommand.
+ assertThat(result.get(TIMEOUT_MS, MILLISECONDS).resultCode)
+ .isEqualTo(SessionResult.RESULT_ERROR_NOT_SUPPORTED);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ assertWithMessage("Fail to get result of the returned future.").fail();
+ }
+ latch.countDown();
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setSessionCallback(testSessionCallback)
+ .build());
+ controllerTestRule.createRemoteController(session.getToken());
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+
+ /** Test {@link MediaSession#getSessionCompatToken()}. */
+ @Test
+ public void getSessionCompatToken_returnsCompatibleWithMediaControllerCompat() throws Exception {
+ String expectedControllerCompatPackageName =
+ (21 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 24)
+ ? MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER
+ : context.getPackageName();
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player)
+ .setId("getSessionCompatToken_returnsCompatibleWithMediaControllerCompat")
+ .setSessionCallback(
+ new SessionCallback() {
+ @Nullable
+ @Override
+ public MediaSession.ConnectResult onConnect(
+ MediaSession session, MediaSession.ControllerInfo controller) {
+ if (TextUtils.equals(
+ expectedControllerCompatPackageName, controller.getPackageName())) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ })
+ .build());
+ MediaSessionCompat.Token token = session.getSessionCompatToken();
+ MediaControllerCompat controllerCompat = new MediaControllerCompat(context, token);
+ CountDownLatch sessionReadyLatch = new CountDownLatch(1);
+ controllerCompat.registerCallback(
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onSessionReady() {
+ sessionReadyLatch.countDown();
+ }
+ },
+ handler);
+ assertThat(sessionReadyLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+
+ long testSeekPositionMs = 1234;
+ controllerCompat.getTransportControls().seekTo(testSeekPositionMs);
+
+ assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(player.seekToCalled).isTrue();
+ assertThat(player.seekPositionMs).isEqualTo(testSeekPositionMs);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTestRule.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTestRule.java
new file mode 100644
index 0000000000..9fd1c38b9b
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaSessionTestRule.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/** TestRule for releasing {@link MediaSession} instances after use. */
+public class MediaSessionTestRule implements TestRule {
+ private final List 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 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaUtilsTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaUtilsTest.java
new file mode 100644
index 0000000000..c2fc3a84f7
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MediaUtilsTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.service.media.MediaBrowserService;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.text.TextUtils;
+import androidx.media.AudioAttributesCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.HeartRating;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PercentageRating;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.StarRating;
+import com.google.android.exoplayer2.ThumbRating;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MediaUtils}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class MediaUtilsTest {
+
+ private Context context;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void convertToBrowserItem() {
+ String mediaId = "testId";
+ CharSequence trackTitle = "testTitle";
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setMediaId(mediaId)
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle(trackTitle).build())
+ .build();
+
+ MediaBrowserCompat.MediaItem browserItem = MediaUtils.convertToBrowserItem(mediaItem);
+
+ assertThat(browserItem.getDescription()).isNotNull();
+ assertThat(browserItem.getDescription().getMediaId()).isEqualTo(mediaId);
+ assertThat(TextUtils.equals(browserItem.getDescription().getTitle(), trackTitle)).isTrue();
+ }
+
+ @Test
+ public void convertToMediaItem_browserItemToMediaItem() {
+ String mediaId = "testId";
+ String title = "testTitle";
+ MediaDescriptionCompat descriptionCompat =
+ new MediaDescriptionCompat.Builder().setMediaId(mediaId).setTitle(title).build();
+ MediaBrowserCompat.MediaItem browserItem =
+ new MediaBrowserCompat.MediaItem(descriptionCompat, /* flags= */ 0);
+
+ MediaItem mediaItem = MediaUtils.convertToMediaItem(browserItem);
+ assertThat(mediaItem.mediaId).isEqualTo(mediaId);
+ assertThat(mediaItem.mediaMetadata.title).isEqualTo(title);
+ }
+
+ @Test
+ public void convertToMediaItem_queueItemToMediaItem() {
+ String mediaId = "testMediaId";
+ String title = "testTitle";
+ MediaDescriptionCompat descriptionCompat =
+ new MediaDescriptionCompat.Builder().setMediaId(mediaId).setTitle(title).build();
+ MediaSessionCompat.QueueItem queueItem =
+ new MediaSessionCompat.QueueItem(descriptionCompat, /* id= */ 1);
+ MediaItem mediaItem = MediaUtils.convertToMediaItem(queueItem);
+ assertThat(mediaItem.mediaId).isEqualTo(mediaId);
+ assertThat(mediaItem.mediaMetadata.title.toString()).isEqualTo(title);
+ }
+
+ @Test
+ public void convertToMediaItem_metadataCompatToMediaItem() {
+ MediaMetadataCompat metadataCompat = null;
+ LegacyMediaItem mediaItem =
+ MediaUtils.convertToLegacyMediaItem(metadataCompat, RatingCompat.RATING_3_STARS);
+ assertThat(mediaItem).isNull();
+
+ String mediaId = "testId";
+ String displaySubtitle = "testDisplaySubtitle";
+ String displayDescription = "testDisplayDescription";
+ String title = "testTitle";
+ String iconUri = "testIconUri";
+ String mediaUri = "testMediaUri";
+
+ metadataCompat =
+ new MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, displayDescription)
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, iconUri)
+ .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, mediaUri)
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, displaySubtitle)
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
+ .build();
+ mediaItem = MediaUtils.convertToLegacyMediaItem(metadataCompat, RatingCompat.RATING_3_STARS);
+ assertThat(mediaItem).isNotNull();
+ assertThat(mediaItem.getMediaId()).isEqualTo(mediaId);
+ LegacyMediaMetadata metadata = mediaItem.getMetadata();
+ assertThat(metadata).isNotNull();
+ assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION))
+ .isEqualTo(displayDescription);
+ assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_ICON_URI))
+ .isEqualTo(iconUri);
+ assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_MEDIA_URI)).isEqualTo(mediaUri);
+ assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE))
+ .isEqualTo(displaySubtitle);
+ assertThat(metadata.getString(LegacyMediaMetadata.METADATA_KEY_TITLE)).isEqualTo(title);
+ }
+
+ @Test
+ public void convertToBrowserItemList() {
+ int size = 3;
+ List mediaItems = MediaTestUtils.createConvergedMediaItems(size);
+ List browserItems =
+ MediaUtils.convertToBrowserItemList(mediaItems);
+ assertThat(browserItems).hasSize(size);
+ for (int i = 0; i < size; ++i) {
+ assertThat(browserItems.get(i).getMediaId()).isEqualTo(mediaItems.get(i).mediaId);
+ }
+ }
+
+ @Test
+ public void convertBrowserItemListToMediaItemList() {
+ int size = 3;
+ List browserItems = MediaTestUtils.createBrowserItems(size);
+ List mediaItems = MediaUtils.convertBrowserItemListToMediaItemList(browserItems);
+ assertThat(mediaItems).hasSize(size);
+ for (int i = 0; i < size; ++i) {
+ assertThat(mediaItems.get(i).mediaId).isEqualTo(browserItems.get(i).getMediaId());
+ }
+ }
+
+ @Test
+ public void convertToMediaItemList_queueItemToMediaItem() {
+ int size = 3;
+ List queueItems = MediaTestUtils.createQueueItems(size);
+ List mediaItems = MediaUtils.convertQueueItemListToMediaItemList(queueItems);
+ assertThat(mediaItems).hasSize(queueItems.size());
+ for (int i = 0; i < size; ++i) {
+ assertThat(mediaItems.get(i).mediaId)
+ .isEqualTo(queueItems.get(i).getDescription().getMediaId());
+ }
+ }
+
+ @Test
+ public void convertToQueueItemList() {
+ int size = 3;
+ List mediaItems = MediaTestUtils.createConvergedMediaItems(size);
+ List queueItems = MediaUtils.convertToQueueItemList(mediaItems);
+ assertThat(queueItems).hasSize(mediaItems.size());
+ for (int i = 0; i < size; ++i) {
+ assertThat(queueItems.get(i).getDescription().getMediaId())
+ .isEqualTo(mediaItems.get(i).mediaId);
+ }
+ }
+
+ @Test
+ public void createMediaDescriptionCompat() {
+ String mediaId = "testId";
+ MediaDescriptionCompat descriptionCompat = MediaUtils.createMediaDescriptionCompat(null);
+ assertThat(descriptionCompat).isNull();
+
+ descriptionCompat = MediaUtils.createMediaDescriptionCompat(mediaId);
+ assertThat(descriptionCompat.getMediaId()).isEqualTo(mediaId);
+ }
+
+ @Test
+ public void convertToQueueItemId() {
+ assertThat(MediaUtils.convertToQueueItemId(C.INDEX_UNSET))
+ .isEqualTo(MediaSessionCompat.QueueItem.UNKNOWN_ID);
+ assertThat(MediaUtils.convertToQueueItemId(100)).isEqualTo(100);
+ }
+
+ @Test
+ public void truncateListBySize() {
+ List bundleList = new ArrayList<>();
+ Bundle testBundle = new Bundle();
+ testBundle.putString("key", "value");
+
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(testBundle, 0);
+ int bundleSize = p.dataSize();
+ p.recycle();
+
+ bundleList.addAll(Collections.nCopies(10, testBundle));
+
+ assertThat(MediaUtils.truncateListBySize(null, 1)).isNull();
+ for (int i = 0; i < 5; i++) {
+ assertThat(MediaUtils.truncateListBySize(bundleList, bundleSize * i + 1)).hasSize(i);
+ }
+ }
+
+ @Test
+ public void convertToMediaMetadata_withoutTitle() {
+ assertThat(MediaUtils.convertToMediaMetadata(null)).isEqualTo(MediaMetadata.EMPTY);
+ }
+
+ @Test
+ public void convertToMediaMetadata_withTitle() {
+ CharSequence title = "title";
+ assertThat(MediaUtils.convertToMediaMetadata(title).title).isEqualTo(title);
+ }
+
+ @Test
+ public void convertBetweenRatingAndRatingCompat() {
+ assertRatingEquals(MediaUtils.convertToRating(null), MediaUtils.convertToRatingCompat(null));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newUnratedRating(RatingCompat.RATING_NONE)),
+ MediaUtils.convertToRatingCompat(null));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newUnratedRating(RatingCompat.RATING_HEART)),
+ MediaUtils.convertToRatingCompat(new HeartRating()));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newHeartRating(true)),
+ MediaUtils.convertToRatingCompat(new HeartRating(true)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newThumbRating(false)),
+ MediaUtils.convertToRatingCompat(new ThumbRating(false)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newThumbRating(false)),
+ MediaUtils.convertToRatingCompat(new ThumbRating(false)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_3_STARS, 1f)),
+ MediaUtils.convertToRatingCompat(new StarRating(3, 1f)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_4_STARS, 0f)),
+ MediaUtils.convertToRatingCompat(new StarRating(4, 0f)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 5f)),
+ MediaUtils.convertToRatingCompat(new StarRating(5, 5f)));
+ assertRatingEquals(
+ MediaUtils.convertToRating(RatingCompat.newPercentageRating(80f)),
+ MediaUtils.convertToRatingCompat(new PercentageRating(80f)));
+ }
+
+ void assertRatingEquals(Rating rating, RatingCompat ratingCompat) {
+ if (rating == null && ratingCompat == null) {
+ return;
+ }
+ assertThat(rating.isRated()).isEqualTo(ratingCompat.isRated());
+ if (rating instanceof HeartRating) {
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_HEART);
+ assertThat(((HeartRating) rating).isHeart()).isEqualTo(ratingCompat.hasHeart());
+ } else if (rating instanceof ThumbRating) {
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_THUMB_UP_DOWN);
+ assertThat(((ThumbRating) rating).isThumbsUp()).isEqualTo(ratingCompat.isThumbUp());
+ } else if (rating instanceof StarRating) {
+ StarRating starRating = (StarRating) rating;
+ switch (starRating.getMaxStars()) {
+ case 3:
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_3_STARS);
+ break;
+ case 4:
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_4_STARS);
+ break;
+ case 5:
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_5_STARS);
+ break;
+ default: // fall out
+ }
+ assertThat(starRating.getStarRating()).isEqualTo(ratingCompat.getStarRating());
+ } else if (rating instanceof PercentageRating) {
+ assertThat(ratingCompat.getRatingStyle()).isEqualTo(RatingCompat.RATING_PERCENTAGE);
+ assertThat(((PercentageRating) rating).getPercent())
+ .isEqualTo(ratingCompat.getPercentRating());
+ }
+ }
+
+ @Test
+ public void convertToLibraryParams() {
+ assertThat(MediaUtils.convertToLibraryParams(context, null)).isNull();
+ Bundle rootHints = new Bundle();
+ rootHints.putString("key", "value");
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE, true);
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED, true);
+
+ MediaLibraryService.LibraryParams params =
+ MediaUtils.convertToLibraryParams(context, rootHints);
+ assertThat(params.offline).isTrue();
+ assertThat(params.recent).isTrue();
+ assertThat(params.suggested).isTrue();
+ assertThat(params.extras.getString("key")).isEqualTo("value");
+ }
+
+ @Test
+ public void convertToRootHints() {
+ assertThat(MediaUtils.convertToRootHints(null)).isNull();
+ Bundle extras = new Bundle();
+ extras.putString("key", "value");
+ MediaLibraryService.LibraryParams param =
+ new MediaLibraryService.LibraryParams.Builder()
+ .setOffline(true)
+ .setRecent(true)
+ .setSuggested(true)
+ .setExtras(extras)
+ .build();
+ Bundle rootHints = MediaUtils.convertToRootHints(param);
+ assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE)).isTrue();
+ assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT)).isTrue();
+ assertThat(rootHints.getBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED)).isTrue();
+ assertThat(rootHints.getString("key")).isEqualTo("value");
+ }
+
+ @Test
+ public void removeNullElements() {
+ List strings = new ArrayList<>();
+ strings.add("str1");
+ strings.add(null);
+ strings.add("str2");
+ strings.add(null);
+ assertThat(MediaUtils.removeNullElements(strings)).containsExactly("str1", "str2");
+ }
+
+ @Test
+ public void convertToSessionCommands() {
+ PlaybackStateCompat playbackState =
+ new PlaybackStateCompat.Builder()
+ .addCustomAction("action", "name", /* icon= */ 100)
+ .build();
+ SessionCommands sessionCommands = MediaUtils.convertToSessionCommands(playbackState);
+ assertThat(sessionCommands.contains(new SessionCommand("action", /* extras= */ null))).isTrue();
+ }
+
+ @Test
+ public void convertToPlayerCommands() {
+ long sessionFlags = FLAG_HANDLES_QUEUE_COMMANDS;
+ Player.Commands playerCommands = MediaUtils.convertToPlayerCommands(sessionFlags);
+ assertThat(playerCommands.contains(Player.COMMAND_GET_MEDIA_ITEMS)).isTrue();
+ }
+
+ @Test
+ public void convertToCustomLayout() {
+ assertThat(MediaUtils.convertToCustomLayout(null)).isEmpty();
+
+ String extraKey = "key";
+ String extraValue = "value";
+ String actionStr = "action";
+ String displayName = "display_name";
+ int iconRes = 21;
+
+ Bundle extras = new Bundle();
+ extras.putString(extraKey, extraValue);
+
+ PlaybackStateCompat.CustomAction action =
+ new PlaybackStateCompat.CustomAction.Builder(actionStr, displayName, iconRes)
+ .setExtras(extras)
+ .build();
+
+ PlaybackStateCompat state =
+ new PlaybackStateCompat.Builder()
+ .setState(
+ PlaybackStateCompat.STATE_NONE,
+ /* position= */ 0,
+ /* playbackSpeed= */ 1,
+ /* updateTime= */ 100)
+ .addCustomAction(action)
+ .build();
+
+ List buttons = MediaUtils.convertToCustomLayout(state);
+ assertThat(buttons).hasSize(1);
+ CommandButton button = buttons.get(0);
+ assertThat(button.displayName.toString()).isEqualTo(displayName);
+ assertThat(button.enabled).isTrue();
+ assertThat(button.iconResId).isEqualTo(iconRes);
+ assertThat(button.sessionCommand.customAction).isEqualTo(actionStr);
+ assertThat(button.sessionCommand.customExtras.getString(extraKey)).isEqualTo(extraValue);
+ }
+
+ @Test
+ public void convertToAudioAttributes() {
+ assertThat(MediaUtils.convertToAudioAttributes((AudioAttributesCompat) null))
+ .isSameInstanceAs(AudioAttributes.DEFAULT);
+ assertThat(MediaUtils.convertToAudioAttributes((MediaControllerCompat.PlaybackInfo) null))
+ .isSameInstanceAs(AudioAttributes.DEFAULT);
+
+ int contentType = AudioAttributesCompat.CONTENT_TYPE_MUSIC;
+ int flags = AudioAttributesCompat.FLAG_AUDIBILITY_ENFORCED;
+ int usage = AudioAttributesCompat.USAGE_MEDIA;
+ AudioAttributesCompat aaCompat =
+ new AudioAttributesCompat.Builder()
+ .setContentType(contentType)
+ .setFlags(flags)
+ .setUsage(usage)
+ .build();
+ AudioAttributes aa =
+ new AudioAttributes.Builder()
+ .setContentType(contentType)
+ .setFlags(flags)
+ .setUsage(usage)
+ .build();
+ assertThat(MediaUtils.convertToAudioAttributes(aaCompat)).isEqualTo(aa);
+ assertThat(MediaUtils.convertToAudioAttributesCompat(aa)).isEqualTo(aaCompat);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MockPlayerTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MockPlayerTest.java
new file mode 100644
index 0000000000..6573d55356
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/MockPlayerTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MockPlayer}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MockPlayerTest {
+
+ private MockPlayer player;
+
+ @Before
+ public void setUp() {
+ player = new MockPlayer.Builder().build();
+ }
+
+ @Test
+ public void play() {
+ player.play();
+ assertThat(player.playCalled).isTrue();
+ }
+
+ @Test
+ public void pause() {
+ player.pause();
+ assertThat(player.pauseCalled).isTrue();
+ }
+
+ @Test
+ public void prepare() {
+ player.prepare();
+ assertThat(player.prepareCalled).isTrue();
+ }
+
+ @Test
+ public void stop() {
+ player.stop();
+ assertThat(player.stopCalled).isTrue();
+ }
+
+ @Test
+ public void release() {
+ player.release();
+ assertThat(player.releaseCalled).isTrue();
+ }
+
+ @Test
+ public void setPlayWhenReady() {
+ boolean testPlayWhenReady = false;
+ player.setPlayWhenReady(testPlayWhenReady);
+ assertThat(player.setPlayWhenReadyCalled).isTrue();
+ }
+
+ @Test
+ public void seekTo() {
+ long pos = 1004L;
+ player.seekTo(pos);
+ assertThat(player.seekToCalled).isTrue();
+ assertThat(player.seekPositionMs).isEqualTo(pos);
+ }
+
+ @Test
+ public void setPlaybackParameters() {
+ PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f);
+ player.setPlaybackParameters(playbackParameters);
+ assertThat(player.setPlaybackParametersCalled).isTrue();
+ assertThat(player.playbackParameters).isEqualTo(playbackParameters);
+ }
+
+ @Test
+ public void setPlaybackSpeed() {
+ float speed = 1.5f;
+ player.setPlaybackSpeed(speed);
+ assertThat(player.setPlaybackSpeedCalled).isTrue();
+ assertThat(player.playbackParameters.speed).isEqualTo(speed);
+ }
+
+ @Test
+ public void setMediaItems() {
+ List list = MediaTestUtils.createConvergedMediaItems(/* size= */ 2);
+ player.setMediaItems(list);
+ assertThat(player.setMediaItemsCalled).isTrue();
+ assertThat(player.mediaItems).isEqualTo(list);
+ }
+
+ @Test
+ public void setMediaItems_withDuplicatedItems() {
+ List 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 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteControllerTestRule.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteControllerTestRule.java
new file mode 100644
index 0000000000..715bd53030
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteControllerTestRule.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.exoplayer2.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.rules.ExternalResource;
+
+/** TestRule for managing {@link RemoteMediaController} instances. */
+public final class RemoteControllerTestRule extends ExternalResource {
+
+ private static final String TAG = "RemoteControllerTestRule";
+
+ private Context context;
+ private final List 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;
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaBrowserCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaBrowserCompatTest.java
new file mode 100644
index 0000000000..c9e653e576
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaBrowserCompatTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.MOCK_MEDIA2_LIBRARY_SERVICE;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link RemoteMediaBrowserCompat}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteMediaBrowserCompatTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private RemoteMediaBrowserCompat remoteBrowserCompat;
+
+ @Before
+ public void setUp() throws Exception {
+ remoteBrowserCompat =
+ new RemoteMediaBrowserCompat(
+ ApplicationProvider.getApplicationContext(), MOCK_MEDIA2_LIBRARY_SERVICE);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ if (remoteBrowserCompat != null) {
+ remoteBrowserCompat.cleanUp();
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void connect() throws Exception {
+ remoteBrowserCompat.connect(/* waitForConnection= */ true);
+ assertThat(remoteBrowserCompat.isConnected()).isTrue();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaControllerCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaControllerCompatTest.java
new file mode 100644
index 0000000000..b8d68f2487
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaControllerCompatTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.TIMEOUT_MS;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.support.v4.media.session.MediaSessionCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.session.vct.common.HandlerThreadTestRule;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link RemoteMediaControllerCompat}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteMediaControllerCompatTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ @Rule
+ public final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("RemoteMediaControllerCompatTest");
+
+ private TestHandler handler;
+ private MediaSessionCompat sessionCompat;
+ private RemoteMediaControllerCompat remoteControllerCompat;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ handler = threadTestRule.getHandler();
+ handler.postAndSync(
+ () -> {
+ sessionCompat = new MediaSessionCompat(context, DEFAULT_TEST_NAME);
+ sessionCompat.setActive(true);
+ });
+ remoteControllerCompat =
+ new RemoteMediaControllerCompat(
+ context, sessionCompat.getSessionToken(), /* waitForConnection= */ true);
+ }
+
+ @After
+ public void cleanUp() {
+ sessionCompat.release();
+ remoteControllerCompat.cleanUp();
+ }
+
+ @Test
+ @SmallTest
+ public void play() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ sessionCompat.setCallback(
+ new MediaSessionCompat.Callback() {
+ @Override
+ public void onPlay() {
+ latch.countDown();
+ }
+ },
+ handler);
+
+ remoteControllerCompat.getTransportControls().play();
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionCompatTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionCompatTest.java
new file mode 100644
index 0000000000..209cb041be
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionCompatTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link RemoteMediaSessionCompat}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteMediaSessionCompatTest {
+
+ private Context context;
+ private RemoteMediaSessionCompat remoteSessionCompat;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ remoteSessionCompat = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ remoteSessionCompat.cleanUp();
+ }
+
+ @Test
+ @SmallTest
+ public void gettingToken() throws Exception {
+ MediaSessionCompat.Token token = remoteSessionCompat.getSessionToken();
+ assertThat(token).isNotNull();
+ }
+
+ @Test
+ @SmallTest
+ public void creatingControllerCompat() throws Exception {
+ MediaSessionCompat.Token token = remoteSessionCompat.getSessionToken();
+ assertThat(token).isNotNull();
+ MediaControllerCompat controller = new MediaControllerCompat(context, token);
+ assertThat(controller.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionTest.java
new file mode 100644
index 0000000000..6a7dfa1d5c
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/RemoteMediaSessionTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Bundle;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link RemoteMediaSession}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteMediaSessionTest {
+
+ private Context context;
+ private RemoteMediaSession remoteSession;
+ private Bundle tokenExtras;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ tokenExtras = TestUtils.createTestBundle();
+ remoteSession = new RemoteMediaSession(DEFAULT_TEST_NAME, context, tokenExtras);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ if (remoteSession != null) {
+ remoteSession.cleanUp();
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void gettingToken() throws Exception {
+ SessionToken token = remoteSession.getToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getPackageName()).isEqualTo(SUPPORT_APP_PACKAGE_NAME);
+ assertThat(TestUtils.equals(tokenExtras, token.getExtras())).isTrue();
+ }
+
+ @Test
+ @SmallTest
+ public void creatingController() throws Exception {
+ SessionToken token = remoteSession.getToken();
+ assertThat(token).isNotNull();
+ MediaController controller =
+ new MediaController.Builder(context)
+ .setSessionToken(token)
+ .setControllerCallback(new MediaController.ControllerCallback() {})
+ .build();
+ assertThat(controller).isNotNull();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionCommandTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionCommandTest.java
new file mode 100644
index 0000000000..513bd3b078
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionCommandTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SessionCommand} and {@link SessionCommands}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SessionCommandTest {
+ // Prefix for all command codes
+ private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
+
+ private static final List 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 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 fields = getSessionCommandsFields(PREFIX_COMMAND_CODE);
+ Set 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 fields = getSessionCommandsFields(PREFIX_COMMAND_CODES.get(i));
+ List 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 getSessionCommandsFields(String prefix) {
+ List 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);
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionTokenTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionTokenTest.java
new file mode 100644
index 0000000000..7b1912bf6e
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/SessionTokenTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Process;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import com.google.android.exoplayer2.session.vct.common.MainLooperTestRule;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SessionToken}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SessionTokenTest {
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private Context context;
+ private List 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();
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/TestBrowserCallbackTest.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/TestBrowserCallbackTest.java
new file mode 100644
index 0000000000..46c971efe5
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/androidTest/java/com/google/android/exoplayer2/session/TestBrowserCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link TestBrowserCallback}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TestBrowserCallbackTest {
+
+ /**
+ * Test if the {@link TestBrowserCallback} wraps the callback proxy without missing any method.
+ */
+ @Test
+ public void methods_overridden() {
+ Method[] methods = TestBrowserCallback.class.getMethods();
+ assertThat(methods).isNotNull();
+ for (Method method : methods) {
+ // For any methods in the controller callback, TestBrowserCallback should have
+ // overridden the method and call matching API in the callback proxy.
+ assertWithMessage(
+ "TestBrowserCallback should override " + method + " and call callback proxy")
+ .that(method.getDeclaringClass())
+ .isNotEqualTo(MediaBrowser.BrowserCallback.class);
+ assertWithMessage(
+ "TestBrowserCallback should override " + method + " and call callback proxy")
+ .that(method.getDeclaringClass())
+ .isNotEqualTo(MediaController.ControllerCallback.class);
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/AndroidManifest.xml b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..3c11436c8c
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/AndroidManifest.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaBrowserServiceCompat.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaBrowserServiceCompat.java
new file mode 100644
index 0000000000..ac8fdf0972
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaBrowserServiceCompat.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+/** {@link MockMediaBrowserServiceCompat} running on a local process. */
+public class LocalMockMediaBrowserServiceCompat extends MockMediaBrowserServiceCompat {}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaSessionService.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaSessionService.java
new file mode 100644
index 0000000000..0c0206cb60
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/LocalMockMediaSessionService.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.session;
+
+/** {@link MockMediaSessionService} running on a local process. */
+public class LocalMockMediaSessionService extends MockMediaSessionService {}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaBrowserCompatProviderService.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaBrowserCompatProviderService.java
new file mode 100644
index 0000000000..d800f343d1
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaBrowserCompatProviderService.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserCompat.ConnectionCallback;
+import android.support.v4.media.MediaBrowserCompat.ItemCallback;
+import android.support.v4.media.MediaBrowserCompat.SearchCallback;
+import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
+import com.google.android.exoplayer2.session.vct.common.IRemoteMediaBrowserCompat;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * A Service that creates {@link MediaBrowserCompat} and calls its methods according to the service
+ * app's requests.
+ */
+public class MediaBrowserCompatProviderService extends Service {
+ private static final String TAG = "MediaBrowserCompatProviderService";
+
+ Map mediaBrowserCompatMap = new HashMap<>();
+ Map 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();
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerCompatProviderService.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerCompatProviderService.java
new file mode 100644
index 0000000000..9bba122c11
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerCompatProviderService.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.KEY_ARGUMENTS;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import com.google.android.exoplayer2.session.vct.common.IRemoteMediaControllerCompat;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * A Service that creates {@link MediaControllerCompat} and calls its methods according to the
+ * service app's requests.
+ */
+public class MediaControllerCompatProviderService extends Service {
+ private static final String TAG = "MediaControllerCompatProviderService";
+
+ Map mediaControllerCompatMap = new HashMap<>();
+ RemoteMediaControllerCompatStub binder;
+
+ TestHandler handler;
+ Executor executor;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ binder = new RemoteMediaControllerCompatStub();
+
+ handler = new TestHandler(getMainLooper());
+ executor = handler::post;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (ACTION_MEDIA_CONTROLLER_COMPAT.equals(intent.getAction())) {
+ return binder;
+ }
+ return null;
+ }
+
+ private class RemoteMediaControllerCompatStub extends IRemoteMediaControllerCompat.Stub {
+
+ @Override
+ public void create(String controllerId, Bundle tokenBundle, boolean waitForConnection) {
+ MediaSessionCompat.Token token = (MediaSessionCompat.Token) getParcelable(tokenBundle);
+ MediaControllerCompat controller =
+ new MediaControllerCompat(MediaControllerCompatProviderService.this, token);
+
+ TestControllerCallback callback = new TestControllerCallback();
+ controller.registerCallback(callback, handler);
+
+ mediaControllerCompatMap.put(controllerId, controller);
+
+ if (!waitForConnection) {
+ return;
+ }
+
+ boolean connected = false;
+ try {
+ connected = callback.connectionLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException occurred while waiting for connection", e);
+ }
+
+ if (!connected) {
+ Log.e(TAG, "Could not connect to the given session.");
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // MediaControllerCompat methods
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void addQueueItem(String controllerId, Bundle descriptionBundle) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
+ controller.addQueueItem(desc);
+ }
+
+ @Override
+ public void addQueueItemWithIndex(String controllerId, Bundle descriptionBundle, int index)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
+ controller.addQueueItem(desc, index);
+ }
+
+ @Override
+ public void removeQueueItem(String controllerId, Bundle descriptionBundle)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ MediaDescriptionCompat desc = (MediaDescriptionCompat) getParcelable(descriptionBundle);
+ controller.removeQueueItem(desc);
+ }
+
+ @Override
+ public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.setVolumeTo(value, flags);
+ }
+
+ @Override
+ public void adjustVolume(String controllerId, int direction, int flags) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.adjustVolume(direction, flags);
+ }
+
+ @Override
+ public void sendCommand(String controllerId, String command, Bundle params, ResultReceiver cb)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.sendCommand(command, params, cb);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // MediaControllerCompat.TransportControls methods
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void prepare(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().prepare();
+ }
+
+ @Override
+ public void prepareFromMediaId(String controllerId, String mediaId, Bundle extras)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().prepareFromMediaId(mediaId, extras);
+ }
+
+ @Override
+ public void prepareFromSearch(String controllerId, String query, Bundle extras)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().prepareFromSearch(query, extras);
+ }
+
+ @Override
+ public void prepareFromUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().prepareFromUri(uri, extras);
+ }
+
+ @Override
+ public void play(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().play();
+ }
+
+ @Override
+ public void playFromMediaId(String controllerId, String mediaId, Bundle extras)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().playFromMediaId(mediaId, extras);
+ }
+
+ @Override
+ public void playFromSearch(String controllerId, String query, Bundle extras)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().playFromSearch(query, extras);
+ }
+
+ @Override
+ public void playFromUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().playFromUri(uri, extras);
+ }
+
+ @Override
+ public void skipToQueueItem(String controllerId, long id) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().skipToQueueItem(id);
+ }
+
+ @Override
+ public void pause(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().pause();
+ }
+
+ @Override
+ public void stop(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().stop();
+ }
+
+ @Override
+ public void seekTo(String controllerId, long pos) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().seekTo(pos);
+ }
+
+ @Override
+ public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().setPlaybackSpeed(speed);
+ }
+
+ @Override
+ public void skipToNext(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().skipToNext();
+ }
+
+ @Override
+ public void skipToPrevious(String controllerId) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().skipToPrevious();
+ }
+
+ @Override
+ public void setRating(String controllerId, Bundle ratingBundle) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
+ controller.getTransportControls().setRating(rating);
+ }
+
+ @Override
+ public void setRatingWithExtras(String controllerId, Bundle ratingBundle, Bundle extras)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ RatingCompat rating = (RatingCompat) getParcelable(ratingBundle);
+ controller.getTransportControls().setRating(rating, extras);
+ }
+
+ @Override
+ public void setCaptioningEnabled(String controllerId, boolean enabled) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().setCaptioningEnabled(enabled);
+ }
+
+ @Override
+ public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().setRepeatMode(repeatMode);
+ }
+
+ @Override
+ public void setShuffleMode(String controllerId, int shuffleMode) throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().setShuffleMode(shuffleMode);
+ }
+
+ @Override
+ public void sendCustomAction(String controllerId, Bundle customActionBundle, Bundle args)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ PlaybackStateCompat.CustomAction customAction =
+ (PlaybackStateCompat.CustomAction) getParcelable(customActionBundle);
+ controller.getTransportControls().sendCustomAction(customAction, args);
+ }
+
+ @Override
+ public void sendCustomActionWithName(String controllerId, String action, Bundle args)
+ throws RemoteException {
+ MediaControllerCompat controller = mediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().sendCustomAction(action, args);
+ }
+
+ private Parcelable getParcelable(Bundle bundle) {
+ bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
+ return bundle.getParcelable(KEY_ARGUMENTS);
+ }
+ }
+
+ private static class TestControllerCallback extends MediaControllerCompat.Callback {
+ private final CountDownLatch connectionLatch = new CountDownLatch(1);
+
+ @Override
+ public void onSessionReady() {
+ super.onSessionReady();
+ connectionLatch.countDown();
+ }
+ }
+}
diff --git a/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerProviderService.java b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerProviderService.java
new file mode 100644
index 0000000000..d4a8d2cc34
--- /dev/null
+++ b/google3/third_party/java_src/android_libs/media/libraries/test_session_current/src/main/java/com/google/android/exoplayer2/session/MediaControllerProviderService.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.session;
+
+import static com.google.android.exoplayer2.session.vct.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
+import static com.google.android.exoplayer2.session.vct.common.TestUtils.SERVICE_CONNECTION_TIMEOUT_MS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import androidx.annotation.NonNull;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Rating;
+import com.google.android.exoplayer2.session.vct.common.IRemoteMediaController;
+import com.google.android.exoplayer2.session.vct.common.TestHandler;
+import com.google.android.exoplayer2.session.vct.common.TestUtils;
+import com.google.android.exoplayer2.util.Log;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A Service that creates {@link MediaController} and calls its methods according to the service
+ * app's requests.
+ */
+public class MediaControllerProviderService extends Service {
+ private static final String TAG = "MediaControllerProviderService";
+
+ Map mediaControllerMap = new HashMap<>();
+ RemoteMediaControllerStub binder;
+
+ TestHandler handler;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ binder = new RemoteMediaControllerStub();
+
+ handler = new TestHandler(getMainLooper());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (ACTION_MEDIA2_CONTROLLER.equals(intent.getAction())) {
+ return binder;
+ }
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ for (MediaController controller : mediaControllerMap.values()) {
+ try {
+ handler.postAndSync(controller::release);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in releasing controller", e);
+ }
+ }
+ }
+
+ private class RemoteMediaControllerStub extends IRemoteMediaController.Stub {
+
+ private void runOnHandler(@NonNull TestHandler.TestRunnable runnable) throws RemoteException {
+ try {
+ handler.postAndSync(runnable);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception thrown while waiting for handler", e);
+ throw new RemoteException("Unexpected exception");
+ }
+ }
+
+ private V runOnHandler(@NonNull Callable callable) throws RemoteException {
+ try {
+ return handler.postAndSync(callable);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception thrown while waiting for handler", e);
+ throw new RemoteException("Unexpected exception");
+ }
+ }
+
+ @Override
+ public void create(
+ boolean isBrowser,
+ String controllerId,
+ Bundle tokenBundle,
+ Bundle connectionHints,
+ boolean waitForConnection)
+ throws RemoteException {
+ SessionToken token = SessionToken.CREATOR.fromBundle(tokenBundle);
+ TestControllerCallback callback = new TestControllerCallback();
+
+ runOnHandler(
+ () -> {
+ Context context = MediaControllerProviderService.this;
+ MediaController controller;
+ if (isBrowser) {
+ MediaBrowser.Builder builder =
+ new MediaBrowser.Builder(context)
+ .setSessionToken(token)
+ .setControllerCallback(callback);
+ if (connectionHints != null) {
+ builder.setConnectionHints(connectionHints);
+ }
+ controller = builder.build();
+ } else {
+ MediaController.Builder builder =
+ new MediaController.Builder(context)
+ .setSessionToken(token)
+ .setControllerCallback(callback);
+ if (connectionHints != null) {
+ builder.setConnectionHints(connectionHints);
+ }
+ controller = builder.build();
+ }
+ mediaControllerMap.put(controllerId, controller);
+ });
+
+ if (!waitForConnection) {
+ return;
+ }
+
+ boolean connected = false;
+ try {
+ connected = callback.connectionLatch.await(SERVICE_CONNECTION_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException occurred while waiting for connection", e);
+ }
+
+ if (!connected) {
+ Log.e(TAG, "Could not connect to the given session.");
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // MediaController methods
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public Bundle getConnectedSessionToken(String controllerId) throws RemoteException {
+ return runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ return BundleableUtils.toNullableBundle(controller.getConnectedToken());
+ });
+ }
+
+ @Override
+ public void play(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.play();
+ });
+ }
+
+ @Override
+ public void pause(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.pause();
+ });
+ }
+
+ @Override
+ public void setPlayWhenReady(String controllerId, boolean playWhenReady)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setPlayWhenReady(playWhenReady);
+ });
+ }
+
+ @Override
+ public void prepare(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.prepare();
+ });
+ }
+
+ @Override
+ public void seekToDefaultPosition(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.seekToDefaultPosition();
+ });
+ }
+
+ @Override
+ public void seekToDefaultPositionWithWindowIndex(String controllerId, int windowIndex)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.seekToDefaultPosition(windowIndex);
+ });
+ }
+
+ @Override
+ public void seekTo(String controllerId, long positionMs) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.seekTo(positionMs);
+ });
+ }
+
+ @Override
+ public void seekToWithWindowIndex(String controllerId, int windowIndex, long positionMs)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.seekTo(windowIndex, positionMs);
+ });
+ }
+
+ @Override
+ public void setPlaybackParameters(String controllerId, Bundle playbackParametersBundle)
+ throws RemoteException {
+ PlaybackParameters playbackParameters =
+ PlaybackParameters.CREATOR.fromBundle(playbackParametersBundle);
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setPlaybackParameters(playbackParameters);
+ });
+ }
+
+ @Override
+ public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setPlaybackSpeed(speed);
+ });
+ }
+
+ @Override
+ public void setMediaItems1(
+ String controllerId, List mediaItemBundles, boolean resetPosition)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setMediaItems(
+ BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles), resetPosition);
+ });
+ }
+
+ @Override
+ public void setMediaItems2(
+ String controllerId,
+ List mediaItemBundles,
+ int startWindowIndex,
+ long startPositionMs)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setMediaItems(
+ BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles),
+ startWindowIndex,
+ startPositionMs);
+ });
+ }
+
+ @Override
+ public void createAndSetFakeMediaItems(String controllerId, int size) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ List itemList = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ // Make media ID of each item same with its index.
+ String mediaId = TestUtils.getMediaIdInFakeTimeline(i);
+ itemList.add(MediaTestUtils.createConvergedMediaItem(mediaId));
+ }
+ controller.setMediaItems(itemList);
+ });
+ }
+
+ @Override
+ @SuppressWarnings("FutureReturnValueIgnored")
+ public void setMediaUri(String controllerId, Uri uri, Bundle extras) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setMediaUri(uri, extras);
+ });
+ }
+
+ @Override
+ public void setPlaylistMetadata(String controllerId, Bundle playlistMetadataBundle)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setPlaylistMetadata(
+ MediaMetadata.CREATOR.fromBundle(playlistMetadataBundle));
+ });
+ }
+
+ @Override
+ public void addMediaItems(String controllerId, int index, List mediaItemBundles)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.addMediaItems(
+ index, BundleableUtils.fromBundleList(MediaItem.CREATOR, mediaItemBundles));
+ });
+ }
+
+ @Override
+ public void removeMediaItems(String controllerId, int fromIndex, int toIndex)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.removeMediaItems(fromIndex, toIndex);
+ });
+ }
+
+ @Override
+ public void moveMediaItems(String controllerId, int fromIndex, int toIndex, int newIndex)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.moveMediaItems(fromIndex, toIndex, newIndex);
+ });
+ }
+
+ @Override
+ public void previous(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.previous();
+ });
+ }
+
+ @Override
+ public void next(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.next();
+ });
+ }
+
+ @Override
+ public void setShuffleModeEnabled(String controllerId, boolean shuffleModeEnabled)
+ throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setShuffleModeEnabled(shuffleModeEnabled);
+ });
+ }
+
+ @Override
+ public void setRepeatMode(String controllerId, int repeatMode) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setRepeatMode(repeatMode);
+ });
+ }
+
+ @Override
+ public void setVolumeTo(String controllerId, int value, int flags) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setDeviceVolume(value);
+ });
+ }
+
+ @Override
+ public void adjustVolume(String controllerId, int direction, int flags) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ switch (direction) {
+ case AudioManager.ADJUST_RAISE:
+ controller.increaseDeviceVolume();
+ break;
+ case AudioManager.ADJUST_LOWER:
+ controller.decreaseDeviceVolume();
+ break;
+ case AudioManager.ADJUST_MUTE:
+ controller.setDeviceMuted(true);
+ break;
+ case AudioManager.ADJUST_UNMUTE:
+ controller.setDeviceMuted(false);
+ break;
+ case AudioManager.ADJUST_TOGGLE_MUTE:
+ controller.setDeviceMuted(controller.isDeviceMuted());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ });
+ }
+
+ @Override
+ public Bundle sendCustomCommand(String controllerId, Bundle command, Bundle args)
+ throws RemoteException {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ Future future =
+ controller.sendCustomCommand(SessionCommand.CREATOR.fromBundle(command), args);
+ SessionResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle setRating(String controllerId, String mediaId, Bundle rating)
+ throws RemoteException {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ Future future =
+ controller.setRating(mediaId, Rating.CREATOR.fromBundle(rating));
+ SessionResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public void setVolume(String controllerId, float volume) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setVolume(volume);
+ });
+ }
+
+ @Override
+ public void setDeviceVolume(String controllerId, int volume) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setDeviceVolume(volume);
+ });
+ }
+
+ @Override
+ public void increaseDeviceVolume(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.increaseDeviceVolume();
+ });
+ }
+
+ @Override
+ public void decreaseDeviceVolume(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.decreaseDeviceVolume();
+ });
+ }
+
+ @Override
+ public void setDeviceMuted(String controllerId, boolean muted) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.setDeviceMuted(muted);
+ });
+ }
+
+ @Override
+ public void release(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.release();
+ });
+ }
+
+ @Override
+ public void stop(String controllerId) throws RemoteException {
+ runOnHandler(
+ () -> {
+ MediaController controller = mediaControllerMap.get(controllerId);
+ controller.stop();
+ });
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // MediaBrowser methods
+ ////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public Bundle getLibraryRoot(String controllerId, Bundle libraryParams) throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future =
+ browser.getLibraryRoot(
+ BundleableUtils.fromNullableBundle(
+ MediaLibraryService.LibraryParams.CREATOR, libraryParams));
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle subscribe(String controllerId, String parentId, Bundle libraryParams)
+ throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future =
+ browser.subscribe(
+ parentId,
+ BundleableUtils.fromNullableBundle(
+ MediaLibraryService.LibraryParams.CREATOR, libraryParams));
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle unsubscribe(String controllerId, String parentId) throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future = browser.unsubscribe(parentId);
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle getChildren(
+ String controllerId, String parentId, int page, int pageSize, Bundle libraryParams)
+ throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future =
+ browser.getChildren(
+ parentId,
+ page,
+ pageSize,
+ BundleableUtils.fromNullableBundle(
+ MediaLibraryService.LibraryParams.CREATOR, libraryParams));
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle getItem(String controllerId, String mediaId) throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future = browser.getItem(mediaId);
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle search(String controllerId, String query, Bundle libraryParams)
+ throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future =
+ browser.search(
+ query,
+ BundleableUtils.fromNullableBundle(
+ MediaLibraryService.LibraryParams.CREATOR, libraryParams));
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ @Override
+ public Bundle getSearchResult(
+ String controllerId, String query, int page, int pageSize, Bundle libraryParams)
+ throws RemoteException {
+ MediaBrowser browser = (MediaBrowser) mediaControllerMap.get(controllerId);
+ Future future =
+ browser.getSearchResult(
+ query,
+ page,
+ pageSize,
+ BundleableUtils.fromNullableBundle(
+ MediaLibraryService.LibraryParams.CREATOR, libraryParams));
+ LibraryResult result = getFutureResult(future);
+ return result.toBundle();
+ }
+
+ private final class TestControllerCallback implements MediaBrowser.BrowserCallback {
+
+ private final CountDownLatch connectionLatch = new CountDownLatch(1);
+
+ @Override
+ public void onConnected(MediaController controller) {
+ connectionLatch.countDown();
+ }
+ }
+ }
+
+ private static T getFutureResult(Future