/* * Copyright 2017 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.CUSTOM_ACTION; import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR; import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR; import static android.support.mediacompat.testlib.MediaBrowserConstants .CUSTOM_ACTION_SEND_PROGRESS_UPDATE; import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT; 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_INCLUDE_METADATA; 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.MEDIA_METADATA; import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED; import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY; import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR; import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT; 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.MediaBrowserConstants.TEST_KEY_1; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_2; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_3; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_4; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_1; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_2; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_3; import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_4; 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 android.support.test.InstrumentationRegistry.getArguments; import static android.support.test.InstrumentationRegistry.getContext; import static android.support.test.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.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.ComponentName; import android.os.Build; import android.os.Bundle; import android.support.mediacompat.testlib.util.PollingCheck; import android.support.test.filters.FlakyTest; import android.support.test.filters.MediumTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaBrowserCompat.MediaItem; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.RatingCompat; import android.util.Log; import androidx.annotation.NonNull; import androidx.media.MediaBrowserServiceCompat; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Test {@link MediaBrowserCompat}. */ @RunWith(AndroidJUnit4.class) public class MediaBrowserCompatTest { private static final String TAG = "MediaBrowserCompatTest"; // 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 MediaBrowserCompat#unsubscribe} works properly, * we notify to the browser after the unsubscription that the media items have changed. * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called. * * The measured time from calling {@link MediaBrowserServiceCompat#notifyChildrenChanged} * to {@link MediaBrowserCompat.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 MediaBrowserCompat mMediaBrowser; private StubConnectionCallback mConnectionCallback; private StubSubscriptionCallback mSubscriptionCallback; private StubItemCallback mItemCallback; private StubSearchCallback mSearchCallback; private CustomActionCallback mCustomActionCallback; 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(); mItemCallback = new StubItemCallback(); mSearchCallback = new StubSearchCallback(); mCustomActionCallback = new CustomActionCallback(); mRootHints = new Bundle(); mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true); mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true); mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mMediaBrowser = new MediaBrowserCompat(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); MediaBrowserServiceCompat.BrowserRoot browserRoot = new MediaBrowserServiceCompat.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 MediaBrowserCompat(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 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); assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); synchronized (mItemCallback.mWaitLock) { // Test getItem. mItemCallback.reset(); mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId()); } // Reconnect after connection was established. mMediaBrowser.disconnect(); connectMediaBrowserService(); synchronized (mItemCallback.mWaitLock) { // Test getItem. mItemCallback.reset(); mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.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 testSubscribe() throws Exception { connectMediaBrowserService(); mSubscriptionCallback.reset(1); mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount); 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 MediaBrowserServiceCompat.notifyChildrenChanged() mSubscriptionCallback.reset(1); callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount); // Test unsubscribe. mSubscriptionCallback.reset(1); mMediaBrowser.unsubscribe(MEDIA_ID_ROOT); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS); // onChildrenLoaded should not be called. assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount); } @Test @MediumTest 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(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize); for (int page = 0; page <= lastPage; ++page) { mSubscriptionCallback.reset(1); options.putInt(MediaBrowserCompat.EXTRA_PAGE, page); mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount); 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 MediaBrowserServiceCompat.notifyChildrenChanged() mSubscriptionCallback.reset(page + 1); callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(page + 1, mSubscriptionCallback.mChildrenLoadedWithOptionCount); } // Test unsubscribe with callback argument. mSubscriptionCallback.reset(1); mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); try { Thread.sleep(SLEEP_MS); } catch (InterruptedException e) { fail("Unexpected InterruptedException occurred."); } // onChildrenLoaded should not be called. assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount); } @Test @SmallTest public void testSubscribeWithOptionsIncludingCompatParcelables() throws Exception { if (Build.VERSION.SDK_INT >= 26 && !VERSION_TOT.equals(mServiceVersion)) { // This test will fail on API 26 or newer APIs if the service application uses // support library v27.0.1 or lower versions. return; } connectMediaBrowserService(); final String mediaId = "1000"; final RatingCompat percentageRating = RatingCompat.newPercentageRating(0.5f); final RatingCompat starRating = RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 4.0f); MediaMetadataCompat mediaMetadataCompat = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putRating(MediaMetadataCompat.METADATA_KEY_RATING, percentageRating) .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, starRating) .build(); Bundle options = new Bundle(); options.putParcelable(MEDIA_METADATA, mediaMetadataCompat); // Remote MediaBrowserService will create a media item with the given MediaMetadataCompat. mSubscriptionCallback.reset(1); mMediaBrowser.subscribe(MEDIA_ID_INCLUDE_METADATA, options, mSubscriptionCallback); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount); assertEquals(1, mSubscriptionCallback.mLastChildMediaItems.size()); assertEquals(mediaId, mSubscriptionCallback.mLastChildMediaItems.get(0).getMediaId()); MediaMetadataCompat metadataOut = mSubscriptionCallback.mLastOptions .getParcelable(MEDIA_METADATA); assertEquals(mediaId, metadataOut.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)); assertEquals("title", metadataOut.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); assertRatingEquals(percentageRating, metadataOut.getRating(MediaMetadataCompat.METADATA_KEY_RATING)); assertRatingEquals(starRating, metadataOut.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING)); } @Test @MediumTest public void testSubscribeDelayedItems() throws Exception { connectMediaBrowserService(); mSubscriptionCallback.reset(1); mMediaBrowser.subscribe(MEDIA_ID_CHILDREN_DELAYED, mSubscriptionCallback); mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS); assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount); callMediaBrowserServiceMethod( SEND_DELAYED_NOTIFY_CHILDREN_CHANGED, MEDIA_ID_CHILDREN_DELAYED, getContext()); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount); } @Test @SmallTest public void testSubscribeInvalidItem() throws Exception { connectMediaBrowserService(); mSubscriptionCallback.reset(1); mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback); mSubscriptionCallback.await(TIME_OUT_MS); assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId); } @Test @SmallTest public void testSubscribeInvalidItemWithOptions() throws Exception { connectMediaBrowserService(); final int pageSize = 5; final int page = 2; Bundle options = new Bundle(); options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize); options.putInt(MediaBrowserCompat.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(MediaBrowserCompat.EXTRA_PAGE)); assertEquals(pageSize, mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)); } @Test @MediumTest public void testUnsubscribeForMultipleSubscriptions() throws Exception { connectMediaBrowserService(); final List 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(MediaBrowserCompat.EXTRA_PAGE, page); options.putInt(MediaBrowserCompat.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); } // Reset callbacks and unsubscribe. for (StubSubscriptionCallback callback : subscriptionCallbacks) { callback.reset(1); } mMediaBrowser.unsubscribe(MEDIA_ID_ROOT); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); 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); } } @Test @MediumTest @FlakyTest(bugId = 74093976) public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception { connectMediaBrowserService(); final List 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(MediaBrowserCompat.EXTRA_PAGE, page); options.putInt(MediaBrowserCompat.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); } // 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 StubMediaBrowserServiceCompat notify that the children are changed. callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext()); try { Thread.sleep(SLEEP_MS); } catch (InterruptedException e) { fail("Unexpected InterruptedException occurred."); } // Only the remaining subscriptionCallbacks should be called. for (int j = 0; j < 4; j++) { int childrenLoadedWithOptionsCount = subscriptionCallbacks .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount; if (j <= i) { assertEquals(0, childrenLoadedWithOptionsCount); } else { assertEquals(1, childrenLoadedWithOptionsCount); } } } } @Test @SmallTest public void testGetItem() throws Exception { connectMediaBrowserService(); synchronized (mItemCallback.mWaitLock) { mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertNotNull(mItemCallback.mLastMediaItem); assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId()); } } @Test @MediumTest public void testGetItemDelayed() throws Exception { connectMediaBrowserService(); synchronized (mItemCallback.mWaitLock) { mMediaBrowser.getItem(MEDIA_ID_CHILDREN_DELAYED, mItemCallback); mItemCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); assertNull(mItemCallback.mLastMediaItem); mItemCallback.reset(); callMediaBrowserServiceMethod(SEND_DELAYED_ITEM_LOADED, new Bundle(), getContext()); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertNotNull(mItemCallback.mLastMediaItem); assertEquals(MEDIA_ID_CHILDREN_DELAYED, mItemCallback.mLastMediaItem.getMediaId()); } } @Test @SmallTest public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception { connectMediaBrowserService(); synchronized (mItemCallback.mWaitLock) { mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId); } } @Test @SmallTest public void testGetItemWhenMediaIdIsInvalid() throws Exception { mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder() .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE); connectMediaBrowserService(); synchronized (mItemCallback.mWaitLock) { mMediaBrowser.getItem(MEDIA_ID_INVALID, mItemCallback); mItemCallback.mWaitLock.wait(TIME_OUT_MS); assertNull(mItemCallback.mLastMediaItem); assertNull(mItemCallback.mLastErrorId); } } @Test @SmallTest public void testSearch() throws Exception { connectMediaBrowserService(); final String key = "test-key"; final String val = "test-val"; synchronized (mSearchCallback.mWaitLock) { mSearchCallback.reset(); mMediaBrowser.search(SEARCH_QUERY_FOR_NO_RESULT, null, mSearchCallback); mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); assertTrue(mSearchCallback.mOnSearchResult); assertTrue(mSearchCallback.mSearchResults != null && mSearchCallback.mSearchResults.size() == 0); assertEquals(null, mSearchCallback.mSearchExtras); mSearchCallback.reset(); mMediaBrowser.search(SEARCH_QUERY_FOR_ERROR, null, mSearchCallback); mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); assertTrue(mSearchCallback.mOnSearchResult); assertNull(mSearchCallback.mSearchResults); assertEquals(null, mSearchCallback.mSearchExtras); mSearchCallback.reset(); Bundle extras = new Bundle(); extras.putString(key, val); mMediaBrowser.search(SEARCH_QUERY, extras, mSearchCallback); mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); assertTrue(mSearchCallback.mOnSearchResult); assertNotNull(mSearchCallback.mSearchResults); for (MediaItem item : mSearchCallback.mSearchResults) { assertNotNull(item.getMediaId()); assertTrue(item.getMediaId().contains(SEARCH_QUERY)); } assertNotNull(mSearchCallback.mSearchExtras); assertEquals(val, mSearchCallback.mSearchExtras.getString(key)); } } @Test @SmallTest public void testSendCustomAction() throws Exception { connectMediaBrowserService(); synchronized (mCustomActionCallback.mWaitLock) { Bundle customActionExtras = new Bundle(); customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1); mMediaBrowser.sendCustomAction( CUSTOM_ACTION, customActionExtras, mCustomActionCallback); mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); mCustomActionCallback.reset(); Bundle data1 = new Bundle(); data1.putString(TEST_KEY_2, TEST_VALUE_2); callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data1, getContext()); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnProgressUpdateCalled); assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction); assertNotNull(mCustomActionCallback.mExtras); assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1)); assertNotNull(mCustomActionCallback.mData); assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2)); mCustomActionCallback.reset(); Bundle data2 = new Bundle(); data2.putString(TEST_KEY_3, TEST_VALUE_3); callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data2, getContext()); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnProgressUpdateCalled); assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction); assertNotNull(mCustomActionCallback.mExtras); assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1)); assertNotNull(mCustomActionCallback.mData); assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3)); Bundle resultData = new Bundle(); resultData.putString(TEST_KEY_4, TEST_VALUE_4); mCustomActionCallback.reset(); callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, resultData, getContext()); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnResultCalled); assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction); assertNotNull(mCustomActionCallback.mExtras); assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1)); assertNotNull(mCustomActionCallback.mData); assertEquals(TEST_VALUE_4, mCustomActionCallback.mData.getString(TEST_KEY_4)); } } @Test @MediumTest public void testSendCustomActionWithDetachedError() throws Exception { connectMediaBrowserService(); synchronized (mCustomActionCallback.mWaitLock) { Bundle customActionExtras = new Bundle(); customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1); mMediaBrowser.sendCustomAction( CUSTOM_ACTION, customActionExtras, mCustomActionCallback); mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS); mCustomActionCallback.reset(); Bundle progressUpdateData = new Bundle(); progressUpdateData.putString(TEST_KEY_2, TEST_VALUE_2); callMediaBrowserServiceMethod( CUSTOM_ACTION_SEND_PROGRESS_UPDATE, progressUpdateData, getContext()); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnProgressUpdateCalled); assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction); assertNotNull(mCustomActionCallback.mExtras); assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1)); assertNotNull(mCustomActionCallback.mData); assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2)); mCustomActionCallback.reset(); Bundle errorData = new Bundle(); errorData.putString(TEST_KEY_3, TEST_VALUE_3); callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_ERROR, errorData, getContext()); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnErrorCalled); assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction); assertNotNull(mCustomActionCallback.mExtras); assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1)); assertNotNull(mCustomActionCallback.mData); assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3)); } } @Test @MediumTest public void testSendCustomActionWithNullCallback() throws Exception { connectMediaBrowserService(); Bundle customActionExtras = new Bundle(); customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1); mMediaBrowser.sendCustomAction(CUSTOM_ACTION, customActionExtras, null); // Wait some time so that the service can get a result receiver for the custom action. Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS); // These calls should not make any exceptions. callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, new Bundle(), getContext()); callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, new Bundle(), getContext()); Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS); } @Test @SmallTest public void testSendCustomActionWithError() throws Exception { connectMediaBrowserService(); synchronized (mCustomActionCallback.mWaitLock) { mMediaBrowser.sendCustomAction(CUSTOM_ACTION_FOR_ERROR, null, mCustomActionCallback); mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS); assertTrue(mCustomActionCallback.mOnErrorCalled); } } @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 MediaBrowserCompat( 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(), getContext()); callback.mWaitLock.wait(TIME_OUT_MS); assertEquals(1, callback.mConnectedCount); if (Build.VERSION.SDK_INT >= 21) { assertNotNull(mMediaBrowser.getSessionToken().getExtraBinder()); } } } 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 void assertRatingEquals(RatingCompat expected, RatingCompat observed) { if (expected == null || observed == null) { assertSame(expected, observed); } assertEquals(expected.getRatingStyle(), observed.getRatingStyle()); if (expected.getRatingStyle() == RatingCompat.RATING_PERCENTAGE) { assertEquals(expected.getPercentRating(), observed.getPercentRating(), 0.01f); } else if (expected.getRatingStyle() == RatingCompat.RATING_5_STARS) { assertEquals(expected.getStarRating(), observed.getStarRating(), 0.01f); } else { // Currently, we use only star and percentage rating. fail("Rating style should be either percentage rating or star rating."); } } private class StubConnectionCallback extends MediaBrowserCompat.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 class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback { private CountDownLatch mLatch; private volatile int mChildrenLoadedCount; private volatile int mChildrenLoadedWithOptionCount; private volatile String mLastErrorId; private volatile String mLastParentId; private volatile Bundle mLastOptions; private volatile List mLastChildMediaItems; public void reset(int count) { mLatch = new CountDownLatch(count); mChildrenLoadedCount = 0; mChildrenLoadedWithOptionCount = 0; mLastErrorId = null; mLastParentId = null; mLastOptions = null; mLastChildMediaItems = null; } public boolean await(long timeoutMs) { try { return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return false; } } @Override public void onChildrenLoaded(@NonNull String parentId, @NonNull List children) { mChildrenLoadedCount++; mLastParentId = parentId; mLastChildMediaItems = children; mLatch.countDown(); } @Override public void onChildrenLoaded(@NonNull String parentId, @NonNull List children, @NonNull Bundle options) { mChildrenLoadedWithOptionCount++; 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(); } } private class StubItemCallback extends MediaBrowserCompat.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 class StubSearchCallback extends MediaBrowserCompat.SearchCallback { final Object mWaitLock = new Object(); boolean mOnSearchResult; Bundle mSearchExtras; List mSearchResults; @Override public void onSearchResult(@NonNull String query, Bundle extras, @NonNull List items) { synchronized (mWaitLock) { mOnSearchResult = true; mSearchResults = items; mSearchExtras = extras; mWaitLock.notify(); } } @Override public void onError(@NonNull String query, Bundle extras) { synchronized (mWaitLock) { mOnSearchResult = true; mSearchResults = null; mSearchExtras = extras; mWaitLock.notify(); } } public void reset() { mOnSearchResult = false; mSearchExtras = null; mSearchResults = null; } } private class CustomActionCallback extends MediaBrowserCompat.CustomActionCallback { final Object mWaitLock = new Object(); String mAction; Bundle mExtras; Bundle mData; boolean mOnProgressUpdateCalled; boolean mOnResultCalled; boolean mOnErrorCalled; @Override public void onProgressUpdate(String action, Bundle extras, Bundle data) { synchronized (mWaitLock) { mOnProgressUpdateCalled = true; mAction = action; mExtras = extras; mData = data; mWaitLock.notify(); } } @Override public void onResult(String action, Bundle extras, Bundle resultData) { synchronized (mWaitLock) { mOnResultCalled = true; mAction = action; mExtras = extras; mData = resultData; mWaitLock.notify(); } } @Override public void onError(String action, Bundle extras, Bundle data) { synchronized (mWaitLock) { mOnErrorCalled = true; mAction = action; mExtras = extras; mData = data; mWaitLock.notify(); } } public void reset() { mOnResultCalled = false; mOnProgressUpdateCalled = false; mOnErrorCalled = false; mAction = null; mExtras = null; mData = null; } } private class ConnectionCallbackForDelayedMediaSession extends MediaBrowserCompat.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(); } } } }