Home | History | Annotate | Download | only in client
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.mediacompat.client;
     18 
     19 import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
     20 import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
     21 import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
     22 import static android.support.mediacompat.testlib.MediaBrowserConstants
     23         .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
     24 import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
     25 import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
     26 import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
     27 import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
     28 import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
     29 import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
     30 import static android.support.mediacompat.testlib.MediaBrowserConstants
     31         .MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED;
     32 import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
     33 import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
     34 import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
     35 import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
     36 import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
     37 import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
     38 import static android.support.mediacompat.testlib.MediaBrowserConstants
     39         .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
     40 import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
     41 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_1;
     42 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_2;
     43 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_3;
     44 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_4;
     45 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_1;
     46 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_2;
     47 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_3;
     48 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_4;
     49 import static android.support.mediacompat.testlib.VersionConstants.KEY_SERVICE_VERSION;
     50 import static android.support.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
     51 import static android.support.mediacompat.testlib.util.IntentUtil.callMediaBrowserServiceMethod;
     52 import static android.support.test.InstrumentationRegistry.getArguments;
     53 import static android.support.test.InstrumentationRegistry.getContext;
     54 import static android.support.test.InstrumentationRegistry.getInstrumentation;
     55 
     56 import static org.junit.Assert.assertEquals;
     57 import static org.junit.Assert.assertFalse;
     58 import static org.junit.Assert.assertNotNull;
     59 import static org.junit.Assert.assertNull;
     60 import static org.junit.Assert.assertTrue;
     61 import static org.junit.Assert.fail;
     62 
     63 import android.content.ComponentName;
     64 import android.os.Build;
     65 import android.os.Bundle;
     66 import android.support.annotation.NonNull;
     67 import android.support.mediacompat.testlib.util.PollingCheck;
     68 import android.support.test.filters.FlakyTest;
     69 import android.support.test.filters.MediumTest;
     70 import android.support.test.filters.SmallTest;
     71 import android.support.test.runner.AndroidJUnit4;
     72 import android.support.v4.media.MediaBrowserCompat;
     73 import android.support.v4.media.MediaBrowserCompat.MediaItem;
     74 import android.support.v4.media.MediaBrowserServiceCompat;
     75 import android.support.v4.media.MediaDescriptionCompat;
     76 import android.util.Log;
     77 
     78 import org.junit.After;
     79 import org.junit.Before;
     80 import org.junit.Test;
     81 import org.junit.runner.RunWith;
     82 
     83 import java.util.ArrayList;
     84 import java.util.List;
     85 import java.util.concurrent.CountDownLatch;
     86 import java.util.concurrent.TimeUnit;
     87 
     88 /**
     89  * Test {@link android.support.v4.media.MediaBrowserCompat}.
     90  */
     91 @RunWith(AndroidJUnit4.class)
     92 public class MediaBrowserCompatTest {
     93 
     94     private static final String TAG = "MediaBrowserCompatTest";
     95 
     96     // The maximum time to wait for an operation.
     97     private static final long TIME_OUT_MS = 3000L;
     98     private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L;
     99 
    100     /**
    101      * To check {@link MediaBrowserCompat#unsubscribe} works properly,
    102      * we notify to the browser after the unsubscription that the media items have changed.
    103      * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called.
    104      *
    105      * The measured time from calling {@link MediaBrowserServiceCompat#notifyChildrenChanged}
    106      * to {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} being called is about
    107      * 50ms.
    108      * So we make the thread sleep for 100ms to properly check that the callback is not called.
    109      */
    110     private static final long SLEEP_MS = 100L;
    111     private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
    112             SERVICE_PACKAGE_NAME,
    113             "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
    114     private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION =
    115             new ComponentName(
    116                     SERVICE_PACKAGE_NAME,
    117                     "android.support.mediacompat.service"
    118                             + ".StubMediaBrowserServiceCompatWithDelayedMediaSession");
    119     private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
    120             "invalid.package", "invalid.ServiceClassName");
    121 
    122     private String mServiceVersion;
    123     private MediaBrowserCompat mMediaBrowser;
    124     private StubConnectionCallback mConnectionCallback;
    125     private StubSubscriptionCallback mSubscriptionCallback;
    126     private StubItemCallback mItemCallback;
    127     private StubSearchCallback mSearchCallback;
    128     private CustomActionCallback mCustomActionCallback;
    129     private Bundle mRootHints;
    130 
    131     @Before
    132     public void setUp() {
    133         // The version of the service app is provided through the instrumentation arguments.
    134         mServiceVersion = getArguments().getString(KEY_SERVICE_VERSION, "");
    135         Log.d(TAG, "Service app version: " + mServiceVersion);
    136 
    137         mConnectionCallback = new StubConnectionCallback();
    138         mSubscriptionCallback = new StubSubscriptionCallback();
    139         mItemCallback = new StubItemCallback();
    140         mSearchCallback = new StubSearchCallback();
    141         mCustomActionCallback = new CustomActionCallback();
    142 
    143         mRootHints = new Bundle();
    144         mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true);
    145         mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true);
    146         mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true);
    147 
    148         getInstrumentation().runOnMainSync(new Runnable() {
    149             @Override
    150             public void run() {
    151                 mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
    152                         TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints);
    153             }
    154         });
    155     }
    156 
    157     @After
    158     public void tearDown() {
    159         if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
    160             mMediaBrowser.disconnect();
    161         }
    162     }
    163 
    164     @Test
    165     @SmallTest
    166     public void testBrowserRoot() {
    167         final String id = "test-id";
    168         final String key = "test-key";
    169         final String val = "test-val";
    170         final Bundle extras = new Bundle();
    171         extras.putString(key, val);
    172 
    173         MediaBrowserServiceCompat.BrowserRoot browserRoot =
    174                 new MediaBrowserServiceCompat.BrowserRoot(id, extras);
    175         assertEquals(id, browserRoot.getRootId());
    176         assertEquals(val, browserRoot.getExtras().getString(key));
    177     }
    178 
    179     @Test
    180     @SmallTest
    181     public void testMediaBrowser() throws Exception {
    182         assertFalse(mMediaBrowser.isConnected());
    183 
    184         connectMediaBrowserService();
    185         assertTrue(mMediaBrowser.isConnected());
    186 
    187         assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
    188         assertEquals(MEDIA_ID_ROOT, mMediaBrowser.getRoot());
    189         assertEquals(EXTRAS_VALUE, mMediaBrowser.getExtras().getString(EXTRAS_KEY));
    190 
    191         mMediaBrowser.disconnect();
    192         new PollingCheck(TIME_OUT_MS) {
    193             @Override
    194             protected boolean check() {
    195                 return !mMediaBrowser.isConnected();
    196             }
    197         }.run();
    198     }
    199 
    200     @Test
    201     @SmallTest
    202     public void testGetServiceComponentBeforeConnection() {
    203         try {
    204             ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
    205             fail();
    206         } catch (IllegalStateException e) {
    207             // expected
    208         }
    209     }
    210 
    211     @Test
    212     @SmallTest
    213     public void testConnectionFailed() throws Exception {
    214         getInstrumentation().runOnMainSync(new Runnable() {
    215             @Override
    216             public void run() {
    217                 mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
    218                         TEST_INVALID_BROWSER_SERVICE, mConnectionCallback, mRootHints);
    219             }
    220         });
    221 
    222         synchronized (mConnectionCallback.mWaitLock) {
    223             mMediaBrowser.connect();
    224             mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
    225         }
    226         assertEquals(1, mConnectionCallback.mConnectionFailedCount);
    227         assertEquals(0, mConnectionCallback.mConnectedCount);
    228         assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
    229     }
    230 
    231     @Test
    232     @SmallTest
    233     public void testConnectTwice() throws Exception {
    234         connectMediaBrowserService();
    235         try {
    236             mMediaBrowser.connect();
    237             fail();
    238         } catch (IllegalStateException e) {
    239             // expected
    240         }
    241     }
    242 
    243     @Test
    244     @MediumTest
    245     public void testReconnection() throws Exception {
    246         getInstrumentation().runOnMainSync(new Runnable() {
    247             @Override
    248             public void run() {
    249                 mMediaBrowser.connect();
    250                 // Reconnect before the first connection was established.
    251                 mMediaBrowser.disconnect();
    252                 mMediaBrowser.connect();
    253             }
    254         });
    255 
    256         synchronized (mConnectionCallback.mWaitLock) {
    257             mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
    258             assertEquals(1, mConnectionCallback.mConnectedCount);
    259         }
    260 
    261         // Test subscribe.
    262         mSubscriptionCallback.reset(1);
    263         mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
    264         mSubscriptionCallback.await(TIME_OUT_MS);
    265         assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
    266         assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
    267 
    268         synchronized (mItemCallback.mWaitLock) {
    269             // Test getItem.
    270             mItemCallback.reset();
    271             mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
    272             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    273             assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
    274         }
    275 
    276         // Reconnect after connection was established.
    277         mMediaBrowser.disconnect();
    278         connectMediaBrowserService();
    279 
    280         synchronized (mItemCallback.mWaitLock) {
    281             // Test getItem.
    282             mItemCallback.reset();
    283             mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
    284             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    285             assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
    286         }
    287     }
    288 
    289     @Test
    290     @MediumTest
    291     public void testConnectionCallbackNotCalledAfterDisconnect() {
    292         getInstrumentation().runOnMainSync(new Runnable() {
    293             @Override
    294             public void run() {
    295                 mMediaBrowser.connect();
    296                 mMediaBrowser.disconnect();
    297                 mConnectionCallback.reset();
    298             }
    299         });
    300 
    301         try {
    302             Thread.sleep(SLEEP_MS);
    303         } catch (InterruptedException e) {
    304             fail("Unexpected InterruptedException occurred.");
    305         }
    306         assertEquals(0, mConnectionCallback.mConnectedCount);
    307         assertEquals(0, mConnectionCallback.mConnectionFailedCount);
    308         assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
    309     }
    310 
    311     @Test
    312     @MediumTest
    313     public void testSubscribe() throws Exception {
    314         connectMediaBrowserService();
    315 
    316         mSubscriptionCallback.reset(1);
    317         mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
    318         mSubscriptionCallback.await(TIME_OUT_MS);
    319         assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
    320         assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
    321         assertEquals(MEDIA_ID_CHILDREN.length, mSubscriptionCallback.mLastChildMediaItems.size());
    322         for (int i = 0; i < MEDIA_ID_CHILDREN.length; ++i) {
    323             assertEquals(MEDIA_ID_CHILDREN[i],
    324                     mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
    325         }
    326 
    327         // Test MediaBrowserServiceCompat.notifyChildrenChanged()
    328         mSubscriptionCallback.reset(1);
    329         callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    330         mSubscriptionCallback.await(TIME_OUT_MS);
    331         assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
    332 
    333         // Test unsubscribe.
    334         mSubscriptionCallback.reset(1);
    335         mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
    336 
    337         // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
    338         // changed.
    339         callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    340         mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
    341 
    342         // onChildrenLoaded should not be called.
    343         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
    344     }
    345 
    346     @Test
    347     @MediumTest
    348     public void testSubscribeWithOptions() throws Exception {
    349         connectMediaBrowserService();
    350         final int pageSize = 3;
    351         final int lastPage = (MEDIA_ID_CHILDREN.length - 1) / pageSize;
    352         Bundle options = new Bundle();
    353         options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
    354 
    355         for (int page = 0; page <= lastPage; ++page) {
    356             mSubscriptionCallback.reset(1);
    357             options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
    358             mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback);
    359             mSubscriptionCallback.await(TIME_OUT_MS);
    360             assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
    361             assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
    362             if (page != lastPage) {
    363                 assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
    364             } else {
    365                 assertEquals((MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
    366                         mSubscriptionCallback.mLastChildMediaItems.size());
    367             }
    368             // Check whether all the items in the current page are loaded.
    369             for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
    370                 assertEquals(MEDIA_ID_CHILDREN[page * pageSize + i],
    371                         mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
    372             }
    373 
    374             // Test MediaBrowserServiceCompat.notifyChildrenChanged()
    375             mSubscriptionCallback.reset(page + 1);
    376             callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    377             mSubscriptionCallback.await(TIME_OUT_MS);
    378             assertEquals(page + 1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
    379         }
    380 
    381         // Test unsubscribe with callback argument.
    382         mSubscriptionCallback.reset(1);
    383         mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
    384 
    385         // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
    386         // changed.
    387         callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    388         try {
    389             Thread.sleep(SLEEP_MS);
    390         } catch (InterruptedException e) {
    391             fail("Unexpected InterruptedException occurred.");
    392         }
    393         // onChildrenLoaded should not be called.
    394         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
    395     }
    396 
    397     @Test
    398     @MediumTest
    399     public void testSubscribeDelayedItems() throws Exception {
    400         connectMediaBrowserService();
    401 
    402         mSubscriptionCallback.reset(1);
    403         mMediaBrowser.subscribe(MEDIA_ID_CHILDREN_DELAYED, mSubscriptionCallback);
    404         mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
    405         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
    406 
    407         callMediaBrowserServiceMethod(
    408                 SEND_DELAYED_NOTIFY_CHILDREN_CHANGED, MEDIA_ID_CHILDREN_DELAYED, getContext());
    409         mSubscriptionCallback.await(TIME_OUT_MS);
    410         assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
    411     }
    412 
    413     @Test
    414     @SmallTest
    415     public void testSubscribeInvalidItem() throws Exception {
    416         connectMediaBrowserService();
    417 
    418         mSubscriptionCallback.reset(1);
    419         mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback);
    420         mSubscriptionCallback.await(TIME_OUT_MS);
    421         assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
    422     }
    423 
    424     @Test
    425     @SmallTest
    426     public void testSubscribeInvalidItemWithOptions() throws Exception {
    427         connectMediaBrowserService();
    428 
    429         final int pageSize = 5;
    430         final int page = 2;
    431         Bundle options = new Bundle();
    432         options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
    433         options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
    434 
    435         mSubscriptionCallback.reset(1);
    436         mMediaBrowser.subscribe(MEDIA_ID_INVALID, options, mSubscriptionCallback);
    437         mSubscriptionCallback.await(TIME_OUT_MS);
    438         assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
    439         assertNotNull(mSubscriptionCallback.mLastOptions);
    440         assertEquals(page,
    441                 mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE));
    442         assertEquals(pageSize,
    443                 mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
    444     }
    445 
    446     @Test
    447     @MediumTest
    448     public void testUnsubscribeForMultipleSubscriptions() throws Exception {
    449         connectMediaBrowserService();
    450         final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
    451         final int pageSize = 1;
    452 
    453         // Subscribe four pages, one item per page.
    454         for (int page = 0; page < 4; page++) {
    455             final StubSubscriptionCallback callback = new StubSubscriptionCallback();
    456             subscriptionCallbacks.add(callback);
    457 
    458             Bundle options = new Bundle();
    459             options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
    460             options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
    461             callback.reset(1);
    462             mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
    463             callback.await(TIME_OUT_MS);
    464 
    465             // Each onChildrenLoaded() must be called.
    466             assertEquals(1, callback.mChildrenLoadedWithOptionCount);
    467         }
    468 
    469         // Reset callbacks and unsubscribe.
    470         for (StubSubscriptionCallback callback : subscriptionCallbacks) {
    471             callback.reset(1);
    472         }
    473         mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
    474 
    475         // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
    476         // changed.
    477         callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    478         try {
    479             Thread.sleep(SLEEP_MS);
    480         } catch (InterruptedException e) {
    481             fail("Unexpected InterruptedException occurred.");
    482         }
    483 
    484         // onChildrenLoaded should not be called.
    485         for (StubSubscriptionCallback callback : subscriptionCallbacks) {
    486             assertEquals(0, callback.mChildrenLoadedWithOptionCount);
    487         }
    488     }
    489 
    490     @Test
    491     @MediumTest
    492     @FlakyTest(bugId = 74093976)
    493     public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
    494         connectMediaBrowserService();
    495         final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
    496         final int pageSize = 1;
    497 
    498         // Subscribe four pages, one item per page.
    499         for (int page = 0; page < 4; page++) {
    500             final StubSubscriptionCallback callback = new StubSubscriptionCallback();
    501             subscriptionCallbacks.add(callback);
    502 
    503             Bundle options = new Bundle();
    504             options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
    505             options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
    506             callback.reset(1);
    507             mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
    508             callback.await(TIME_OUT_MS);
    509 
    510             // Each onChildrenLoaded() must be called.
    511             assertEquals(1, callback.mChildrenLoadedWithOptionCount);
    512         }
    513 
    514         // Unsubscribe existing subscriptions one-by-one.
    515         final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
    516         for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
    517             // Reset callbacks
    518             for (StubSubscriptionCallback callback : subscriptionCallbacks) {
    519                 callback.reset(1);
    520             }
    521 
    522             // Remove one subscription
    523             mMediaBrowser.unsubscribe(MEDIA_ID_ROOT,
    524                     subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
    525 
    526             // Make StubMediaBrowserServiceCompat notify that the children are changed.
    527             callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
    528             try {
    529                 Thread.sleep(SLEEP_MS);
    530             } catch (InterruptedException e) {
    531                 fail("Unexpected InterruptedException occurred.");
    532             }
    533 
    534             // Only the remaining subscriptionCallbacks should be called.
    535             for (int j = 0; j < 4; j++) {
    536                 int childrenLoadedWithOptionsCount = subscriptionCallbacks
    537                         .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
    538                 if (j <= i) {
    539                     assertEquals(0, childrenLoadedWithOptionsCount);
    540                 } else {
    541                     assertEquals(1, childrenLoadedWithOptionsCount);
    542                 }
    543             }
    544         }
    545     }
    546 
    547     @Test
    548     @SmallTest
    549     public void testGetItem() throws Exception {
    550         connectMediaBrowserService();
    551 
    552         synchronized (mItemCallback.mWaitLock) {
    553             mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
    554             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    555             assertNotNull(mItemCallback.mLastMediaItem);
    556             assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
    557         }
    558     }
    559 
    560     @Test
    561     @MediumTest
    562     public void testGetItemDelayed() throws Exception {
    563         connectMediaBrowserService();
    564 
    565         synchronized (mItemCallback.mWaitLock) {
    566             mMediaBrowser.getItem(MEDIA_ID_CHILDREN_DELAYED, mItemCallback);
    567             mItemCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    568             assertNull(mItemCallback.mLastMediaItem);
    569 
    570             mItemCallback.reset();
    571             callMediaBrowserServiceMethod(SEND_DELAYED_ITEM_LOADED, new Bundle(), getContext());
    572             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    573             assertNotNull(mItemCallback.mLastMediaItem);
    574             assertEquals(MEDIA_ID_CHILDREN_DELAYED, mItemCallback.mLastMediaItem.getMediaId());
    575         }
    576     }
    577 
    578     @Test
    579     @SmallTest
    580     public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception {
    581         connectMediaBrowserService();
    582         synchronized (mItemCallback.mWaitLock) {
    583             mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback);
    584             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    585             assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId);
    586         }
    587     }
    588 
    589     @Test
    590     @SmallTest
    591     public void testGetItemWhenMediaIdIsInvalid() throws Exception {
    592         mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder()
    593                 .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE);
    594 
    595         connectMediaBrowserService();
    596         synchronized (mItemCallback.mWaitLock) {
    597             mMediaBrowser.getItem(MEDIA_ID_INVALID, mItemCallback);
    598             mItemCallback.mWaitLock.wait(TIME_OUT_MS);
    599             assertNull(mItemCallback.mLastMediaItem);
    600             assertNull(mItemCallback.mLastErrorId);
    601         }
    602     }
    603 
    604     @Test
    605     @SmallTest
    606     public void testSearch() throws Exception {
    607         connectMediaBrowserService();
    608 
    609         final String key = "test-key";
    610         final String val = "test-val";
    611 
    612         synchronized (mSearchCallback.mWaitLock) {
    613             mSearchCallback.reset();
    614             mMediaBrowser.search(SEARCH_QUERY_FOR_NO_RESULT, null, mSearchCallback);
    615             mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    616             assertTrue(mSearchCallback.mOnSearchResult);
    617             assertTrue(mSearchCallback.mSearchResults != null
    618                     && mSearchCallback.mSearchResults.size() == 0);
    619             assertEquals(null, mSearchCallback.mSearchExtras);
    620 
    621             mSearchCallback.reset();
    622             mMediaBrowser.search(SEARCH_QUERY_FOR_ERROR, null, mSearchCallback);
    623             mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    624             assertTrue(mSearchCallback.mOnSearchResult);
    625             assertNull(mSearchCallback.mSearchResults);
    626             assertEquals(null, mSearchCallback.mSearchExtras);
    627 
    628             mSearchCallback.reset();
    629             Bundle extras = new Bundle();
    630             extras.putString(key, val);
    631             mMediaBrowser.search(SEARCH_QUERY, extras, mSearchCallback);
    632             mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    633             assertTrue(mSearchCallback.mOnSearchResult);
    634             assertNotNull(mSearchCallback.mSearchResults);
    635             for (MediaItem item : mSearchCallback.mSearchResults) {
    636                 assertNotNull(item.getMediaId());
    637                 assertTrue(item.getMediaId().contains(SEARCH_QUERY));
    638             }
    639             assertNotNull(mSearchCallback.mSearchExtras);
    640             assertEquals(val, mSearchCallback.mSearchExtras.getString(key));
    641         }
    642     }
    643 
    644     @Test
    645     @SmallTest
    646     public void testSendCustomAction() throws Exception {
    647         connectMediaBrowserService();
    648 
    649         synchronized (mCustomActionCallback.mWaitLock) {
    650             Bundle customActionExtras = new Bundle();
    651             customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
    652             mMediaBrowser.sendCustomAction(
    653                     CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
    654             mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    655 
    656             mCustomActionCallback.reset();
    657             Bundle data1 = new Bundle();
    658             data1.putString(TEST_KEY_2, TEST_VALUE_2);
    659             callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data1, getContext());
    660             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    661 
    662             assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
    663             assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
    664             assertNotNull(mCustomActionCallback.mExtras);
    665             assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
    666             assertNotNull(mCustomActionCallback.mData);
    667             assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
    668 
    669             mCustomActionCallback.reset();
    670             Bundle data2 = new Bundle();
    671             data2.putString(TEST_KEY_3, TEST_VALUE_3);
    672             callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data2, getContext());
    673             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    674 
    675             assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
    676             assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
    677             assertNotNull(mCustomActionCallback.mExtras);
    678             assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
    679             assertNotNull(mCustomActionCallback.mData);
    680             assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
    681 
    682             Bundle resultData = new Bundle();
    683             resultData.putString(TEST_KEY_4, TEST_VALUE_4);
    684             mCustomActionCallback.reset();
    685             callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, resultData, getContext());
    686             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    687 
    688             assertTrue(mCustomActionCallback.mOnResultCalled);
    689             assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
    690             assertNotNull(mCustomActionCallback.mExtras);
    691             assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
    692             assertNotNull(mCustomActionCallback.mData);
    693             assertEquals(TEST_VALUE_4, mCustomActionCallback.mData.getString(TEST_KEY_4));
    694         }
    695     }
    696 
    697 
    698     @Test
    699     @MediumTest
    700     public void testSendCustomActionWithDetachedError() throws Exception {
    701         connectMediaBrowserService();
    702 
    703         synchronized (mCustomActionCallback.mWaitLock) {
    704             Bundle customActionExtras = new Bundle();
    705             customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
    706             mMediaBrowser.sendCustomAction(
    707                     CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
    708             mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    709 
    710             mCustomActionCallback.reset();
    711             Bundle progressUpdateData = new Bundle();
    712             progressUpdateData.putString(TEST_KEY_2, TEST_VALUE_2);
    713             callMediaBrowserServiceMethod(
    714                     CUSTOM_ACTION_SEND_PROGRESS_UPDATE, progressUpdateData, getContext());
    715             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    716             assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
    717             assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
    718             assertNotNull(mCustomActionCallback.mExtras);
    719             assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
    720             assertNotNull(mCustomActionCallback.mData);
    721             assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
    722 
    723             mCustomActionCallback.reset();
    724             Bundle errorData = new Bundle();
    725             errorData.putString(TEST_KEY_3, TEST_VALUE_3);
    726             callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_ERROR, errorData, getContext());
    727             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    728             assertTrue(mCustomActionCallback.mOnErrorCalled);
    729             assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
    730             assertNotNull(mCustomActionCallback.mExtras);
    731             assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
    732             assertNotNull(mCustomActionCallback.mData);
    733             assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
    734         }
    735     }
    736 
    737     @Test
    738     @MediumTest
    739     public void testSendCustomActionWithNullCallback() throws Exception {
    740         connectMediaBrowserService();
    741 
    742         Bundle customActionExtras = new Bundle();
    743         customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
    744         mMediaBrowser.sendCustomAction(CUSTOM_ACTION, customActionExtras, null);
    745         // Wait some time so that the service can get a result receiver for the custom action.
    746         Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
    747 
    748         // These calls should not make any exceptions.
    749         callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, new Bundle(),
    750                 getContext());
    751         callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, new Bundle(), getContext());
    752         Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
    753     }
    754 
    755     @Test
    756     @SmallTest
    757     public void testSendCustomActionWithError() throws Exception {
    758         connectMediaBrowserService();
    759 
    760         synchronized (mCustomActionCallback.mWaitLock) {
    761             mMediaBrowser.sendCustomAction(CUSTOM_ACTION_FOR_ERROR, null, mCustomActionCallback);
    762             mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
    763             assertTrue(mCustomActionCallback.mOnErrorCalled);
    764         }
    765     }
    766 
    767     @Test
    768     @MediumTest
    769     public void testDelayedSetSessionToken() throws Exception {
    770         // This test has no meaning in API 21. The framework MediaBrowserService just connects to
    771         // the media browser without waiting setMediaSession() to be called.
    772         if (Build.VERSION.SDK_INT == 21) {
    773             return;
    774         }
    775         final ConnectionCallbackForDelayedMediaSession callback =
    776                 new ConnectionCallbackForDelayedMediaSession();
    777 
    778         getInstrumentation().runOnMainSync(new Runnable() {
    779             @Override
    780             public void run() {
    781                 mMediaBrowser = new MediaBrowserCompat(
    782                         getInstrumentation().getTargetContext(),
    783                         TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION,
    784                         callback,
    785                         null);
    786             }
    787         });
    788 
    789         synchronized (callback.mWaitLock) {
    790             mMediaBrowser.connect();
    791             callback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
    792             assertEquals(0, callback.mConnectedCount);
    793 
    794             callMediaBrowserServiceMethod(SET_SESSION_TOKEN, new Bundle(), getContext());
    795             callback.mWaitLock.wait(TIME_OUT_MS);
    796             assertEquals(1, callback.mConnectedCount);
    797 
    798             if (Build.VERSION.SDK_INT >= 21) {
    799                 assertNotNull(mMediaBrowser.getSessionToken().getExtraBinder());
    800             }
    801         }
    802     }
    803 
    804     private void connectMediaBrowserService() throws Exception {
    805         synchronized (mConnectionCallback.mWaitLock) {
    806             mMediaBrowser.connect();
    807             mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
    808             if (!mMediaBrowser.isConnected()) {
    809                 fail("Browser failed to connect!");
    810             }
    811         }
    812     }
    813 
    814     private class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
    815         final Object mWaitLock = new Object();
    816         volatile int mConnectedCount;
    817         volatile int mConnectionFailedCount;
    818         volatile int mConnectionSuspendedCount;
    819 
    820         public void reset() {
    821             mConnectedCount = 0;
    822             mConnectionFailedCount = 0;
    823             mConnectionSuspendedCount = 0;
    824         }
    825 
    826         @Override
    827         public void onConnected() {
    828             synchronized (mWaitLock) {
    829                 mConnectedCount++;
    830                 mWaitLock.notify();
    831             }
    832         }
    833 
    834         @Override
    835         public void onConnectionFailed() {
    836             synchronized (mWaitLock) {
    837                 mConnectionFailedCount++;
    838                 mWaitLock.notify();
    839             }
    840         }
    841 
    842         @Override
    843         public void onConnectionSuspended() {
    844             synchronized (mWaitLock) {
    845                 mConnectionSuspendedCount++;
    846                 mWaitLock.notify();
    847             }
    848         }
    849     }
    850 
    851     private class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
    852         private CountDownLatch mLatch;
    853         private volatile int mChildrenLoadedCount;
    854         private volatile int mChildrenLoadedWithOptionCount;
    855         private volatile String mLastErrorId;
    856         private volatile String mLastParentId;
    857         private volatile Bundle mLastOptions;
    858         private volatile List<MediaItem> mLastChildMediaItems;
    859 
    860         public void reset(int count) {
    861             mLatch = new CountDownLatch(count);
    862             mChildrenLoadedCount = 0;
    863             mChildrenLoadedWithOptionCount = 0;
    864             mLastErrorId = null;
    865             mLastParentId = null;
    866             mLastOptions = null;
    867             mLastChildMediaItems = null;
    868         }
    869 
    870         public boolean await(long timeoutMs) {
    871             try {
    872                 return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
    873             } catch (InterruptedException e) {
    874                 return false;
    875             }
    876         }
    877 
    878         @Override
    879         public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
    880             mChildrenLoadedCount++;
    881             mLastParentId = parentId;
    882             mLastChildMediaItems = children;
    883             mLatch.countDown();
    884         }
    885 
    886         @Override
    887         public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
    888                 @NonNull Bundle options) {
    889             mChildrenLoadedWithOptionCount++;
    890             mLastParentId = parentId;
    891             mLastOptions = options;
    892             mLastChildMediaItems = children;
    893             mLatch.countDown();
    894         }
    895 
    896         @Override
    897         public void onError(@NonNull String id) {
    898             mLastErrorId = id;
    899             mLatch.countDown();
    900         }
    901 
    902         @Override
    903         public void onError(@NonNull String id, @NonNull Bundle options) {
    904             mLastErrorId = id;
    905             mLastOptions = options;
    906             mLatch.countDown();
    907         }
    908     }
    909 
    910     private class StubItemCallback extends MediaBrowserCompat.ItemCallback {
    911         final Object mWaitLock = new Object();
    912         private volatile MediaItem mLastMediaItem;
    913         private volatile String mLastErrorId;
    914 
    915         public void reset() {
    916             mLastMediaItem = null;
    917             mLastErrorId = null;
    918         }
    919 
    920         @Override
    921         public void onItemLoaded(MediaItem item) {
    922             synchronized (mWaitLock) {
    923                 mLastMediaItem = item;
    924                 mWaitLock.notify();
    925             }
    926         }
    927 
    928         @Override
    929         public void onError(@NonNull String id) {
    930             synchronized (mWaitLock) {
    931                 mLastErrorId = id;
    932                 mWaitLock.notify();
    933             }
    934         }
    935     }
    936 
    937     private class StubSearchCallback extends MediaBrowserCompat.SearchCallback {
    938         final Object mWaitLock = new Object();
    939         boolean mOnSearchResult;
    940         Bundle mSearchExtras;
    941         List<MediaItem> mSearchResults;
    942 
    943         @Override
    944         public void onSearchResult(@NonNull String query, Bundle extras,
    945                 @NonNull List<MediaItem> items) {
    946             synchronized (mWaitLock) {
    947                 mOnSearchResult = true;
    948                 mSearchResults = items;
    949                 mSearchExtras = extras;
    950                 mWaitLock.notify();
    951             }
    952         }
    953 
    954         @Override
    955         public void onError(@NonNull String query, Bundle extras) {
    956             synchronized (mWaitLock) {
    957                 mOnSearchResult = true;
    958                 mSearchResults = null;
    959                 mSearchExtras = extras;
    960                 mWaitLock.notify();
    961             }
    962         }
    963 
    964         public void reset() {
    965             mOnSearchResult = false;
    966             mSearchExtras = null;
    967             mSearchResults = null;
    968         }
    969     }
    970 
    971     private class CustomActionCallback extends MediaBrowserCompat.CustomActionCallback {
    972         final Object mWaitLock = new Object();
    973         String mAction;
    974         Bundle mExtras;
    975         Bundle mData;
    976         boolean mOnProgressUpdateCalled;
    977         boolean mOnResultCalled;
    978         boolean mOnErrorCalled;
    979 
    980         @Override
    981         public void onProgressUpdate(String action, Bundle extras, Bundle data) {
    982             synchronized (mWaitLock) {
    983                 mOnProgressUpdateCalled = true;
    984                 mAction = action;
    985                 mExtras = extras;
    986                 mData = data;
    987                 mWaitLock.notify();
    988             }
    989         }
    990 
    991         @Override
    992         public void onResult(String action, Bundle extras, Bundle resultData) {
    993             synchronized (mWaitLock) {
    994                 mOnResultCalled = true;
    995                 mAction = action;
    996                 mExtras = extras;
    997                 mData = resultData;
    998                 mWaitLock.notify();
    999             }
   1000         }
   1001 
   1002         @Override
   1003         public void onError(String action, Bundle extras, Bundle data) {
   1004             synchronized (mWaitLock) {
   1005                 mOnErrorCalled = true;
   1006                 mAction = action;
   1007                 mExtras = extras;
   1008                 mData = data;
   1009                 mWaitLock.notify();
   1010             }
   1011         }
   1012 
   1013         public void reset() {
   1014             mOnResultCalled = false;
   1015             mOnProgressUpdateCalled = false;
   1016             mOnErrorCalled = false;
   1017             mAction = null;
   1018             mExtras = null;
   1019             mData = null;
   1020         }
   1021     }
   1022 
   1023     private class ConnectionCallbackForDelayedMediaSession extends
   1024             MediaBrowserCompat.ConnectionCallback {
   1025         final Object mWaitLock = new Object();
   1026         private int mConnectedCount = 0;
   1027 
   1028         @Override
   1029         public void onConnected() {
   1030             synchronized (mWaitLock) {
   1031                 mConnectedCount++;
   1032                 mWaitLock.notify();
   1033             }
   1034         }
   1035 
   1036         @Override
   1037         public void onConnectionFailed() {
   1038             synchronized (mWaitLock) {
   1039                 mWaitLock.notify();
   1040             }
   1041         }
   1042 
   1043         @Override
   1044         public void onConnectionSuspended() {
   1045             synchronized (mWaitLock) {
   1046                 mWaitLock.notify();
   1047             }
   1048         }
   1049     }
   1050 }
   1051