| /* |
| * 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 android.support.mediacompat.client; |
| |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_NOTIFY_CHILDREN_CHANGED; |
| import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN; |
| import static android.support.mediacompat.testlib.VersionConstants.KEY_SERVICE_VERSION; |
| import static android.support.mediacompat.testlib.VersionConstants.VERSION_TOT; |
| import static android.support.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME; |
| import static android.support.mediacompat.testlib.util.IntentUtil.callMediaBrowserServiceMethod; |
| |
| import static androidx.test.core.app.ApplicationProvider.getApplicationContext; |
| import static androidx.test.platform.app.InstrumentationRegistry.getArguments; |
| import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.media.MediaDescription; |
| import android.media.browse.MediaBrowser; |
| import android.media.browse.MediaBrowser.MediaItem; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.service.media.MediaBrowserService; |
| import android.support.mediacompat.testlib.util.PollingCheck; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.RequiresApi; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.filters.SdkSuppress; |
| import androidx.test.filters.SmallTest; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Test connection between framework {@link MediaBrowser} and |
| * {@link androidx.media.MediaBrowserServiceCompat}. |
| * |
| * TODO: Lower the minSdkVersion of this test to LOLLIPOP. |
| */ |
| @RunWith(AndroidJUnit4.class) |
| @SdkSuppress(minSdkVersion = 21) |
| public class MediaBrowserTest { |
| |
| private static final String TAG = "MediaBrowserTest"; |
| |
| // The maximum time to wait for an operation. |
| private static final long TIME_OUT_MS = 3000L; |
| private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L; |
| |
| /** |
| * To check {@link MediaBrowser#unsubscribe} works properly, |
| * we notify to the browser after the unsubscription that the media items have changed. |
| * Then {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} should not be called. |
| * |
| * The measured time from calling {@link MediaBrowserService#notifyChildrenChanged} |
| * to {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} being called is about |
| * 50ms. |
| * So we make the thread sleep for 100ms to properly check that the callback is not called. |
| */ |
| private static final long SLEEP_MS = 100L; |
| private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName( |
| SERVICE_PACKAGE_NAME, |
| "android.support.mediacompat.service.StubMediaBrowserServiceCompat"); |
| private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION = |
| new ComponentName( |
| SERVICE_PACKAGE_NAME, |
| "android.support.mediacompat.service" |
| + ".StubMediaBrowserServiceCompatWithDelayedMediaSession"); |
| private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName( |
| "invalid.package", "invalid.ServiceClassName"); |
| |
| private String mServiceVersion; |
| private MediaBrowser mMediaBrowser; |
| private StubConnectionCallback mConnectionCallback; |
| private StubSubscriptionCallback mSubscriptionCallback; |
| private Bundle mRootHints; |
| |
| @Before |
| public void setUp() { |
| // The version of the service app is provided through the instrumentation arguments. |
| mServiceVersion = getArguments().getString(KEY_SERVICE_VERSION, ""); |
| Log.d(TAG, "Service app version: " + mServiceVersion); |
| |
| mConnectionCallback = new StubConnectionCallback(); |
| mSubscriptionCallback = new StubSubscriptionCallback(); |
| |
| mRootHints = new Bundle(); |
| mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); |
| mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE, true); |
| mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED, true); |
| |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(), |
| TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints); |
| } |
| }); |
| } |
| |
| @After |
| public void tearDown() { |
| if (mMediaBrowser != null && mMediaBrowser.isConnected()) { |
| mMediaBrowser.disconnect(); |
| } |
| } |
| |
| @Test |
| @SmallTest |
| public void testBrowserRoot() { |
| final String id = "test-id"; |
| final String key = "test-key"; |
| final String val = "test-val"; |
| final Bundle extras = new Bundle(); |
| extras.putString(key, val); |
| |
| MediaBrowserService.BrowserRoot browserRoot = |
| new MediaBrowserService.BrowserRoot(id, extras); |
| assertEquals(id, browserRoot.getRootId()); |
| assertEquals(val, browserRoot.getExtras().getString(key)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testMediaBrowser() throws Exception { |
| assertFalse(mMediaBrowser.isConnected()); |
| |
| connectMediaBrowserService(); |
| assertTrue(mMediaBrowser.isConnected()); |
| |
| assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent()); |
| assertEquals(MEDIA_ID_ROOT, mMediaBrowser.getRoot()); |
| assertEquals(EXTRAS_VALUE, mMediaBrowser.getExtras().getString(EXTRAS_KEY)); |
| |
| mMediaBrowser.disconnect(); |
| new PollingCheck(TIME_OUT_MS) { |
| @Override |
| protected boolean check() { |
| return !mMediaBrowser.isConnected(); |
| } |
| }.run(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testGetServiceComponentBeforeConnection() { |
| try { |
| ComponentName serviceComponent = mMediaBrowser.getServiceComponent(); |
| fail(); |
| } catch (IllegalStateException e) { |
| // expected |
| } |
| } |
| |
| @Test |
| @SmallTest |
| public void testConnectionFailed() throws Exception { |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(), |
| TEST_INVALID_BROWSER_SERVICE, mConnectionCallback, mRootHints); |
| } |
| }); |
| |
| synchronized (mConnectionCallback.mWaitLock) { |
| mMediaBrowser.connect(); |
| mConnectionCallback.mWaitLock.wait(TIME_OUT_MS); |
| } |
| assertEquals(1, mConnectionCallback.mConnectionFailedCount); |
| assertEquals(0, mConnectionCallback.mConnectedCount); |
| assertEquals(0, mConnectionCallback.mConnectionSuspendedCount); |
| } |
| |
| @Test |
| @SmallTest |
| public void testConnectTwice() throws Exception { |
| connectMediaBrowserService(); |
| try { |
| mMediaBrowser.connect(); |
| fail(); |
| } catch (IllegalStateException e) { |
| // expected |
| } |
| } |
| |
| @Test |
| @MediumTest |
| @SdkSuppress(minSdkVersion = 23) |
| public void testReconnection() throws Exception { |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mMediaBrowser.connect(); |
| // Reconnect before the first connection was established. |
| mMediaBrowser.disconnect(); |
| mMediaBrowser.connect(); |
| } |
| }); |
| |
| synchronized (mConnectionCallback.mWaitLock) { |
| mConnectionCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertEquals(1, mConnectionCallback.mConnectedCount); |
| } |
| |
| // Test subscribe. |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback); |
| mSubscriptionCallback.await(TIME_OUT_MS); |
| assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); |
| |
| StubItemCallback itemCallback = new StubItemCallback(); |
| synchronized (itemCallback.mWaitLock) { |
| // Test getItem. |
| itemCallback.reset(); |
| mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], itemCallback); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertEquals(MEDIA_ID_CHILDREN[0], itemCallback.mLastMediaItem.getMediaId()); |
| } |
| |
| // Reconnect after connection was established. |
| mMediaBrowser.disconnect(); |
| connectMediaBrowserService(); |
| |
| synchronized (itemCallback.mWaitLock) { |
| // Test getItem. |
| itemCallback.reset(); |
| mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], itemCallback); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertEquals(MEDIA_ID_CHILDREN[0], itemCallback.mLastMediaItem.getMediaId()); |
| } |
| } |
| |
| @Test |
| @MediumTest |
| public void testConnectionCallbackNotCalledAfterDisconnect() { |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mMediaBrowser.connect(); |
| mMediaBrowser.disconnect(); |
| mConnectionCallback.reset(); |
| } |
| }); |
| |
| try { |
| Thread.sleep(SLEEP_MS); |
| } catch (InterruptedException e) { |
| fail("Unexpected InterruptedException occurred."); |
| } |
| assertEquals(0, mConnectionCallback.mConnectedCount); |
| assertEquals(0, mConnectionCallback.mConnectionFailedCount); |
| assertEquals(0, mConnectionCallback.mConnectionSuspendedCount); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMultipleConnections() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| final StubConnectionCallback callback1 = new StubConnectionCallback(); |
| final StubConnectionCallback callback2 = new StubConnectionCallback(); |
| final StubConnectionCallback callback3 = new StubConnectionCallback(); |
| final List<MediaBrowser> browserList = new ArrayList<>(); |
| |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| MediaBrowser browser1 = new MediaBrowser(context, TEST_BROWSER_SERVICE, |
| callback1, new Bundle()); |
| MediaBrowser browser2 = new MediaBrowser(context, TEST_BROWSER_SERVICE, |
| callback2, new Bundle()); |
| MediaBrowser browser3 = new MediaBrowser(context, TEST_BROWSER_SERVICE, |
| callback3, new Bundle()); |
| |
| browserList.add(browser1); |
| browserList.add(browser2); |
| browserList.add(browser3); |
| |
| browser1.connect(); |
| browser2.connect(); |
| browser3.connect(); |
| } |
| }); |
| |
| try { |
| new PollingCheck(TIME_OUT_MS) { |
| @Override |
| protected boolean check() { |
| return callback1.mConnectedCount == 1 |
| && callback2.mConnectedCount == 1 |
| && callback3.mConnectedCount == 1; |
| } |
| }.run(); |
| } finally { |
| for (int i = 0; i < browserList.size(); i++) { |
| MediaBrowser browser = browserList.get(i); |
| if (browser.isConnected()) { |
| browser.disconnect(); |
| } |
| } |
| } |
| } |
| |
| @Test |
| @MediumTest |
| public void testSubscribe() throws Exception { |
| connectMediaBrowserService(); |
| |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback); |
| mSubscriptionCallback.await(TIME_OUT_MS); |
| assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); |
| assertEquals(MEDIA_ID_CHILDREN.length, mSubscriptionCallback.mLastChildMediaItems.size()); |
| for (int i = 0; i < MEDIA_ID_CHILDREN.length; ++i) { |
| assertEquals(MEDIA_ID_CHILDREN[i], |
| mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId()); |
| } |
| |
| // Test MediaBrowserService.notifyChildrenChanged() |
| mSubscriptionCallback.reset(1); |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| mSubscriptionCallback.await(TIME_OUT_MS); |
| assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| |
| // Test unsubscribe. |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.unsubscribe(MEDIA_ID_ROOT); |
| |
| // After unsubscribing, make StubMediaBrowserService notify that the children are |
| // changed. |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS); |
| |
| // onChildrenLoaded should not be called. |
| assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| } |
| |
| @Test |
| @MediumTest |
| @SdkSuppress(minSdkVersion = 26) |
| public void testSubscribeWithOptions() throws Exception { |
| connectMediaBrowserService(); |
| final int pageSize = 3; |
| final int lastPage = (MEDIA_ID_CHILDREN.length - 1) / pageSize; |
| Bundle options = new Bundle(); |
| options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); |
| |
| for (int page = 0; page <= lastPage; ++page) { |
| mSubscriptionCallback.reset(1); |
| options.putInt(MediaBrowser.EXTRA_PAGE, page); |
| mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback); |
| assertTrue(mSubscriptionCallback.await(TIME_OUT_MS)); |
| assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount.get()); |
| assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); |
| if (page != lastPage) { |
| assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size()); |
| } else { |
| assertEquals((MEDIA_ID_CHILDREN.length - 1) % pageSize + 1, |
| mSubscriptionCallback.mLastChildMediaItems.size()); |
| } |
| // Check whether all the items in the current page are loaded. |
| for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) { |
| assertEquals(MEDIA_ID_CHILDREN[page * pageSize + i], |
| mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId()); |
| } |
| |
| // Test MediaBrowserService.notifyChildrenChanged() |
| mSubscriptionCallback.reset(page + 1); |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| assertTrue(mSubscriptionCallback.await(TIME_OUT_MS * (page + 1))); |
| assertEquals(page + 1, mSubscriptionCallback.mChildrenLoadedWithOptionCount.get()); |
| } |
| |
| // Test unsubscribe with callback argument. |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback); |
| |
| // After unsubscribing, make StubMediaBrowserService notify that the children are |
| // changed. |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| try { |
| Thread.sleep(SLEEP_MS); |
| } catch (InterruptedException e) { |
| fail("Unexpected InterruptedException occurred."); |
| } |
| // onChildrenLoaded should not be called. |
| assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testSubscribeDelayedItems() throws Exception { |
| connectMediaBrowserService(); |
| |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_CHILDREN_DELAYED, mSubscriptionCallback); |
| assertFalse(mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS)); |
| assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| |
| callMediaBrowserServiceMethod( |
| SEND_DELAYED_NOTIFY_CHILDREN_CHANGED, MEDIA_ID_CHILDREN_DELAYED, |
| getApplicationContext()); |
| assertTrue(mSubscriptionCallback.await(TIME_OUT_MS)); |
| assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount.get()); |
| } |
| |
| @Test |
| @SmallTest |
| @SdkSuppress(minSdkVersion = 21) |
| public void testSubscribeInvalidItem() throws Exception { |
| // TODO: Remove this when the 'previous' service version contains the fix in |
| // http://r.android.com/2794937. |
| assumeTrue(Build.VERSION.SDK_INT >= 24 || mServiceVersion.equals(VERSION_TOT)); |
| |
| connectMediaBrowserService(); |
| |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback); |
| mSubscriptionCallback.await(TIME_OUT_MS); |
| if (Build.VERSION.SDK_INT < 24) { |
| // There's no way to communicate an invalid media ID on API < 24 because the documented |
| // way of emitting children = null from MediaBrowserService.onLoadChildren throws an |
| // exception from inside MediaBrowserService (b/19127753). Therefore we return an empty |
| // list of children, which is technically incorrect but avoids the exception. |
| assertEquals(Collections.emptyList(), mSubscriptionCallback.mLastChildMediaItems); |
| } else { |
| assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId); |
| } |
| } |
| |
| @Test |
| @SmallTest |
| @SdkSuppress(minSdkVersion = 24) |
| public void testSubscribeInvalidItemWithOptions() throws Exception { |
| connectMediaBrowserService(); |
| |
| final int pageSize = 5; |
| final int page = 2; |
| Bundle options = new Bundle(); |
| options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); |
| options.putInt(MediaBrowser.EXTRA_PAGE, page); |
| |
| mSubscriptionCallback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_INVALID, options, mSubscriptionCallback); |
| mSubscriptionCallback.await(TIME_OUT_MS); |
| assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId); |
| assertNotNull(mSubscriptionCallback.mLastOptions); |
| assertEquals(page, |
| mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE)); |
| assertEquals(pageSize, |
| mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE)); |
| } |
| |
| @Test |
| @MediumTest |
| @SdkSuppress(minSdkVersion = 24) |
| public void testUnsubscribeForMultipleSubscriptions() throws Exception { |
| connectMediaBrowserService(); |
| final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>(); |
| final int pageSize = 1; |
| |
| // Subscribe four pages, one item per page. |
| for (int page = 0; page < 4; page++) { |
| final StubSubscriptionCallback callback = new StubSubscriptionCallback(); |
| subscriptionCallbacks.add(callback); |
| |
| Bundle options = new Bundle(); |
| options.putInt(MediaBrowser.EXTRA_PAGE, page); |
| options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); |
| callback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback); |
| callback.await(TIME_OUT_MS); |
| |
| // Each onChildrenLoaded() must be called. |
| assertEquals(1, callback.mChildrenLoadedWithOptionCount.get()); |
| } |
| |
| // Reset callbacks and unsubscribe. |
| for (StubSubscriptionCallback callback : subscriptionCallbacks) { |
| callback.reset(1); |
| } |
| mMediaBrowser.unsubscribe(MEDIA_ID_ROOT); |
| |
| // After unsubscribing, make StubMediaBrowserService notify that the children are |
| // changed. |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| try { |
| Thread.sleep(SLEEP_MS); |
| } catch (InterruptedException e) { |
| fail("Unexpected InterruptedException occurred."); |
| } |
| |
| // onChildrenLoaded should not be called. |
| for (StubSubscriptionCallback callback : subscriptionCallbacks) { |
| assertEquals(0, callback.mChildrenLoadedWithOptionCount.get()); |
| } |
| } |
| |
| @Test |
| @LargeTest |
| @SdkSuppress(minSdkVersion = 26) |
| public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception { |
| connectMediaBrowserService(); |
| final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>(); |
| final int pageSize = 1; |
| |
| // Subscribe four pages, one item per page. |
| for (int page = 0; page < 4; page++) { |
| final StubSubscriptionCallback callback = new StubSubscriptionCallback(); |
| subscriptionCallbacks.add(callback); |
| |
| Bundle options = new Bundle(); |
| options.putInt(MediaBrowser.EXTRA_PAGE, page); |
| options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); |
| callback.reset(1); |
| mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback); |
| callback.await(TIME_OUT_MS); |
| |
| // Each onChildrenLoaded() must be called. |
| assertEquals(1, callback.mChildrenLoadedWithOptionCount.get()); |
| } |
| |
| // Unsubscribe existing subscriptions one-by-one. |
| final int[] orderOfRemovingCallbacks = {2, 0, 3, 1}; |
| for (int i = 0; i < orderOfRemovingCallbacks.length; i++) { |
| // Reset callbacks |
| for (StubSubscriptionCallback callback : subscriptionCallbacks) { |
| callback.reset(1); |
| } |
| |
| // Remove one subscription |
| mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, |
| subscriptionCallbacks.get(orderOfRemovingCallbacks[i])); |
| |
| // Make StubMediaBrowserService notify that the children are changed. |
| callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, |
| getApplicationContext()); |
| |
| // Remaining subscriptionCallbacks should be called. |
| int remaining = orderOfRemovingCallbacks.length - i - 1; |
| for (int j = i + 1; j < orderOfRemovingCallbacks.length; j++) { |
| StubSubscriptionCallback callback = subscriptionCallbacks |
| .get(orderOfRemovingCallbacks[j]); |
| assertTrue(callback.await(TIME_OUT_MS * remaining)); |
| assertEquals(1, callback.mChildrenLoadedWithOptionCount.get()); |
| } |
| |
| try { |
| Thread.sleep(SLEEP_MS); |
| } catch (InterruptedException e) { |
| fail("Unexpected InterruptedException occurred."); |
| } |
| |
| // Removed subscriptionCallbacks should NOT be called. |
| for (int j = 0; j <= i; j++) { |
| StubSubscriptionCallback callback = subscriptionCallbacks |
| .get(orderOfRemovingCallbacks[j]); |
| assertEquals(0, callback.mChildrenLoadedWithOptionCount.get()); |
| } |
| } |
| } |
| |
| @Test |
| @SmallTest |
| @SdkSuppress(minSdkVersion = 23) |
| public void testGetItem() throws Exception { |
| connectMediaBrowserService(); |
| |
| StubItemCallback itemCallback = new StubItemCallback(); |
| synchronized (itemCallback.mWaitLock) { |
| mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], itemCallback); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertNotNull(itemCallback.mLastMediaItem); |
| assertEquals(MEDIA_ID_CHILDREN[0], itemCallback.mLastMediaItem.getMediaId()); |
| } |
| } |
| |
| @Test |
| @MediumTest |
| @SdkSuppress(minSdkVersion = 23) |
| public void testGetItemDelayed() throws Exception { |
| connectMediaBrowserService(); |
| |
| StubItemCallback itemCallback = new StubItemCallback(); |
| synchronized (itemCallback.mWaitLock) { |
| mMediaBrowser.getItem(MEDIA_ID_CHILDREN_DELAYED, itemCallback); |
| itemCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); |
| assertNull(itemCallback.mLastMediaItem); |
| |
| itemCallback.reset(); |
| callMediaBrowserServiceMethod(SEND_DELAYED_ITEM_LOADED, new Bundle(), |
| getApplicationContext()); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertNotNull(itemCallback.mLastMediaItem); |
| assertEquals(MEDIA_ID_CHILDREN_DELAYED, itemCallback.mLastMediaItem.getMediaId()); |
| } |
| } |
| |
| @Test |
| @SmallTest |
| @SdkSuppress(minSdkVersion = 23) |
| public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception { |
| connectMediaBrowserService(); |
| StubItemCallback itemCallback = new StubItemCallback(); |
| synchronized (itemCallback.mWaitLock) { |
| mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, itemCallback); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| // Limitation: Framework media browser gets onItemLoaded() call with null media item, |
| // instead of onError(). |
| // assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, itemCallback.mLastErrorId); |
| } |
| } |
| |
| @Test |
| @SmallTest |
| // TODO: Lower this to 23. This test fails because mLastMediaItem below is not null on API |
| // levels 23, 24 and 25. |
| @SdkSuppress(minSdkVersion = 26) |
| public void testGetItemWhenMediaIdIsInvalid() throws Exception { |
| StubItemCallback itemCallback = new StubItemCallback(); |
| itemCallback.mLastMediaItem = new MediaItem(new MediaDescription.Builder() |
| .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE); |
| |
| connectMediaBrowserService(); |
| synchronized (itemCallback.mWaitLock) { |
| mMediaBrowser.getItem(MEDIA_ID_INVALID, itemCallback); |
| itemCallback.mWaitLock.wait(TIME_OUT_MS); |
| assertNull(itemCallback.mLastMediaItem); |
| assertNull(itemCallback.mLastErrorId); |
| } |
| } |
| |
| @Test |
| @MediumTest |
| public void testDelayedSetSessionToken() throws Exception { |
| // This test has no meaning in API 21. The framework MediaBrowserService just connects to |
| // the media browser without waiting setMediaSession() to be called. |
| if (Build.VERSION.SDK_INT == 21) { |
| return; |
| } |
| final ConnectionCallbackForDelayedMediaSession callback = |
| new ConnectionCallbackForDelayedMediaSession(); |
| |
| getInstrumentation().runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mMediaBrowser = new MediaBrowser( |
| getInstrumentation().getTargetContext(), |
| TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION, |
| callback, |
| null); |
| } |
| }); |
| |
| synchronized (callback.mWaitLock) { |
| mMediaBrowser.connect(); |
| callback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); |
| assertEquals(0, callback.mConnectedCount); |
| |
| callMediaBrowserServiceMethod(SET_SESSION_TOKEN, new Bundle(), getApplicationContext()); |
| callback.mWaitLock.wait(TIME_OUT_MS); |
| assertEquals(1, callback.mConnectedCount); |
| } |
| } |
| |
| private void connectMediaBrowserService() throws Exception { |
| synchronized (mConnectionCallback.mWaitLock) { |
| mMediaBrowser.connect(); |
| mConnectionCallback.mWaitLock.wait(TIME_OUT_MS); |
| if (!mMediaBrowser.isConnected()) { |
| fail("Browser failed to connect!"); |
| } |
| } |
| } |
| |
| private static class StubConnectionCallback extends MediaBrowser.ConnectionCallback { |
| final Object mWaitLock = new Object(); |
| volatile int mConnectedCount; |
| volatile int mConnectionFailedCount; |
| volatile int mConnectionSuspendedCount; |
| |
| public void reset() { |
| mConnectedCount = 0; |
| mConnectionFailedCount = 0; |
| mConnectionSuspendedCount = 0; |
| } |
| |
| @Override |
| public void onConnected() { |
| synchronized (mWaitLock) { |
| mConnectedCount++; |
| mWaitLock.notify(); |
| } |
| } |
| |
| @Override |
| public void onConnectionFailed() { |
| synchronized (mWaitLock) { |
| mConnectionFailedCount++; |
| mWaitLock.notify(); |
| } |
| } |
| |
| @Override |
| public void onConnectionSuspended() { |
| synchronized (mWaitLock) { |
| mConnectionSuspendedCount++; |
| mWaitLock.notify(); |
| } |
| } |
| } |
| |
| private static class StubSubscriptionCallback extends MediaBrowser.SubscriptionCallback { |
| private final AtomicInteger mChildrenLoadedCount = new AtomicInteger(); |
| private final AtomicInteger mChildrenLoadedWithOptionCount = new AtomicInteger(); |
| private volatile CountDownLatch mLatch; |
| private volatile String mLastErrorId; |
| private volatile String mLastParentId; |
| private volatile Bundle mLastOptions; |
| private volatile List<MediaItem> mLastChildMediaItems; |
| |
| public void reset(int count) { |
| mLatch = new CountDownLatch(count); |
| mChildrenLoadedCount.set(0); |
| mChildrenLoadedWithOptionCount.set(0); |
| mLastErrorId = null; |
| mLastParentId = null; |
| mLastOptions = null; |
| mLastChildMediaItems = null; |
| } |
| |
| public boolean await(long timeoutMs) { |
| try { |
| return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "interrupt while awaiting", e); |
| return false; |
| } |
| } |
| |
| @Override |
| public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) { |
| mChildrenLoadedCount.incrementAndGet(); |
| mLastParentId = parentId; |
| mLastChildMediaItems = children; |
| mLatch.countDown(); |
| } |
| |
| @Override |
| public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children, |
| @NonNull Bundle options) { |
| mChildrenLoadedWithOptionCount.incrementAndGet(); |
| mLastParentId = parentId; |
| mLastOptions = options; |
| mLastChildMediaItems = children; |
| mLatch.countDown(); |
| } |
| |
| @Override |
| public void onError(@NonNull String id) { |
| mLastErrorId = id; |
| mLatch.countDown(); |
| } |
| |
| @Override |
| public void onError(@NonNull String id, @NonNull Bundle options) { |
| mLastErrorId = id; |
| mLastOptions = options; |
| mLatch.countDown(); |
| } |
| } |
| |
| @RequiresApi(23) |
| private static class StubItemCallback extends MediaBrowser.ItemCallback { |
| final Object mWaitLock = new Object(); |
| private volatile MediaItem mLastMediaItem; |
| private volatile String mLastErrorId; |
| |
| public void reset() { |
| mLastMediaItem = null; |
| mLastErrorId = null; |
| } |
| |
| @Override |
| public void onItemLoaded(MediaItem item) { |
| synchronized (mWaitLock) { |
| mLastMediaItem = item; |
| mWaitLock.notify(); |
| } |
| } |
| |
| @Override |
| public void onError(@NonNull String id) { |
| synchronized (mWaitLock) { |
| mLastErrorId = id; |
| mWaitLock.notify(); |
| } |
| } |
| } |
| |
| private static class ConnectionCallbackForDelayedMediaSession |
| extends MediaBrowser.ConnectionCallback { |
| final Object mWaitLock = new Object(); |
| private int mConnectedCount = 0; |
| |
| @Override |
| public void onConnected() { |
| synchronized (mWaitLock) { |
| mConnectedCount++; |
| mWaitLock.notify(); |
| } |
| } |
| |
| @Override |
| public void onConnectionFailed() { |
| synchronized (mWaitLock) { |
| mWaitLock.notify(); |
| } |
| } |
| |
| @Override |
| public void onConnectionSuspended() { |
| synchronized (mWaitLock) { |
| mWaitLock.notify(); |
| } |
| } |
| } |
| } |