1 /* 2 * Copyright (C) 2022 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 package android.media.bettertogether.cts; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import static org.junit.Assert.assertThrows; 22 23 import android.app.Instrumentation; 24 import android.content.ComponentName; 25 import android.media.browse.MediaBrowser; 26 import android.media.browse.MediaBrowser.MediaItem; 27 import android.os.Bundle; 28 import android.platform.test.annotations.AppModeNonSdkSandbox; 29 30 import androidx.test.ext.junit.runners.AndroidJUnit4; 31 import androidx.test.platform.app.InstrumentationRegistry; 32 33 import com.android.compatibility.common.util.FrameworkSpecificTest; 34 import com.android.compatibility.common.util.NonMainlineTest; 35 import com.android.compatibility.common.util.PollingCheck; 36 37 import com.google.common.truth.Correspondence; 38 39 import org.junit.After; 40 import org.junit.Ignore; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.concurrent.atomic.AtomicReference; 48 49 /** 50 * Test {@link android.media.browse.MediaBrowser}. 51 */ 52 @FrameworkSpecificTest 53 @NonMainlineTest 54 @RunWith(AndroidJUnit4.class) 55 @AppModeNonSdkSandbox(reason = "SDK sandbox does not need MediaBrowser.") 56 public class MediaBrowserTest { 57 // The maximum time to wait for an operation. 58 private static final long TIME_OUT_MS = 3000L; 59 60 /** 61 * To check {@link MediaBrowser#unsubscribe} works properly, 62 * we notify to the browser after the unsubscription that the media items have changed. 63 * Then {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} should not be called. 64 * 65 * The measured time from calling {@link StubMediaBrowserService#notifyChildrenChanged} 66 * to {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} being called is about 50ms. 67 * So we make the thread sleep for 100ms to properly check that the callback is not called. 68 */ 69 private static final long SLEEP_MS = 100L; 70 private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName( 71 "android.media.bettertogether.cts", 72 "android.media.bettertogether.cts.StubMediaBrowserService"); 73 private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName( 74 "invalid.package", "invalid.ServiceClassName"); 75 76 private static final Correspondence<MediaBrowser.MediaItem, String> 77 MEDIA_ITEM_HAS_ID = 78 Correspondence.from((MediaBrowser.MediaItem actual, String expected) -> { 79 if (actual == null) { 80 return expected == null; 81 } 82 83 return actual.getMediaId().equals(expected); 84 }, "has an ID of"); 85 86 private final StubConnectionCallback mConnectionCallback = new StubConnectionCallback(); 87 private final StubSubscriptionCallback mSubscriptionCallback = new StubSubscriptionCallback(); 88 private final StubItemCallback mItemCallback = new StubItemCallback(); 89 90 private MediaBrowser mMediaBrowser; 91 getInstrumentation()92 private Instrumentation getInstrumentation() { 93 return InstrumentationRegistry.getInstrumentation(); 94 } 95 96 @After tearDown()97 public void tearDown() { 98 if (mMediaBrowser != null) { 99 try { 100 disconnectMediaBrowser(); 101 } catch (Throwable t) { 102 // Ignore. 103 } 104 mMediaBrowser = null; 105 } 106 } 107 108 @Test testMediaBrowser()109 public void testMediaBrowser() throws Throwable { 110 resetCallbacks(); 111 createMediaBrowser(TEST_BROWSER_SERVICE); 112 runOnMainThread(() -> assertThat(mMediaBrowser.isConnected()).isFalse()); 113 114 connectMediaBrowserService(); 115 runOnMainThread(() -> assertThat(mMediaBrowser.isConnected()).isTrue()); 116 117 runOnMainThread(() -> { 118 assertThat(mMediaBrowser.getServiceComponent()) 119 .isEqualTo(TEST_BROWSER_SERVICE); 120 assertThat(mMediaBrowser.getRoot()) 121 .isEqualTo(StubMediaBrowserService.MEDIA_ID_ROOT); 122 assertThat(mMediaBrowser.getExtras().getString(StubMediaBrowserService.EXTRAS_KEY)) 123 .isEqualTo(StubMediaBrowserService.EXTRAS_VALUE); 124 assertThat(mMediaBrowser.getSessionToken()) 125 .isEqualTo(StubMediaBrowserService.sSession.getSessionToken()); 126 }); 127 128 disconnectMediaBrowser(); 129 runOnMainThread(() -> new PollingCheck(TIME_OUT_MS) { 130 @Override 131 protected boolean check() { 132 return !mMediaBrowser.isConnected(); 133 } 134 }.run()); 135 } 136 137 @Test testThrowingISEWhileNotConnected()138 public void testThrowingISEWhileNotConnected() throws Throwable { 139 resetCallbacks(); 140 createMediaBrowser(TEST_BROWSER_SERVICE); 141 runOnMainThread(() -> assertThat(mMediaBrowser.isConnected()).isFalse()); 142 143 runOnMainThread(() -> { 144 assertThrows(IllegalStateException.class, 145 () -> mMediaBrowser.getExtras()); 146 147 assertThrows(IllegalStateException.class, 148 () -> mMediaBrowser.getRoot()); 149 150 assertThrows(IllegalStateException.class, 151 () -> mMediaBrowser.getServiceComponent()); 152 153 assertThrows(IllegalStateException.class, 154 () -> mMediaBrowser.getSessionToken()); 155 }); 156 } 157 158 @Test testConnectTwice()159 public void testConnectTwice() throws Throwable { 160 resetCallbacks(); 161 createMediaBrowser(TEST_BROWSER_SERVICE); 162 connectMediaBrowserService(); 163 runOnMainThread(() -> { 164 assertThrows(IllegalStateException.class, 165 () -> mMediaBrowser.connect()); 166 }); 167 } 168 169 @Test testConnectionFailed()170 public void testConnectionFailed() throws Throwable { 171 resetCallbacks(); 172 createMediaBrowser(TEST_INVALID_BROWSER_SERVICE); 173 174 runOnMainThread(() -> mMediaBrowser.connect()); 175 176 new PollingCheck(TIME_OUT_MS) { 177 @Override 178 protected boolean check() { 179 return mConnectionCallback.mConnectionFailedCount > 0 180 && mConnectionCallback.mConnectedCount == 0 181 && mConnectionCallback.mConnectionSuspendedCount == 0; 182 } 183 }.run(); 184 } 185 186 @Test testReconnection()187 public void testReconnection() throws Throwable { 188 createMediaBrowser(TEST_BROWSER_SERVICE); 189 190 runOnMainThread(() -> { 191 // Reconnect before the first connection was established. 192 mMediaBrowser.connect(); 193 mMediaBrowser.disconnect(); 194 }); 195 resetCallbacks(); 196 connectMediaBrowserService(); 197 198 // Test subscribe. 199 resetCallbacks(); 200 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 201 mSubscriptionCallback)); 202 new PollingCheck(TIME_OUT_MS) { 203 @Override 204 protected boolean check() { 205 return mSubscriptionCallback.mChildrenLoadedCount > 0; 206 } 207 }.run(); 208 209 // Test getItem. 210 resetCallbacks(); 211 runOnMainThread(() -> mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], 212 mItemCallback)); 213 new PollingCheck(TIME_OUT_MS) { 214 @Override 215 protected boolean check() { 216 return mItemCallback.mLastMediaItem != null; 217 } 218 }.run(); 219 220 // Reconnect after connection was established. 221 disconnectMediaBrowser(); 222 resetCallbacks(); 223 connectMediaBrowserService(); 224 225 // Test getItem. 226 resetCallbacks(); 227 runOnMainThread(() -> mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], 228 mItemCallback)); 229 new PollingCheck(TIME_OUT_MS) { 230 @Override 231 protected boolean check() { 232 return mItemCallback.mLastMediaItem != null; 233 } 234 }.run(); 235 } 236 237 @Test testConnectionCallbackNotCalledAfterDisconnect()238 public void testConnectionCallbackNotCalledAfterDisconnect() throws Throwable { 239 createMediaBrowser(TEST_BROWSER_SERVICE); 240 runOnMainThread(() -> { 241 mMediaBrowser.connect(); 242 mMediaBrowser.disconnect(); 243 }); 244 resetCallbacks(); 245 try { 246 Thread.sleep(SLEEP_MS); 247 } catch (InterruptedException e) { 248 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 249 } 250 251 assertThat(mConnectionCallback.mConnectedCount) 252 .isEqualTo(0); 253 assertThat(mConnectionCallback.mConnectionFailedCount) 254 .isEqualTo(0); 255 assertThat(mConnectionCallback.mConnectionSuspendedCount) 256 .isEqualTo(0); 257 } 258 259 @Test testSubscribe()260 public void testSubscribe() throws Throwable { 261 resetCallbacks(); 262 createMediaBrowser(TEST_BROWSER_SERVICE); 263 connectMediaBrowserService(); 264 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 265 mSubscriptionCallback)); 266 new PollingCheck(TIME_OUT_MS) { 267 @Override 268 protected boolean check() { 269 return mSubscriptionCallback.mChildrenLoadedCount > 0; 270 } 271 }.run(); 272 273 assertThat(mSubscriptionCallback.mLastParentId) 274 .isEqualTo(StubMediaBrowserService.MEDIA_ID_ROOT); 275 assertThat(mSubscriptionCallback.mLastChildMediaItems) 276 .hasSize(StubMediaBrowserService.MEDIA_ID_CHILDREN.length); 277 278 assertThat(mSubscriptionCallback.mLastChildMediaItems) 279 .comparingElementsUsing(MEDIA_ITEM_HAS_ID) 280 .containsExactlyElementsIn(StubMediaBrowserService.MEDIA_ID_CHILDREN) 281 .inOrder(); 282 283 // Test unsubscribe. 284 resetCallbacks(); 285 runOnMainThread(() -> mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT)); 286 287 // After unsubscribing, make StubMediaBrowserService notify that the children are changed. 288 StubMediaBrowserService.sInstance.notifyChildrenChanged( 289 StubMediaBrowserService.MEDIA_ID_ROOT); 290 try { 291 Thread.sleep(SLEEP_MS); 292 } catch (InterruptedException e) { 293 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 294 } 295 // onChildrenLoaded should not be called. 296 assertThat(mSubscriptionCallback.mChildrenLoadedCount) 297 .isEqualTo(0); 298 } 299 300 @Test testSubscribeWithIllegalArguments()301 public void testSubscribeWithIllegalArguments() throws Throwable { 302 createMediaBrowser(TEST_BROWSER_SERVICE); 303 304 runOnMainThread(() -> { 305 assertThrows(IllegalArgumentException.class, () -> { 306 final String nullMediaId = null; 307 mMediaBrowser.subscribe(nullMediaId, mSubscriptionCallback); 308 }); 309 310 assertThrows(IllegalArgumentException.class, () -> { 311 final String emptyMediaId = ""; 312 mMediaBrowser.subscribe(emptyMediaId, mSubscriptionCallback); 313 }); 314 315 assertThrows(IllegalArgumentException.class, () -> { 316 final MediaBrowser.SubscriptionCallback nullCallback = null; 317 mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback); 318 }); 319 320 assertThrows(IllegalArgumentException.class, () -> { 321 final Bundle nullOptions = null; 322 mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullOptions, 323 mSubscriptionCallback); 324 }); 325 }); 326 } 327 328 @Test testSubscribeWithOptions()329 public void testSubscribeWithOptions() throws Throwable { 330 createMediaBrowser(TEST_BROWSER_SERVICE); 331 connectMediaBrowserService(); 332 final int pageSize = 3; 333 final int lastPage = (StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) / pageSize; 334 Bundle options = new Bundle(); 335 options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); 336 for (int page = 0; page <= lastPage; ++page) { 337 resetCallbacks(); 338 options.putInt(MediaBrowser.EXTRA_PAGE, page); 339 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 340 options, mSubscriptionCallback)); 341 new PollingCheck(TIME_OUT_MS) { 342 @Override 343 protected boolean check() { 344 return mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0; 345 } 346 }.run(); 347 assertThat(mSubscriptionCallback.mLastParentId) 348 .isEqualTo(StubMediaBrowserService.MEDIA_ID_ROOT); 349 if (page != lastPage) { 350 assertThat(mSubscriptionCallback.mLastChildMediaItems).hasSize(pageSize); 351 } else { 352 assertThat(mSubscriptionCallback.mLastChildMediaItems) 353 .hasSize((StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) 354 % pageSize + 1); 355 } 356 357 final int lastChildMediaItemsCount = mSubscriptionCallback.mLastChildMediaItems.size(); 358 359 final String[] expectedMediaIds = getMediaIdsCurrentPage( 360 StubMediaBrowserService.MEDIA_ID_CHILDREN, 361 page, pageSize, lastChildMediaItemsCount); 362 363 // Check whether all the items in the current page are loaded. 364 assertThat(mSubscriptionCallback.mLastChildMediaItems) 365 .comparingElementsUsing(MEDIA_ITEM_HAS_ID) 366 .containsExactlyElementsIn(expectedMediaIds) 367 .inOrder(); 368 } 369 370 // Test unsubscribe with callback argument. 371 resetCallbacks(); 372 runOnMainThread(() -> mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 373 mSubscriptionCallback)); 374 375 // After unsubscribing, make StubMediaBrowserService notify that the children are changed. 376 StubMediaBrowserService.sInstance.notifyChildrenChanged( 377 StubMediaBrowserService.MEDIA_ID_ROOT); 378 try { 379 Thread.sleep(SLEEP_MS); 380 } catch (InterruptedException e) { 381 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 382 } 383 // onChildrenLoaded should not be called. 384 assertThat(mSubscriptionCallback.mChildrenLoadedCount).isEqualTo(0); 385 } 386 387 @Test testSubscribeInvalidItem()388 public void testSubscribeInvalidItem() throws Throwable { 389 resetCallbacks(); 390 createMediaBrowser(TEST_BROWSER_SERVICE); 391 connectMediaBrowserService(); 392 runOnMainThread(() -> mMediaBrowser.subscribe( 393 StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback)); 394 new PollingCheck(TIME_OUT_MS) { 395 @Override 396 protected boolean check() { 397 return mSubscriptionCallback.mLastErrorId != null; 398 } 399 }.run(); 400 401 assertThat(mSubscriptionCallback.mLastErrorId) 402 .isEqualTo(StubMediaBrowserService.MEDIA_ID_INVALID); 403 } 404 405 @Test testSubscribeInvalidItemWithOptions()406 public void testSubscribeInvalidItemWithOptions() throws Throwable { 407 resetCallbacks(); 408 createMediaBrowser(TEST_BROWSER_SERVICE); 409 connectMediaBrowserService(); 410 411 final int pageSize = 5; 412 final int page = 2; 413 Bundle options = new Bundle(); 414 options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); 415 options.putInt(MediaBrowser.EXTRA_PAGE, page); 416 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_INVALID, 417 options, mSubscriptionCallback)); 418 new PollingCheck(TIME_OUT_MS) { 419 @Override 420 protected boolean check() { 421 return mSubscriptionCallback.mLastErrorId != null; 422 } 423 }.run(); 424 425 assertThat(mSubscriptionCallback.mLastErrorId) 426 .isEqualTo(StubMediaBrowserService.MEDIA_ID_INVALID); 427 assertThat(mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE)) 428 .isEqualTo(page); 429 assertThat(mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE)) 430 .isEqualTo(pageSize); 431 } 432 433 @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable. 434 @Test testSubscriptionCallbackNotCalledAfterDisconnect()435 public void testSubscriptionCallbackNotCalledAfterDisconnect() throws Throwable { 436 createMediaBrowser(TEST_BROWSER_SERVICE); 437 connectMediaBrowserService(); 438 runOnMainThread(() -> { 439 mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback); 440 mMediaBrowser.disconnect(); 441 }); 442 resetCallbacks(); 443 StubMediaBrowserService.sInstance.notifyChildrenChanged( 444 StubMediaBrowserService.MEDIA_ID_ROOT); 445 try { 446 Thread.sleep(SLEEP_MS); 447 } catch (InterruptedException e) { 448 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 449 } 450 assertThat(mSubscriptionCallback.mChildrenLoadedCount).isEqualTo(0); 451 assertThat(mSubscriptionCallback.mChildrenLoadedWithOptionCount).isEqualTo(0); 452 assertThat(mSubscriptionCallback.mLastParentId).isNull(); 453 } 454 455 @Test testUnsubscribeWithIllegalArguments()456 public void testUnsubscribeWithIllegalArguments() throws Throwable { 457 createMediaBrowser(TEST_BROWSER_SERVICE); 458 runOnMainThread(() -> { 459 assertThrows(IllegalArgumentException.class, () -> { 460 final String nullMediaId = null; 461 mMediaBrowser.unsubscribe(nullMediaId); 462 }); 463 464 assertThrows(IllegalArgumentException.class, () -> { 465 final String emptyMediaId = ""; 466 mMediaBrowser.unsubscribe(emptyMediaId); 467 }); 468 469 assertThrows(IllegalArgumentException.class, () -> { 470 final MediaBrowser.SubscriptionCallback nullCallback = null; 471 mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback); 472 }); 473 }); 474 } 475 476 @Test testUnsubscribeForMultipleSubscriptions()477 public void testUnsubscribeForMultipleSubscriptions() throws Throwable { 478 createMediaBrowser(TEST_BROWSER_SERVICE); 479 connectMediaBrowserService(); 480 final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>(); 481 final int pageSize = 1; 482 483 // Subscribe four pages, one item per page. 484 for (int page = 0; page < 4; page++) { 485 final StubSubscriptionCallback callback = new StubSubscriptionCallback(); 486 subscriptionCallbacks.add(callback); 487 488 Bundle options = new Bundle(); 489 options.putInt(MediaBrowser.EXTRA_PAGE, page); 490 options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); 491 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 492 options, callback)); 493 494 // Each onChildrenLoaded() must be called. 495 new PollingCheck(TIME_OUT_MS) { 496 @Override 497 protected boolean check() { 498 return callback.mChildrenLoadedWithOptionCount == 1; 499 } 500 }.run(); 501 } 502 503 // Reset callbacks and unsubscribe. 504 for (StubSubscriptionCallback callback : subscriptionCallbacks) { 505 callback.reset(); 506 } 507 runOnMainThread(() -> mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT)); 508 509 // After unsubscribing, make StubMediaBrowserService notify that the children are changed. 510 StubMediaBrowserService.sInstance.notifyChildrenChanged( 511 StubMediaBrowserService.MEDIA_ID_ROOT); 512 try { 513 Thread.sleep(SLEEP_MS); 514 } catch (InterruptedException e) { 515 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 516 } 517 518 // onChildrenLoaded should not be called. 519 for (StubSubscriptionCallback callback : subscriptionCallbacks) { 520 assertThat(callback.mChildrenLoadedWithOptionCount).isEqualTo(0); 521 } 522 } 523 524 @Test testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions()525 public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Throwable { 526 createMediaBrowser(TEST_BROWSER_SERVICE); 527 connectMediaBrowserService(); 528 final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>(); 529 final int pageSize = 1; 530 531 // Subscribe four pages, one item per page. 532 for (int page = 0; page < 4; page++) { 533 final StubSubscriptionCallback callback = new StubSubscriptionCallback(); 534 subscriptionCallbacks.add(callback); 535 536 Bundle options = new Bundle(); 537 options.putInt(MediaBrowser.EXTRA_PAGE, page); 538 options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize); 539 runOnMainThread(() -> mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 540 options, callback)); 541 542 // Each onChildrenLoaded() must be called. 543 new PollingCheck(TIME_OUT_MS) { 544 @Override 545 protected boolean check() { 546 return callback.mChildrenLoadedWithOptionCount == 1; 547 } 548 }.run(); 549 } 550 551 // Unsubscribe existing subscriptions one-by-one. 552 final int[] orderOfRemovingCallbacks = {2, 0, 3, 1}; 553 for (int i = 0; i < orderOfRemovingCallbacks.length; i++) { 554 // Reset callbacks 555 for (StubSubscriptionCallback callback : subscriptionCallbacks) { 556 callback.reset(); 557 } 558 559 final int index = i; 560 runOnMainThread(() -> { 561 // Remove one subscription 562 mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, 563 subscriptionCallbacks.get(orderOfRemovingCallbacks[index])); 564 }); 565 566 // Make StubMediaBrowserService notify that the children are changed. 567 StubMediaBrowserService.sInstance.notifyChildrenChanged( 568 StubMediaBrowserService.MEDIA_ID_ROOT); 569 try { 570 Thread.sleep(SLEEP_MS); 571 } catch (InterruptedException e) { 572 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 573 } 574 575 // Only the remaining subscriptionCallbacks should be called. 576 for (int j = 0; j < 4; j++) { 577 int childrenLoadedWithOptionsCount = subscriptionCallbacks 578 .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount; 579 if (j <= i) { 580 assertThat(childrenLoadedWithOptionsCount) 581 .isEqualTo(0); 582 } else { 583 assertThat(childrenLoadedWithOptionsCount) 584 .isEqualTo(1); 585 } 586 } 587 } 588 } 589 590 @Test testGetItem()591 public void testGetItem() throws Throwable { 592 resetCallbacks(); 593 createMediaBrowser(TEST_BROWSER_SERVICE); 594 connectMediaBrowserService(); 595 596 runOnMainThread(() -> mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], 597 mItemCallback)); 598 new PollingCheck(TIME_OUT_MS) { 599 @Override 600 protected boolean check() { 601 return mItemCallback.mLastMediaItem != null; 602 } 603 }.run(); 604 605 assertThat(mItemCallback.mLastMediaItem.getMediaId()) 606 .isEqualTo(StubMediaBrowserService.MEDIA_ID_CHILDREN[0]); 607 } 608 609 @Test testGetItemThrowsIAE()610 public void testGetItemThrowsIAE() throws Throwable { 611 resetCallbacks(); 612 createMediaBrowser(TEST_BROWSER_SERVICE); 613 614 runOnMainThread(() -> { 615 assertThrows(IllegalArgumentException.class, () -> { 616 // Calling getItem() with empty mediaId will throw IAE. 617 mMediaBrowser.getItem("", mItemCallback); 618 }); 619 620 assertThrows(IllegalArgumentException.class, () -> { 621 // Calling getItem() with null mediaId will throw IAE. 622 mMediaBrowser.getItem(null, mItemCallback); 623 }); 624 625 assertThrows(IllegalArgumentException.class, () -> { 626 // Calling getItem() with null itemCallback will throw IAE. 627 mMediaBrowser.getItem("media_id", null); 628 }); 629 }); 630 } 631 632 @Test testGetItemWhileNotConnected()633 public void testGetItemWhileNotConnected() throws Throwable { 634 resetCallbacks(); 635 createMediaBrowser(TEST_BROWSER_SERVICE); 636 637 final String mediaId = "test_media_id"; 638 runOnMainThread(() -> mMediaBrowser.getItem(mediaId, mItemCallback)); 639 640 // Calling getItem while not connected will invoke ItemCallback.onError(). 641 new PollingCheck(TIME_OUT_MS) { 642 @Override 643 protected boolean check() { 644 return mItemCallback.mLastErrorId != null; 645 } 646 }.run(); 647 648 assertThat(mItemCallback.mLastErrorId) 649 .isEqualTo(mediaId); 650 } 651 652 @Test testGetItemFailure()653 public void testGetItemFailure() throws Throwable { 654 resetCallbacks(); 655 createMediaBrowser(TEST_BROWSER_SERVICE); 656 connectMediaBrowserService(); 657 runOnMainThread(() -> mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_INVALID, 658 mItemCallback)); 659 new PollingCheck(TIME_OUT_MS) { 660 @Override 661 protected boolean check() { 662 return mItemCallback.mLastErrorId != null; 663 } 664 }.run(); 665 666 assertThat(mItemCallback.mLastErrorId) 667 .isEqualTo(StubMediaBrowserService.MEDIA_ID_INVALID); 668 } 669 670 @Test testItemCallbackNotCalledAfterDisconnect()671 public void testItemCallbackNotCalledAfterDisconnect() throws Throwable { 672 createMediaBrowser(TEST_BROWSER_SERVICE); 673 connectMediaBrowserService(); 674 runOnMainThread(() -> { 675 mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback); 676 mMediaBrowser.disconnect(); 677 }); 678 resetCallbacks(); 679 try { 680 Thread.sleep(SLEEP_MS); 681 } catch (InterruptedException e) { 682 assertWithMessage("Unexpected InterruptedException occurred.").fail(); 683 } 684 assertThat(mItemCallback.mLastMediaItem).isNull(); 685 assertThat(mItemCallback.mLastErrorId).isNull(); 686 } 687 createMediaBrowser(final ComponentName component)688 private void createMediaBrowser(final ComponentName component) throws Throwable { 689 runOnMainThread(() -> mMediaBrowser = new MediaBrowser( 690 getInstrumentation().getTargetContext(), component, mConnectionCallback, null)); 691 } 692 connectMediaBrowserService()693 private void connectMediaBrowserService() throws Throwable { 694 runOnMainThread(() -> mMediaBrowser.connect()); 695 new PollingCheck(TIME_OUT_MS) { 696 @Override 697 protected boolean check() { 698 return mConnectionCallback.mConnectedCount > 0; 699 } 700 }.run(); 701 } 702 disconnectMediaBrowser()703 private void disconnectMediaBrowser() throws Throwable { 704 runOnMainThread(() -> mMediaBrowser.disconnect()); 705 } 706 resetCallbacks()707 private void resetCallbacks() { 708 mConnectionCallback.reset(); 709 mSubscriptionCallback.reset(); 710 mItemCallback.reset(); 711 } 712 runOnMainThread(Runnable runnable)713 private void runOnMainThread(Runnable runnable) throws Throwable { 714 AtomicReference<Throwable> throwableRef = new AtomicReference<>(); 715 716 getInstrumentation().runOnMainSync(() -> { 717 try { 718 runnable.run(); 719 } catch (Throwable t) { 720 throwableRef.set(t); 721 } 722 }); 723 724 Throwable t = throwableRef.get(); 725 if (t != null) { 726 throw t; 727 } 728 } 729 getMediaIdsCurrentPage( String[] mediaIds, int pageIndex, int pageSize, int itemsCount)730 private static String[] getMediaIdsCurrentPage( 731 String[] mediaIds, int pageIndex, int pageSize, int itemsCount) { 732 final int pageOffset = pageIndex * pageSize; 733 return Arrays.copyOfRange(mediaIds, pageOffset, pageOffset + itemsCount); 734 } 735 736 private static class StubConnectionCallback extends MediaBrowser.ConnectionCallback { 737 volatile int mConnectedCount; 738 volatile int mConnectionFailedCount; 739 volatile int mConnectionSuspendedCount; 740 reset()741 public void reset() { 742 mConnectedCount = 0; 743 mConnectionFailedCount = 0; 744 mConnectionSuspendedCount = 0; 745 } 746 747 @Override onConnected()748 public void onConnected() { 749 mConnectedCount++; 750 } 751 752 @Override onConnectionFailed()753 public void onConnectionFailed() { 754 mConnectionFailedCount++; 755 } 756 757 @Override onConnectionSuspended()758 public void onConnectionSuspended() { 759 mConnectionSuspendedCount++; 760 } 761 } 762 763 private static class StubSubscriptionCallback extends MediaBrowser.SubscriptionCallback { 764 private volatile int mChildrenLoadedCount; 765 private volatile int mChildrenLoadedWithOptionCount; 766 private volatile String mLastErrorId; 767 private volatile String mLastParentId; 768 private volatile Bundle mLastOptions; 769 private volatile List<MediaBrowser.MediaItem> mLastChildMediaItems; 770 reset()771 public void reset() { 772 mChildrenLoadedCount = 0; 773 mChildrenLoadedWithOptionCount = 0; 774 mLastErrorId = null; 775 mLastParentId = null; 776 mLastOptions = null; 777 mLastChildMediaItems = null; 778 } 779 780 @Override onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children)781 public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { 782 mChildrenLoadedCount++; 783 mLastParentId = parentId; 784 mLastChildMediaItems = children; 785 } 786 787 @Override onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children, Bundle options)788 public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children, 789 Bundle options) { 790 mChildrenLoadedWithOptionCount++; 791 mLastParentId = parentId; 792 mLastOptions = options; 793 mLastChildMediaItems = children; 794 } 795 796 @Override onError(String id)797 public void onError(String id) { 798 mLastErrorId = id; 799 } 800 801 @Override onError(String id, Bundle options)802 public void onError(String id, Bundle options) { 803 mLastErrorId = id; 804 mLastOptions = options; 805 } 806 } 807 808 private static class StubItemCallback extends MediaBrowser.ItemCallback { 809 private volatile MediaBrowser.MediaItem mLastMediaItem; 810 private volatile String mLastErrorId; 811 reset()812 public void reset() { 813 mLastMediaItem = null; 814 mLastErrorId = null; 815 } 816 817 @Override onItemLoaded(MediaItem item)818 public void onItemLoaded(MediaItem item) { 819 mLastMediaItem = item; 820 } 821 822 @Override onError(String id)823 public void onError(String id) { 824 mLastErrorId = id; 825 } 826 } 827 } 828