/* * Copyright (C) 2016 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.v4.media; import static android.support.test.InstrumentationRegistry.getInstrumentation; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.junit.Assert.fail; import android.content.ComponentName; import android.os.Bundle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.v4.media.MediaBrowserCompat.MediaItem; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; /** * Test {@link android.support.v4.media.MediaBrowserCompat}. */ @RunWith(AndroidJUnit4.class) public class MediaBrowserCompatTest { // The maximum time to wait for an operation. private static final long TIME_OUT_MS = 3000L; /** * 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 StubMediaBrowserServiceCompat#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( "android.support.mediacompat.test", "android.support.v4.media.StubMediaBrowserServiceCompat"); private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName( "invalid.package", "invalid.ServiceClassName"); private final StubConnectionCallback mConnectionCallback = new StubConnectionCallback(); private final StubSubscriptionCallback mSubscriptionCallback = new StubSubscriptionCallback(); private final StubItemCallback mItemCallback = new StubItemCallback(); private MediaBrowserCompat mMediaBrowser; @Test @SmallTest public void testMediaBrowser() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); assertEquals(false, mMediaBrowser.isConnected()); connectMediaBrowserService(); assertEquals(true, mMediaBrowser.isConnected()); assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent()); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mMediaBrowser.getRoot()); assertEquals(StubMediaBrowserServiceCompat.EXTRAS_VALUE, mMediaBrowser.getExtras().getString(StubMediaBrowserServiceCompat.EXTRAS_KEY)); assertEquals(StubMediaBrowserServiceCompat.sSession.getSessionToken(), mMediaBrowser.getSessionToken()); mMediaBrowser.disconnect(); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return !mMediaBrowser.isConnected(); } }.run(); } @Test @SmallTest public void testConnectTwice() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); try { mMediaBrowser.connect(); fail(); } catch (IllegalStateException e) { // expected } } @Test @SmallTest public void testConnectionFailed() { resetCallbacks(); createMediaBrowser(TEST_INVALID_BROWSER_SERVICE); mMediaBrowser.connect(); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mConnectionCallback.mConnectionFailedCount > 0 && mConnectionCallback.mConnectedCount == 0 && mConnectionCallback.mConnectionSuspendedCount == 0; } }.run(); } @Test @SmallTest public void testGetServiceComponentBeforeConnection() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); try { ComponentName serviceComponent = mMediaBrowser.getServiceComponent(); fail(); } catch (IllegalStateException e) { // expected } } @Test @SmallTest public void testSubscribe() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mSubscriptionCallback.mChildrenLoadedCount > 0; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length, mSubscriptionCallback.mLastChildMediaItems.size()); for (int i = 0; i < StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length; ++i) { assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[i], mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId()); } // Test unsubscribe. resetCallbacks(); mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged( StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); 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 testSubscribeWithOptions() { createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); final int pageSize = 3; final int lastPage = (StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length - 1) / pageSize; Bundle options = new Bundle(); options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize); for (int page = 0; page <= lastPage; ++page) { resetCallbacks(); options.putInt(MediaBrowserCompat.EXTRA_PAGE, page); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options, mSubscriptionCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId); if (page != lastPage) { assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size()); } else { assertEquals( (StubMediaBrowserServiceCompat.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(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[page * pageSize + i], mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId()); } } // Test unsubscribe with callback argument. resetCallbacks(); mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged( StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); 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 testSubscribeInvalidItem() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, mSubscriptionCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mSubscriptionCallback.mLastErrorId != null; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId); } @Test @SmallTest public void testSubscribeInvalidItemWithOptions() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); 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); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, options, mSubscriptionCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mSubscriptionCallback.mLastErrorId != null; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId); assertEquals(page, mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE)); assertEquals(pageSize, mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE)); } @Test @SmallTest public void testUnsubscribeForMultipleSubscriptions() { createMediaBrowser(TEST_BROWSER_SERVICE); 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(MediaBrowserCompat.EXTRA_PAGE, page); options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options, callback); // Each onChildrenLoaded() must be called. new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return callback.mChildrenLoadedWithOptionCount == 1; } }.run(); } // Reset callbacks and unsubscribe. for (StubSubscriptionCallback callback : subscriptionCallbacks) { callback.reset(); } mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are // changed. StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged( StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); 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 @SmallTest public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() { createMediaBrowser(TEST_BROWSER_SERVICE); 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(MediaBrowserCompat.EXTRA_PAGE, page); options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize); mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options, callback); // Each onChildrenLoaded() must be called. new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return callback.mChildrenLoadedWithOptionCount == 1; } }.run(); } // 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(); } // Remove one subscription mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, subscriptionCallbacks.get(orderOfRemovingCallbacks[i])); // Make StubMediaBrowserServiceCompat notify that the children are changed. StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged( StubMediaBrowserServiceCompat.MEDIA_ID_ROOT); 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() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); mMediaBrowser.getItem(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0], mItemCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mItemCallback.mLastMediaItem != null; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId()); } @Test @SmallTest public void testGetItemWhenOnLoadItemIsNotImplemented() { resetCallbacks(); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); mMediaBrowser.getItem(StubMediaBrowserServiceCompat.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mItemCallback.mLastErrorId != null; } }.run(); assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId); } @Test @SmallTest public void testGetItemWhenMediaIdIsInvalid() { resetCallbacks(); mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder() .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE); createMediaBrowser(TEST_BROWSER_SERVICE); connectMediaBrowserService(); mMediaBrowser.getItem(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, mItemCallback); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { // MediaBrowserServiceCompat.onLoadItem implementations must send null result when // the given media id is invalid. return mItemCallback.mLastMediaItem == null; } }.run(); assertNull(mItemCallback.mLastErrorId); } private void createMediaBrowser(final ComponentName component) { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(), component, mConnectionCallback, null); } }); } private void connectMediaBrowserService() { mMediaBrowser.connect(); new PollingCheck(TIME_OUT_MS) { @Override protected boolean check() { return mConnectionCallback.mConnectedCount > 0; } }.run(); } private void resetCallbacks() { mConnectionCallback.reset(); mSubscriptionCallback.reset(); mItemCallback.reset(); } private static class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback { volatile int mConnectedCount; volatile int mConnectionFailedCount; volatile int mConnectionSuspendedCount; public void reset() { mConnectedCount = 0; mConnectionFailedCount = 0; mConnectionSuspendedCount = 0; } @Override public void onConnected() { mConnectedCount++; } @Override public void onConnectionFailed() { mConnectionFailedCount++; } @Override public void onConnectionSuspended() { mConnectionSuspendedCount++; } } private static class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback { private volatile int mChildrenLoadedCount; private volatile int mChildrenLoadedWithOptionCount; private volatile String mLastErrorId; private volatile String mLastParentId; private volatile Bundle mLastOptions; private volatile List<MediaItem> mLastChildMediaItems; public void reset() { mChildrenLoadedCount = 0; mChildrenLoadedWithOptionCount = 0; mLastErrorId = null; mLastParentId = null; mLastOptions = null; mLastChildMediaItems = null; } @Override public void onChildrenLoaded(String parentId, List<MediaItem> children) { mChildrenLoadedCount++; mLastParentId = parentId; mLastChildMediaItems = children; } @Override public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) { mChildrenLoadedWithOptionCount++; mLastParentId = parentId; mLastOptions = options; mLastChildMediaItems = children; } @Override public void onError(String id) { mLastErrorId = id; } @Override public void onError(String id, Bundle options) { mLastErrorId = id; mLastOptions = options; } } private static class StubItemCallback extends MediaBrowserCompat.ItemCallback { private volatile MediaItem mLastMediaItem; private volatile String mLastErrorId; public void reset() { mLastMediaItem = null; mLastErrorId = null; } @Override public void onItemLoaded(MediaItem item) { mLastMediaItem = item; } @Override public void onError(String id) { mLastErrorId = id; } } }