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