1 /* 2 * Copyright 2018 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 androidx.media; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotEquals; 22 import static org.junit.Assert.assertNotNull; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import android.app.PendingIntent; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.media.AudioManager; 31 import android.net.Uri; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.Process; 37 import android.os.ResultReceiver; 38 import android.support.test.filters.FlakyTest; 39 import android.support.test.filters.SdkSuppress; 40 import android.support.test.filters.SmallTest; 41 import android.support.test.runner.AndroidJUnit4; 42 43 import androidx.annotation.NonNull; 44 import androidx.media.MediaController2.ControllerCallback; 45 import androidx.media.MediaController2.PlaybackInfo; 46 import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback; 47 import androidx.media.MediaSession2.ControllerInfo; 48 import androidx.media.MediaSession2.SessionCallback; 49 import androidx.media.TestServiceRegistry.SessionServiceCallback; 50 import androidx.media.TestUtils.SyncHandler; 51 import androidx.testutils.PollingCheck; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 import java.lang.reflect.Method; 59 import java.util.List; 60 import java.util.concurrent.CountDownLatch; 61 import java.util.concurrent.TimeUnit; 62 import java.util.concurrent.atomic.AtomicReference; 63 64 /** 65 * Tests {@link MediaController2}. 66 */ 67 // TODO(jaewan): Implement host-side test so controller and session can run in different processes. 68 // TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController() 69 // TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary. 70 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) 71 @RunWith(AndroidJUnit4.class) 72 @SmallTest 73 @FlakyTest 74 public class MediaController2Test extends MediaSession2TestBase { 75 private static final String TAG = "MediaController2Test"; 76 77 PendingIntent mIntent; 78 MediaSession2 mSession; 79 MediaController2 mController; 80 MockPlayer mPlayer; 81 MockPlaylistAgent mMockAgent; 82 AudioManager mAudioManager; 83 84 @Before 85 @Override 86 public void setUp() throws Exception { 87 super.setUp(); 88 final Intent sessionActivity = new Intent(mContext, MockActivity.class); 89 // Create this test specific MediaSession2 to use our own Handler. 90 mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0); 91 92 mPlayer = new MockPlayer(1); 93 mMockAgent = new MockPlaylistAgent(); 94 mSession = new MediaSession2.Builder(mContext) 95 .setPlayer(mPlayer) 96 .setPlaylistAgent(mMockAgent) 97 .setSessionCallback(sHandlerExecutor, new SessionCallback() { 98 @Override 99 public SessionCommandGroup2 onConnect(MediaSession2 session, 100 ControllerInfo controller) { 101 if (Process.myUid() == controller.getUid()) { 102 return super.onConnect(session, controller); 103 } 104 return null; 105 } 106 107 @Override 108 public void onPlaylistMetadataChanged(MediaSession2 session, 109 MediaPlaylistAgent playlistAgent, 110 MediaMetadata2 metadata) { 111 super.onPlaylistMetadataChanged(session, playlistAgent, metadata); 112 } 113 }) 114 .setSessionActivity(mIntent) 115 .setId(TAG).build(); 116 mController = createController(mSession.getToken()); 117 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 118 TestServiceRegistry.getInstance().setHandler(sHandler); 119 } 120 121 @After 122 @Override 123 public void cleanUp() throws Exception { 124 super.cleanUp(); 125 if (mSession != null) { 126 mSession.close(); 127 } 128 TestServiceRegistry.getInstance().cleanUp(); 129 } 130 131 /** 132 * Test if the {@link MediaSession2TestBase.TestControllerCallback} wraps the callback proxy 133 * without missing any method. 134 */ 135 @Test 136 public void testTestControllerCallback() { 137 prepareLooper(); 138 Method[] methods = TestControllerCallback.class.getMethods(); 139 assertNotNull(methods); 140 for (int i = 0; i < methods.length; i++) { 141 // For any methods in the controller callback, TestControllerCallback should have 142 // overriden the method and call matching API in the callback proxy. 143 assertNotEquals("TestControllerCallback should override " + methods[i] 144 + " and call callback proxy", 145 ControllerCallback.class, methods[i].getDeclaringClass()); 146 } 147 } 148 149 @Test 150 public void testPlay() { 151 prepareLooper(); 152 mController.play(); 153 try { 154 assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 155 } catch (InterruptedException e) { 156 fail(e.getMessage()); 157 } 158 assertTrue(mPlayer.mPlayCalled); 159 } 160 161 @Test 162 public void testPause() { 163 prepareLooper(); 164 mController.pause(); 165 try { 166 assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 167 } catch (InterruptedException e) { 168 fail(e.getMessage()); 169 } 170 assertTrue(mPlayer.mPauseCalled); 171 } 172 173 @Test 174 public void testReset() { 175 prepareLooper(); 176 mController.reset(); 177 try { 178 assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 179 } catch (InterruptedException e) { 180 fail(e.getMessage()); 181 } 182 assertTrue(mPlayer.mResetCalled); 183 } 184 185 @Test 186 public void testPrepare() { 187 prepareLooper(); 188 mController.prepare(); 189 try { 190 assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 191 } catch (InterruptedException e) { 192 fail(e.getMessage()); 193 } 194 assertTrue(mPlayer.mPrepareCalled); 195 } 196 197 @Test 198 public void testSeekTo() { 199 prepareLooper(); 200 final long seekPosition = 12125L; 201 mController.seekTo(seekPosition); 202 try { 203 assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 204 } catch (InterruptedException e) { 205 fail(e.getMessage()); 206 } 207 assertTrue(mPlayer.mSeekToCalled); 208 assertEquals(seekPosition, mPlayer.mSeekPosition); 209 } 210 211 @Test 212 public void testGettersAfterConnected() throws InterruptedException { 213 prepareLooper(); 214 final int state = MediaPlayerInterface.PLAYER_STATE_PLAYING; 215 final int bufferingState = MediaPlayerInterface.BUFFERING_STATE_BUFFERING_COMPLETE; 216 final long position = 150000; 217 final long bufferedPosition = 900000; 218 final float speed = 0.5f; 219 final long timeDiff = 102; 220 final MediaItem2 currentMediaItem = TestUtils.createMediaItemWithMetadata(); 221 222 mPlayer.mLastPlayerState = state; 223 mPlayer.mLastBufferingState = bufferingState; 224 mPlayer.mCurrentPosition = position; 225 mPlayer.mBufferedPosition = bufferedPosition; 226 mPlayer.mPlaybackSpeed = speed; 227 mMockAgent.mCurrentMediaItem = currentMediaItem; 228 229 MediaController2 controller = createController(mSession.getToken()); 230 controller.setTimeDiff(timeDiff); 231 assertEquals(state, controller.getPlayerState()); 232 assertEquals(bufferedPosition, controller.getBufferedPosition()); 233 assertEquals(speed, controller.getPlaybackSpeed(), 0.0f); 234 assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition()); 235 assertEquals(currentMediaItem, controller.getCurrentMediaItem()); 236 } 237 238 @Test 239 public void testUpdatePlayer() throws InterruptedException { 240 prepareLooper(); 241 final int testState = MediaPlayerInterface.PLAYER_STATE_PLAYING; 242 final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3); 243 final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder() 244 .setLegacyStreamType(AudioManager.STREAM_RING).build(); 245 final CountDownLatch latch = new CountDownLatch(3); 246 mController = createController(mSession.getToken(), true, new ControllerCallback() { 247 @Override 248 public void onPlayerStateChanged(MediaController2 controller, int state) { 249 assertEquals(mController, controller); 250 assertEquals(testState, state); 251 latch.countDown(); 252 } 253 254 @Override 255 public void onPlaylistChanged(MediaController2 controller, List<MediaItem2> list, 256 MediaMetadata2 metadata) { 257 assertEquals(mController, controller); 258 assertEquals(testPlaylist, list); 259 assertNull(metadata); 260 latch.countDown(); 261 } 262 263 @Override 264 public void onPlaybackInfoChanged(MediaController2 controller, PlaybackInfo info) { 265 assertEquals(mController, controller); 266 assertEquals(testAudioAttributes, info.getAudioAttributes()); 267 latch.countDown(); 268 } 269 }); 270 271 MockPlayer player = new MockPlayer(0); 272 player.mLastPlayerState = testState; 273 player.setAudioAttributes(testAudioAttributes); 274 275 MockPlaylistAgent agent = new MockPlaylistAgent(); 276 agent.mPlaylist = testPlaylist; 277 278 mSession.updatePlayer(player, agent, null); 279 assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 280 } 281 282 @Test 283 public void testGetSessionActivity() { 284 prepareLooper(); 285 PendingIntent sessionActivity = mController.getSessionActivity(); 286 assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage()); 287 assertEquals(Process.myUid(), sessionActivity.getCreatorUid()); 288 } 289 290 @Test 291 public void testSetPlaylist() throws InterruptedException { 292 prepareLooper(); 293 final List<MediaItem2> list = TestUtils.createPlaylist(2); 294 mController.setPlaylist(list, null /* Metadata */); 295 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 296 297 assertTrue(mMockAgent.mSetPlaylistCalled); 298 assertNull(mMockAgent.mMetadata); 299 300 assertNotNull(mMockAgent.mPlaylist); 301 assertEquals(list.size(), mMockAgent.mPlaylist.size()); 302 for (int i = 0; i < list.size(); i++) { 303 // MediaController2.setPlaylist does not ensure the equality of the items. 304 assertEquals(list.get(i).getMediaId(), mMockAgent.mPlaylist.get(i).getMediaId()); 305 } 306 } 307 308 /** 309 * This also tests {@link ControllerCallback#onPlaylistChanged( 310 * MediaController2, List, MediaMetadata2)}. 311 */ 312 @Test 313 public void testGetPlaylist() throws InterruptedException { 314 prepareLooper(); 315 final List<MediaItem2> testList = TestUtils.createPlaylist(2); 316 final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>(); 317 final CountDownLatch latch = new CountDownLatch(1); 318 final ControllerCallback callback = new ControllerCallback() { 319 @Override 320 public void onPlaylistChanged(MediaController2 controller, 321 List<MediaItem2> playlist, MediaMetadata2 metadata) { 322 assertNotNull(playlist); 323 assertEquals(testList.size(), playlist.size()); 324 for (int i = 0; i < playlist.size(); i++) { 325 assertEquals(testList.get(i).getMediaId(), playlist.get(i).getMediaId()); 326 } 327 listFromCallback.set(playlist); 328 latch.countDown(); 329 } 330 }; 331 final MediaPlaylistAgent agent = new MockPlaylistAgent() { 332 @Override 333 public List<MediaItem2> getPlaylist() { 334 return testList; 335 } 336 }; 337 try (MediaSession2 session = new MediaSession2.Builder(mContext) 338 .setPlayer(mPlayer) 339 .setId("testControllerCallback_onPlaylistChanged") 340 .setSessionCallback(sHandlerExecutor, new SessionCallback() {}) 341 .setPlaylistAgent(agent) 342 .build()) { 343 MediaController2 controller = createController( 344 session.getToken(), true, callback); 345 agent.notifyPlaylistChanged(); 346 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 347 assertEquals(listFromCallback.get(), controller.getPlaylist()); 348 } 349 } 350 351 @Test 352 public void testUpdatePlaylistMetadata() throws InterruptedException { 353 prepareLooper(); 354 final MediaMetadata2 testMetadata = TestUtils.createMetadata(); 355 mController.updatePlaylistMetadata(testMetadata); 356 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 357 358 assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled); 359 assertNotNull(mMockAgent.mMetadata); 360 assertEquals(testMetadata.getMediaId(), mMockAgent.mMetadata.getMediaId()); 361 } 362 363 @Test 364 public void testGetPlaylistMetadata() throws InterruptedException { 365 prepareLooper(); 366 final MediaMetadata2 testMetadata = TestUtils.createMetadata(); 367 final AtomicReference<MediaMetadata2> metadataFromCallback = new AtomicReference<>(); 368 final CountDownLatch latch = new CountDownLatch(1); 369 final ControllerCallback callback = new ControllerCallback() { 370 @Override 371 public void onPlaylistMetadataChanged(MediaController2 controller, 372 MediaMetadata2 metadata) { 373 assertNotNull(testMetadata); 374 assertEquals(testMetadata.getMediaId(), metadata.getMediaId()); 375 metadataFromCallback.set(metadata); 376 latch.countDown(); 377 } 378 }; 379 final MediaPlaylistAgent agent = new MockPlaylistAgent() { 380 @Override 381 public MediaMetadata2 getPlaylistMetadata() { 382 return testMetadata; 383 } 384 }; 385 try (MediaSession2 session = new MediaSession2.Builder(mContext) 386 .setPlayer(mPlayer) 387 .setId("testGetPlaylistMetadata") 388 .setSessionCallback(sHandlerExecutor, new SessionCallback() {}) 389 .setPlaylistAgent(agent) 390 .build()) { 391 MediaController2 controller = createController(session.getToken(), true, callback); 392 agent.notifyPlaylistMetadataChanged(); 393 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 394 assertEquals(metadataFromCallback.get().getMediaId(), 395 controller.getPlaylistMetadata().getMediaId()); 396 } 397 } 398 399 @Test 400 public void testSetPlaybackSpeed() throws Exception { 401 prepareLooper(); 402 final float speed = 1.5f; 403 mController.setPlaybackSpeed(speed); 404 assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 405 assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f); 406 } 407 408 /** 409 * Test whether {@link MediaSession2#setPlaylist(List, MediaMetadata2)} is notified 410 * through the 411 * {@link ControllerCallback#onPlaylistMetadataChanged(MediaController2, MediaMetadata2)} 412 * if the controller doesn't have {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST} but 413 * {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST_METADATA}. 414 */ 415 @Test 416 public void testControllerCallback_onPlaylistMetadataChanged() throws InterruptedException { 417 prepareLooper(); 418 final MediaItem2 item = TestUtils.createMediaItemWithMetadata(); 419 final List<MediaItem2> list = TestUtils.createPlaylist(2); 420 final CountDownLatch latch = new CountDownLatch(1); 421 final ControllerCallback callback = new ControllerCallback() { 422 @Override 423 public void onPlaylistMetadataChanged(MediaController2 controller, 424 MediaMetadata2 metadata) { 425 assertNotNull(metadata); 426 assertEquals(item.getMediaId(), metadata.getMediaId()); 427 latch.countDown(); 428 } 429 }; 430 final SessionCallback sessionCallback = new SessionCallback() { 431 @Override 432 public SessionCommandGroup2 onConnect(MediaSession2 session, 433 ControllerInfo controller) { 434 if (Process.myUid() == controller.getUid()) { 435 SessionCommandGroup2 commands = new SessionCommandGroup2(); 436 commands.addCommand(new SessionCommand2( 437 SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA)); 438 return commands; 439 } 440 return super.onConnect(session, controller); 441 } 442 }; 443 final MediaPlaylistAgent agent = new MockPlaylistAgent() { 444 @Override 445 public MediaMetadata2 getPlaylistMetadata() { 446 return item.getMetadata(); 447 } 448 449 @Override 450 public List<MediaItem2> getPlaylist() { 451 return list; 452 } 453 }; 454 try (MediaSession2 session = new MediaSession2.Builder(mContext) 455 .setPlayer(mPlayer) 456 .setId("testControllerCallback_onPlaylistMetadataChanged") 457 .setSessionCallback(sHandlerExecutor, sessionCallback) 458 .setPlaylistAgent(agent) 459 .build()) { 460 MediaController2 controller = createController(session.getToken(), true, callback); 461 agent.notifyPlaylistMetadataChanged(); 462 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 463 } 464 } 465 466 467 @Test 468 public void testControllerCallback_onSeekCompleted() throws InterruptedException { 469 prepareLooper(); 470 final long testSeekPosition = 400; 471 final long testPosition = 500; 472 final CountDownLatch latch = new CountDownLatch(1); 473 final ControllerCallback callback = new ControllerCallback() { 474 @Override 475 public void onSeekCompleted(MediaController2 controller, long position) { 476 controller.setTimeDiff(Long.valueOf(0)); 477 assertEquals(testSeekPosition, position); 478 assertEquals(testPosition, controller.getCurrentPosition()); 479 latch.countDown(); 480 } 481 }; 482 final MediaController2 controller = createController(mSession.getToken(), true, callback); 483 mPlayer.mCurrentPosition = testPosition; 484 mPlayer.notifySeekCompleted(testSeekPosition); 485 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 486 } 487 488 @Test 489 public void testControllerCallback_onBufferingStateChanged() throws InterruptedException { 490 prepareLooper(); 491 final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3); 492 final MediaItem2 testItem = testPlaylist.get(0); 493 final int testBufferingState = MediaPlayerInterface.BUFFERING_STATE_BUFFERING_AND_PLAYABLE; 494 final long testBufferingPosition = 500; 495 final CountDownLatch latch = new CountDownLatch(1); 496 final ControllerCallback callback = new ControllerCallback() { 497 @Override 498 public void onBufferingStateChanged(MediaController2 controller, MediaItem2 item, 499 int state) { 500 controller.setTimeDiff(Long.valueOf(0)); 501 assertEquals(testItem, item); 502 assertEquals(testBufferingState, state); 503 assertEquals(testBufferingState, controller.getBufferingState()); 504 assertEquals(testBufferingPosition, controller.getBufferedPosition()); 505 latch.countDown(); 506 } 507 }; 508 final MediaController2 controller = createController(mSession.getToken(), true, callback); 509 mSession.setPlaylist(testPlaylist, null); 510 mPlayer.mBufferedPosition = testBufferingPosition; 511 mPlayer.notifyBufferingStateChanged(testItem.getDataSourceDesc(), testBufferingState); 512 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 513 } 514 515 @Test 516 public void testControllerCallback_onPlayerStateChanged() throws InterruptedException { 517 prepareLooper(); 518 final int testPlayerState = MediaPlayerInterface.PLAYER_STATE_PLAYING; 519 final long testPosition = 500; 520 final CountDownLatch latch = new CountDownLatch(1); 521 final ControllerCallback callback = new ControllerCallback() { 522 @Override 523 public void onPlayerStateChanged(MediaController2 controller, int state) { 524 controller.setTimeDiff(Long.valueOf(0)); 525 assertEquals(testPlayerState, state); 526 assertEquals(testPlayerState, controller.getPlayerState()); 527 assertEquals(testPosition, controller.getCurrentPosition()); 528 latch.countDown(); 529 } 530 }; 531 final MediaController2 controller = createController(mSession.getToken(), true, callback); 532 mPlayer.mCurrentPosition = testPosition; 533 mPlayer.notifyPlaybackState(testPlayerState); 534 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 535 } 536 537 @Test 538 public void testAddPlaylistItem() throws InterruptedException { 539 prepareLooper(); 540 final int testIndex = 12; 541 final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata(); 542 mController.addPlaylistItem(testIndex, testMediaItem); 543 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 544 545 assertTrue(mMockAgent.mAddPlaylistItemCalled); 546 assertEquals(testIndex, mMockAgent.mIndex); 547 // MediaController2.addPlaylistItem does not ensure the equality of the items. 548 assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId()); 549 } 550 551 @Test 552 public void testRemovePlaylistItem() throws InterruptedException { 553 prepareLooper(); 554 mMockAgent.mPlaylist = TestUtils.createPlaylist(2); 555 556 // Recreate controller for sending removePlaylistItem. 557 // It's easier to ensure that MediaController2.getPlaylist() returns the playlist from the 558 // agent. 559 MediaController2 controller = createController(mSession.getToken()); 560 MediaItem2 targetItem = controller.getPlaylist().get(0); 561 controller.removePlaylistItem(targetItem); 562 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 563 564 assertTrue(mMockAgent.mRemovePlaylistItemCalled); 565 assertEquals(targetItem, mMockAgent.mItem); 566 } 567 568 @Test 569 public void testReplacePlaylistItem() throws InterruptedException { 570 prepareLooper(); 571 final int testIndex = 12; 572 final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata(); 573 mController.replacePlaylistItem(testIndex, testMediaItem); 574 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 575 576 assertTrue(mMockAgent.mReplacePlaylistItemCalled); 577 // MediaController2.replacePlaylistItem does not ensure the equality of the items. 578 assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId()); 579 } 580 581 @Test 582 public void testSkipToPreviousItem() throws InterruptedException { 583 prepareLooper(); 584 mController.skipToPreviousItem(); 585 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 586 assertTrue(mMockAgent.mSkipToPreviousItemCalled); 587 } 588 589 @Test 590 public void testSkipToNextItem() throws InterruptedException { 591 prepareLooper(); 592 mController.skipToNextItem(); 593 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 594 assertTrue(mMockAgent.mSkipToNextItemCalled); 595 } 596 597 @Test 598 public void testSkipToPlaylistItem() throws InterruptedException { 599 prepareLooper(); 600 MediaController2 controller = createController(mSession.getToken()); 601 MediaItem2 targetItem = TestUtils.createMediaItemWithMetadata(); 602 controller.skipToPlaylistItem(targetItem); 603 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 604 605 assertTrue(mMockAgent.mSkipToPlaylistItemCalled); 606 assertEquals(targetItem, mMockAgent.mItem); 607 } 608 609 /** 610 * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController2, int)}. 611 */ 612 @Test 613 public void testGetShuffleMode() throws InterruptedException { 614 prepareLooper(); 615 final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP; 616 final MediaPlaylistAgent agent = new MockPlaylistAgent() { 617 @Override 618 public int getShuffleMode() { 619 return testShuffleMode; 620 } 621 }; 622 final CountDownLatch latch = new CountDownLatch(1); 623 final ControllerCallback callback = new ControllerCallback() { 624 @Override 625 public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) { 626 assertEquals(testShuffleMode, shuffleMode); 627 latch.countDown(); 628 } 629 }; 630 mSession.updatePlayer(mPlayer, agent, null); 631 MediaController2 controller = createController(mSession.getToken(), true, callback); 632 agent.notifyShuffleModeChanged(); 633 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 634 assertEquals(testShuffleMode, controller.getShuffleMode()); 635 } 636 637 @Test 638 public void testSetShuffleMode() throws InterruptedException { 639 prepareLooper(); 640 final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP; 641 mController.setShuffleMode(testShuffleMode); 642 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 643 644 assertTrue(mMockAgent.mSetShuffleModeCalled); 645 assertEquals(testShuffleMode, mMockAgent.mShuffleMode); 646 } 647 648 /** 649 * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController2, int)}. 650 */ 651 @Test 652 public void testGetRepeatMode() throws InterruptedException { 653 prepareLooper(); 654 final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP; 655 final MediaPlaylistAgent agent = new MockPlaylistAgent() { 656 @Override 657 public int getRepeatMode() { 658 return testRepeatMode; 659 } 660 }; 661 final CountDownLatch latch = new CountDownLatch(1); 662 final ControllerCallback callback = new ControllerCallback() { 663 @Override 664 public void onRepeatModeChanged(MediaController2 controller, int repeatMode) { 665 assertEquals(testRepeatMode, repeatMode); 666 latch.countDown(); 667 } 668 }; 669 mSession.updatePlayer(mPlayer, agent, null); 670 MediaController2 controller = createController(mSession.getToken(), true, callback); 671 agent.notifyRepeatModeChanged(); 672 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 673 assertEquals(testRepeatMode, controller.getRepeatMode()); 674 } 675 676 @Test 677 public void testSetRepeatMode() throws InterruptedException { 678 prepareLooper(); 679 final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP; 680 mController.setRepeatMode(testRepeatMode); 681 assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 682 683 assertTrue(mMockAgent.mSetRepeatModeCalled); 684 assertEquals(testRepeatMode, mMockAgent.mRepeatMode); 685 } 686 687 @Test 688 public void testSetVolumeTo() throws Exception { 689 prepareLooper(); 690 final int maxVolume = 100; 691 final int currentVolume = 23; 692 final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 693 TestVolumeProvider volumeProvider = 694 new TestVolumeProvider(volumeControlType, maxVolume, currentVolume); 695 696 mSession.updatePlayer(new MockPlayer(0), null, volumeProvider); 697 final MediaController2 controller = createController(mSession.getToken(), true, null); 698 699 final int targetVolume = 50; 700 controller.setVolumeTo(targetVolume, 0 /* flags */); 701 assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 702 assertTrue(volumeProvider.mSetVolumeToCalled); 703 assertEquals(targetVolume, volumeProvider.mVolume); 704 } 705 706 @Test 707 public void testAdjustVolume() throws Exception { 708 prepareLooper(); 709 final int maxVolume = 100; 710 final int currentVolume = 23; 711 final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 712 TestVolumeProvider volumeProvider = 713 new TestVolumeProvider(volumeControlType, maxVolume, currentVolume); 714 715 mSession.updatePlayer(new MockPlayer(0), null, volumeProvider); 716 final MediaController2 controller = createController(mSession.getToken(), true, null); 717 718 final int direction = AudioManager.ADJUST_RAISE; 719 controller.adjustVolume(direction, 0 /* flags */); 720 assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 721 assertTrue(volumeProvider.mAdjustVolumeCalled); 722 assertEquals(direction, volumeProvider.mDirection); 723 } 724 725 @Test 726 public void testSetVolumeWithLocalVolume() throws Exception { 727 prepareLooper(); 728 if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) { 729 // This test is not eligible for this device. 730 return; 731 } 732 733 // Here, we intentionally choose STREAM_ALARM in order not to consider 734 // 'Do Not Disturb' or 'Volume limit'. 735 final int stream = AudioManager.STREAM_ALARM; 736 final int maxVolume = mAudioManager.getStreamMaxVolume(stream); 737 final int minVolume = 0; 738 if (maxVolume <= minVolume) { 739 return; 740 } 741 742 // Set stream of the session. 743 AudioAttributesCompat attrs = new AudioAttributesCompat.Builder() 744 .setLegacyStreamType(stream) 745 .build(); 746 mPlayer.setAudioAttributes(attrs); 747 mSession.updatePlayer(mPlayer, null, null); 748 749 final int originalVolume = mAudioManager.getStreamVolume(stream); 750 final int targetVolume = originalVolume == minVolume 751 ? originalVolume + 1 : originalVolume - 1; 752 753 mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI); 754 new PollingCheck(WAIT_TIME_MS) { 755 @Override 756 protected boolean check() { 757 return targetVolume == mAudioManager.getStreamVolume(stream); 758 } 759 }.run(); 760 761 // Set back to original volume. 762 mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */); 763 } 764 765 @Test 766 public void testAdjustVolumeWithLocalVolume() throws Exception { 767 prepareLooper(); 768 if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) { 769 // This test is not eligible for this device. 770 return; 771 } 772 773 // Here, we intentionally choose STREAM_ALARM in order not to consider 774 // 'Do Not Disturb' or 'Volume limit'. 775 final int stream = AudioManager.STREAM_ALARM; 776 final int maxVolume = mAudioManager.getStreamMaxVolume(stream); 777 final int minVolume = 0; 778 if (maxVolume <= minVolume) { 779 return; 780 } 781 782 // Set stream of the session. 783 AudioAttributesCompat attrs = new AudioAttributesCompat.Builder() 784 .setLegacyStreamType(stream) 785 .build(); 786 mPlayer.setAudioAttributes(attrs); 787 mSession.updatePlayer(mPlayer, null, null); 788 789 final int originalVolume = mAudioManager.getStreamVolume(stream); 790 final int direction = originalVolume == minVolume 791 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER; 792 final int targetVolume = originalVolume + direction; 793 794 mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI); 795 new PollingCheck(WAIT_TIME_MS) { 796 @Override 797 protected boolean check() { 798 return targetVolume == mAudioManager.getStreamVolume(stream); 799 } 800 }.run(); 801 802 // Set back to original volume. 803 mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */); 804 } 805 806 @Test 807 public void testGetPackageName() { 808 prepareLooper(); 809 assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName()); 810 } 811 812 @Test 813 public void testSendCustomCommand() throws InterruptedException { 814 prepareLooper(); 815 // TODO(jaewan): Need to revisit with the permission. 816 final SessionCommand2 testCommand = 817 new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE); 818 final Bundle testArgs = new Bundle(); 819 testArgs.putString("args", "testSendCustomCommand"); 820 821 final CountDownLatch latch = new CountDownLatch(1); 822 final SessionCallback callback = new SessionCallback() { 823 @Override 824 public void onCustomCommand(MediaSession2 session, ControllerInfo controller, 825 SessionCommand2 customCommand, Bundle args, ResultReceiver cb) { 826 assertEquals(mContext.getPackageName(), controller.getPackageName()); 827 assertEquals(testCommand, customCommand); 828 assertTrue(TestUtils.equals(testArgs, args)); 829 assertNull(cb); 830 latch.countDown(); 831 } 832 }; 833 mSession.close(); 834 mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer) 835 .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build(); 836 final MediaController2 controller = createController(mSession.getToken()); 837 controller.sendCustomCommand(testCommand, testArgs, null); 838 assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 839 } 840 841 @Test 842 public void testControllerCallback_onConnected() throws InterruptedException { 843 prepareLooper(); 844 // createController() uses controller callback to wait until the controller becomes 845 // available. 846 MediaController2 controller = createController(mSession.getToken()); 847 assertNotNull(controller); 848 } 849 850 @Test 851 public void testControllerCallback_sessionRejects() throws InterruptedException { 852 prepareLooper(); 853 final MediaSession2.SessionCallback sessionCallback = new SessionCallback() { 854 @Override 855 public SessionCommandGroup2 onConnect(MediaSession2 session, 856 ControllerInfo controller) { 857 return null; 858 } 859 }; 860 sHandler.postAndSync(new Runnable() { 861 @Override 862 public void run() { 863 mSession.close(); 864 mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer) 865 .setSessionCallback(sHandlerExecutor, sessionCallback).build(); 866 } 867 }); 868 MediaController2 controller = 869 createController(mSession.getToken(), false, null); 870 assertNotNull(controller); 871 waitForConnect(controller, false); 872 waitForDisconnect(controller, true); 873 } 874 875 @Test 876 public void testControllerCallback_releaseSession() throws InterruptedException { 877 prepareLooper(); 878 mSession.close(); 879 waitForDisconnect(mController, true); 880 } 881 882 @Test 883 public void testControllerCallback_close() throws InterruptedException { 884 prepareLooper(); 885 mController.close(); 886 waitForDisconnect(mController, true); 887 } 888 889 @Test 890 public void testFastForward() throws InterruptedException { 891 prepareLooper(); 892 final CountDownLatch latch = new CountDownLatch(1); 893 final SessionCallback callback = new SessionCallback() { 894 @Override 895 public void onFastForward(MediaSession2 session, ControllerInfo controller) { 896 assertEquals(mContext.getPackageName(), controller.getPackageName()); 897 latch.countDown(); 898 } 899 }; 900 try (MediaSession2 session = new MediaSession2.Builder(mContext) 901 .setPlayer(mPlayer) 902 .setSessionCallback(sHandlerExecutor, callback) 903 .setId("testFastForward").build()) { 904 MediaController2 controller = createController(session.getToken()); 905 controller.fastForward(); 906 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 907 } 908 } 909 910 @Test 911 public void testRewind() throws InterruptedException { 912 prepareLooper(); 913 final CountDownLatch latch = new CountDownLatch(1); 914 final SessionCallback callback = new SessionCallback() { 915 @Override 916 public void onRewind(MediaSession2 session, ControllerInfo controller) { 917 assertEquals(mContext.getPackageName(), controller.getPackageName()); 918 latch.countDown(); 919 } 920 }; 921 try (MediaSession2 session = new MediaSession2.Builder(mContext) 922 .setPlayer(mPlayer) 923 .setSessionCallback(sHandlerExecutor, callback) 924 .setId("testRewind").build()) { 925 MediaController2 controller = createController(session.getToken()); 926 controller.rewind(); 927 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 928 } 929 } 930 931 @Test 932 public void testPlayFromSearch() throws InterruptedException { 933 prepareLooper(); 934 final String request = "random query"; 935 final Bundle bundle = new Bundle(); 936 bundle.putString("key", "value"); 937 final CountDownLatch latch = new CountDownLatch(1); 938 final SessionCallback callback = new SessionCallback() { 939 @Override 940 public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller, 941 String query, Bundle extras) { 942 super.onPlayFromSearch(session, controller, query, extras); 943 assertEquals(mContext.getPackageName(), controller.getPackageName()); 944 assertEquals(request, query); 945 assertTrue(TestUtils.equals(bundle, extras)); 946 latch.countDown(); 947 } 948 }; 949 try (MediaSession2 session = new MediaSession2.Builder(mContext) 950 .setPlayer(mPlayer) 951 .setSessionCallback(sHandlerExecutor, callback) 952 .setId("testPlayFromSearch").build()) { 953 MediaController2 controller = createController(session.getToken()); 954 controller.playFromSearch(request, bundle); 955 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 956 } 957 } 958 959 @Test 960 public void testPlayFromUri() throws InterruptedException { 961 prepareLooper(); 962 final Uri request = Uri.parse("foo://boo"); 963 final Bundle bundle = new Bundle(); 964 bundle.putString("key", "value"); 965 final CountDownLatch latch = new CountDownLatch(1); 966 final SessionCallback callback = new SessionCallback() { 967 @Override 968 public void onPlayFromUri(MediaSession2 session, ControllerInfo controller, Uri uri, 969 Bundle extras) { 970 assertEquals(mContext.getPackageName(), controller.getPackageName()); 971 assertEquals(request, uri); 972 assertTrue(TestUtils.equals(bundle, extras)); 973 latch.countDown(); 974 } 975 }; 976 try (MediaSession2 session = new MediaSession2.Builder(mContext) 977 .setPlayer(mPlayer) 978 .setSessionCallback(sHandlerExecutor, callback) 979 .setId("testPlayFromUri").build()) { 980 MediaController2 controller = createController(session.getToken()); 981 controller.playFromUri(request, bundle); 982 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 983 } 984 } 985 986 @Test 987 public void testPlayFromMediaId() throws InterruptedException { 988 prepareLooper(); 989 final String request = "media_id"; 990 final Bundle bundle = new Bundle(); 991 bundle.putString("key", "value"); 992 final CountDownLatch latch = new CountDownLatch(1); 993 final SessionCallback callback = new SessionCallback() { 994 @Override 995 public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller, 996 String mediaId, Bundle extras) { 997 assertEquals(mContext.getPackageName(), controller.getPackageName()); 998 assertEquals(request, mediaId); 999 assertTrue(TestUtils.equals(bundle, extras)); 1000 latch.countDown(); 1001 } 1002 }; 1003 try (MediaSession2 session = new MediaSession2.Builder(mContext) 1004 .setPlayer(mPlayer) 1005 .setSessionCallback(sHandlerExecutor, callback) 1006 .setId("testPlayFromMediaId").build()) { 1007 MediaController2 controller = createController(session.getToken()); 1008 controller.playFromMediaId(request, bundle); 1009 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1010 } 1011 } 1012 1013 @Test 1014 public void testPrepareFromSearch() throws InterruptedException { 1015 prepareLooper(); 1016 final String request = "random query"; 1017 final Bundle bundle = new Bundle(); 1018 bundle.putString("key", "value"); 1019 final CountDownLatch latch = new CountDownLatch(1); 1020 final SessionCallback callback = new SessionCallback() { 1021 @Override 1022 public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller, 1023 String query, Bundle extras) { 1024 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1025 assertEquals(request, query); 1026 assertTrue(TestUtils.equals(bundle, extras)); 1027 latch.countDown(); 1028 } 1029 }; 1030 try (MediaSession2 session = new MediaSession2.Builder(mContext) 1031 .setPlayer(mPlayer) 1032 .setSessionCallback(sHandlerExecutor, callback) 1033 .setId("testPrepareFromSearch").build()) { 1034 MediaController2 controller = createController(session.getToken()); 1035 controller.prepareFromSearch(request, bundle); 1036 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1037 } 1038 } 1039 1040 @Test 1041 public void testPrepareFromUri() throws InterruptedException { 1042 prepareLooper(); 1043 final Uri request = Uri.parse("foo://boo"); 1044 final Bundle bundle = new Bundle(); 1045 bundle.putString("key", "value"); 1046 final CountDownLatch latch = new CountDownLatch(1); 1047 final SessionCallback callback = new SessionCallback() { 1048 @Override 1049 public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri, 1050 Bundle extras) { 1051 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1052 assertEquals(request, uri); 1053 assertTrue(TestUtils.equals(bundle, extras)); 1054 latch.countDown(); 1055 } 1056 }; 1057 try (MediaSession2 session = new MediaSession2.Builder(mContext) 1058 .setPlayer(mPlayer) 1059 .setSessionCallback(sHandlerExecutor, callback) 1060 .setId("testPrepareFromUri").build()) { 1061 MediaController2 controller = createController(session.getToken()); 1062 controller.prepareFromUri(request, bundle); 1063 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1064 } 1065 } 1066 1067 @Test 1068 public void testPrepareFromMediaId() throws InterruptedException { 1069 prepareLooper(); 1070 final String request = "media_id"; 1071 final Bundle bundle = new Bundle(); 1072 bundle.putString("key", "value"); 1073 final CountDownLatch latch = new CountDownLatch(1); 1074 final SessionCallback callback = new SessionCallback() { 1075 @Override 1076 public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller, 1077 String mediaId, Bundle extras) { 1078 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1079 assertEquals(request, mediaId); 1080 assertTrue(TestUtils.equals(bundle, extras)); 1081 latch.countDown(); 1082 } 1083 }; 1084 try (MediaSession2 session = new MediaSession2.Builder(mContext) 1085 .setPlayer(mPlayer) 1086 .setSessionCallback(sHandlerExecutor, callback) 1087 .setId("testPrepareFromMediaId").build()) { 1088 MediaController2 controller = createController(session.getToken()); 1089 controller.prepareFromMediaId(request, bundle); 1090 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1091 } 1092 } 1093 1094 @Test 1095 public void testSetRating() throws InterruptedException { 1096 prepareLooper(); 1097 final int ratingType = Rating2.RATING_5_STARS; 1098 final float ratingValue = 3.5f; 1099 final Rating2 rating = Rating2.newStarRating(ratingType, ratingValue); 1100 final String mediaId = "media_id"; 1101 1102 final CountDownLatch latch = new CountDownLatch(1); 1103 final SessionCallback callback = new SessionCallback() { 1104 @Override 1105 public void onSetRating(MediaSession2 session, ControllerInfo controller, 1106 String mediaIdOut, Rating2 ratingOut) { 1107 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1108 assertEquals(mediaId, mediaIdOut); 1109 assertEquals(rating, ratingOut); 1110 latch.countDown(); 1111 } 1112 }; 1113 1114 try (MediaSession2 session = new MediaSession2.Builder(mContext) 1115 .setPlayer(mPlayer) 1116 .setSessionCallback(sHandlerExecutor, callback) 1117 .setId("testSetRating").build()) { 1118 MediaController2 controller = createController(session.getToken()); 1119 controller.setRating(mediaId, rating); 1120 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1121 } 1122 } 1123 1124 @Test 1125 public void testIsConnected() throws InterruptedException { 1126 prepareLooper(); 1127 assertTrue(mController.isConnected()); 1128 sHandler.postAndSync(new Runnable() { 1129 @Override 1130 public void run() { 1131 mSession.close(); 1132 } 1133 }); 1134 waitForDisconnect(mController, true); 1135 assertFalse(mController.isConnected()); 1136 } 1137 1138 /** 1139 * Test potential deadlock for calls between controller and session. 1140 */ 1141 @Test 1142 public void testDeadlock() throws InterruptedException { 1143 prepareLooper(); 1144 sHandler.postAndSync(new Runnable() { 1145 @Override 1146 public void run() { 1147 mSession.close(); 1148 mSession = null; 1149 } 1150 }); 1151 1152 // Two more threads are needed not to block test thread nor test wide thread (sHandler). 1153 final HandlerThread sessionThread = new HandlerThread("testDeadlock_session"); 1154 final HandlerThread testThread = new HandlerThread("testDeadlock_test"); 1155 sessionThread.start(); 1156 testThread.start(); 1157 final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper()); 1158 final Handler testHandler = new Handler(testThread.getLooper()); 1159 final CountDownLatch latch = new CountDownLatch(1); 1160 try { 1161 final MockPlayer player = new MockPlayer(0); 1162 sessionHandler.postAndSync(new Runnable() { 1163 @Override 1164 public void run() { 1165 mSession = new MediaSession2.Builder(mContext) 1166 .setPlayer(mPlayer) 1167 .setSessionCallback(sHandlerExecutor, new SessionCallback() {}) 1168 .setId("testDeadlock").build(); 1169 } 1170 }); 1171 final MediaController2 controller = createController(mSession.getToken()); 1172 testHandler.post(new Runnable() { 1173 @Override 1174 public void run() { 1175 final int state = MediaPlayerInterface.PLAYER_STATE_ERROR; 1176 for (int i = 0; i < 100; i++) { 1177 // triggers call from session to controller. 1178 player.notifyPlaybackState(state); 1179 // triggers call from controller to session. 1180 controller.play(); 1181 1182 // Repeat above 1183 player.notifyPlaybackState(state); 1184 controller.pause(); 1185 player.notifyPlaybackState(state); 1186 controller.reset(); 1187 player.notifyPlaybackState(state); 1188 controller.skipToNextItem(); 1189 player.notifyPlaybackState(state); 1190 controller.skipToPreviousItem(); 1191 } 1192 // This may hang if deadlock happens. 1193 latch.countDown(); 1194 } 1195 }); 1196 assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 1197 } finally { 1198 if (mSession != null) { 1199 sessionHandler.postAndSync(new Runnable() { 1200 @Override 1201 public void run() { 1202 // Clean up here because sessionHandler will be removed afterwards. 1203 mSession.close(); 1204 mSession = null; 1205 } 1206 }); 1207 } 1208 1209 if (Build.VERSION.SDK_INT >= 18) { 1210 sessionThread.quitSafely(); 1211 testThread.quitSafely(); 1212 } else { 1213 sessionThread.quit(); 1214 testThread.quit(); 1215 } 1216 } 1217 } 1218 1219 @Test 1220 public void testGetServiceToken() { 1221 prepareLooper(); 1222 SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID); 1223 assertNotNull(token); 1224 assertEquals(mContext.getPackageName(), token.getPackageName()); 1225 assertEquals(MockMediaSessionService2.ID, token.getId()); 1226 assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType()); 1227 } 1228 1229 @Test 1230 public void testConnectToService_sessionService() throws InterruptedException { 1231 prepareLooper(); 1232 testConnectToService(MockMediaSessionService2.ID); 1233 } 1234 1235 @Test 1236 public void testConnectToService_libraryService() throws InterruptedException { 1237 prepareLooper(); 1238 testConnectToService(MockMediaLibraryService2.ID); 1239 } 1240 1241 public void testConnectToService(String id) throws InterruptedException { 1242 prepareLooper(); 1243 final CountDownLatch latch = new CountDownLatch(1); 1244 final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() { 1245 @Override 1246 public SessionCommandGroup2 onConnect(@NonNull MediaSession2 session, 1247 @NonNull ControllerInfo controller) { 1248 if (Process.myUid() == controller.getUid()) { 1249 if (mSession != null) { 1250 mSession.close(); 1251 } 1252 mSession = session; 1253 mPlayer = (MockPlayer) session.getPlayer(); 1254 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1255 assertFalse(controller.isTrusted()); 1256 latch.countDown(); 1257 } 1258 return super.onConnect(session, controller); 1259 } 1260 }; 1261 TestServiceRegistry.getInstance().setSessionCallback(sessionCallback); 1262 1263 final SessionCommand2 testCommand = new SessionCommand2("testConnectToService", null); 1264 final CountDownLatch controllerLatch = new CountDownLatch(1); 1265 mController = createController(TestUtils.getServiceToken(mContext, id), true, 1266 new ControllerCallback() { 1267 @Override 1268 public void onCustomCommand(MediaController2 controller, 1269 SessionCommand2 command, Bundle args, ResultReceiver receiver) { 1270 if (testCommand.equals(command)) { 1271 controllerLatch.countDown(); 1272 } 1273 } 1274 } 1275 ); 1276 assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1277 1278 // Test command from controller to session service. 1279 mController.play(); 1280 assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1281 assertTrue(mPlayer.mPlayCalled); 1282 1283 // Test command from session service to controller. 1284 mSession.sendCustomCommand(testCommand, null); 1285 assertTrue(controllerLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 1286 } 1287 1288 @Test 1289 public void testControllerAfterSessionIsGone_session() throws InterruptedException { 1290 prepareLooper(); 1291 testControllerAfterSessionIsClosed(mSession.getToken().getId()); 1292 } 1293 1294 @Test 1295 public void testControllerAfterSessionIsClosed_sessionService() throws InterruptedException { 1296 prepareLooper(); 1297 testConnectToService(MockMediaSessionService2.ID); 1298 testControllerAfterSessionIsClosed(MockMediaSessionService2.ID); 1299 } 1300 1301 @Test 1302 public void testSubscribeRouteInfo() throws InterruptedException { 1303 prepareLooper(); 1304 final TestSessionCallback callback = new TestSessionCallback() { 1305 @Override 1306 public void onSubscribeRoutesInfo(@NonNull MediaSession2 session, 1307 @NonNull ControllerInfo controller) { 1308 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1309 mLatch.countDown(); 1310 } 1311 1312 @Override 1313 public void onUnsubscribeRoutesInfo(@NonNull MediaSession2 session, 1314 @NonNull ControllerInfo controller) { 1315 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1316 mLatch.countDown(); 1317 } 1318 }; 1319 mSession.close(); 1320 mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer) 1321 .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build(); 1322 final MediaController2 controller = createController(mSession.getToken()); 1323 1324 callback.resetLatchCount(1); 1325 controller.subscribeRoutesInfo(); 1326 assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1327 1328 callback.resetLatchCount(1); 1329 controller.unsubscribeRoutesInfo(); 1330 assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1331 } 1332 1333 @Test 1334 public void testSelectRouteInfo() throws InterruptedException { 1335 prepareLooper(); 1336 final Bundle testRoute = new Bundle(); 1337 testRoute.putString("id", "testRoute"); 1338 final TestSessionCallback callback = new TestSessionCallback() { 1339 @Override 1340 public void onSelectRoute(@NonNull MediaSession2 session, 1341 @NonNull ControllerInfo controller, @NonNull Bundle route) { 1342 assertEquals(mContext.getPackageName(), controller.getPackageName()); 1343 assertTrue(TestUtils.equals(route, testRoute)); 1344 mLatch.countDown(); 1345 } 1346 }; 1347 mSession.close(); 1348 mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer) 1349 .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build(); 1350 final MediaController2 controller = createController(mSession.getToken()); 1351 1352 callback.resetLatchCount(1); 1353 controller.selectRoute(testRoute); 1354 assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1355 } 1356 1357 @Test 1358 public void testClose_beforeConnected() throws InterruptedException { 1359 prepareLooper(); 1360 MediaController2 controller = 1361 createController(mSession.getToken(), false, null); 1362 controller.close(); 1363 } 1364 1365 @Test 1366 public void testClose_twice() { 1367 prepareLooper(); 1368 mController.close(); 1369 mController.close(); 1370 } 1371 1372 @Test 1373 public void testClose_session() throws InterruptedException { 1374 prepareLooper(); 1375 final String id = mSession.getToken().getId(); 1376 mController.close(); 1377 // close is done immediately for session. 1378 testNoInteraction(); 1379 1380 // Test whether the controller is notified about later close of the session or 1381 // re-creation. 1382 testControllerAfterSessionIsClosed(id); 1383 } 1384 1385 @Test 1386 public void testClose_sessionService() throws InterruptedException { 1387 prepareLooper(); 1388 testCloseFromService(MockMediaSessionService2.ID); 1389 } 1390 1391 @Test 1392 public void testClose_libraryService() throws InterruptedException { 1393 prepareLooper(); 1394 testCloseFromService(MockMediaLibraryService2.ID); 1395 } 1396 1397 private void testCloseFromService(String id) throws InterruptedException { 1398 final CountDownLatch latch = new CountDownLatch(1); 1399 TestServiceRegistry.getInstance().setSessionServiceCallback(new SessionServiceCallback() { 1400 @Override 1401 public void onCreated() { 1402 // Do nothing. 1403 } 1404 1405 @Override 1406 public void onDestroyed() { 1407 latch.countDown(); 1408 } 1409 }); 1410 mController = createController(TestUtils.getServiceToken(mContext, id)); 1411 mController.close(); 1412 // Wait until close triggers onDestroy() of the session service. 1413 assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 1414 assertNull(TestServiceRegistry.getInstance().getServiceInstance()); 1415 testNoInteraction(); 1416 1417 // Test whether the controller is notified about later close of the session or 1418 // re-creation. 1419 testControllerAfterSessionIsClosed(id); 1420 } 1421 1422 private void testControllerAfterSessionIsClosed(final String id) throws InterruptedException { 1423 // This cause session service to be died. 1424 mSession.close(); 1425 waitForDisconnect(mController, true); 1426 testNoInteraction(); 1427 1428 // Ensure that the controller cannot use newly create session with the same ID. 1429 // Recreated session has different session stub, so previously created controller 1430 // shouldn't be available. 1431 mSession = new MediaSession2.Builder(mContext) 1432 .setPlayer(mPlayer) 1433 .setSessionCallback(sHandlerExecutor, new SessionCallback() {}) 1434 .setId(id).build(); 1435 testNoInteraction(); 1436 } 1437 1438 // Test that mSession and mController doesn't interact. 1439 // Note that this method can be called after the mSession is died, so mSession may not have 1440 // valid player. 1441 private void testNoInteraction() throws InterruptedException { 1442 // TODO: check that calls from the controller to session shouldn't be delivered. 1443 1444 // Calls from the session to controller shouldn't be delivered. 1445 final CountDownLatch latch = new CountDownLatch(1); 1446 setRunnableForOnCustomCommand(mController, new Runnable() { 1447 @Override 1448 public void run() { 1449 latch.countDown(); 1450 } 1451 }); 1452 SessionCommand2 customCommand = new SessionCommand2("testNoInteraction", null); 1453 mSession.sendCustomCommand(customCommand, null); 1454 assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); 1455 setRunnableForOnCustomCommand(mController, null); 1456 } 1457 1458 // TODO(jaewan): Add test for service connect rejection, when we differentiate session 1459 // active/inactive and connection accept/refuse 1460 1461 class TestVolumeProvider extends VolumeProviderCompat { 1462 final CountDownLatch mLatch = new CountDownLatch(1); 1463 boolean mSetVolumeToCalled; 1464 boolean mAdjustVolumeCalled; 1465 int mVolume; 1466 int mDirection; 1467 1468 TestVolumeProvider(int controlType, int maxVolume, int currentVolume) { 1469 super(controlType, maxVolume, currentVolume); 1470 } 1471 1472 @Override 1473 public void onSetVolumeTo(int volume) { 1474 mSetVolumeToCalled = true; 1475 mVolume = volume; 1476 mLatch.countDown(); 1477 } 1478 1479 @Override 1480 public void onAdjustVolume(int direction) { 1481 mAdjustVolumeCalled = true; 1482 mDirection = direction; 1483 mLatch.countDown(); 1484 } 1485 } 1486 1487 class TestSessionCallback extends SessionCallback { 1488 CountDownLatch mLatch; 1489 1490 void resetLatchCount(int count) { 1491 mLatch = new CountDownLatch(count); 1492 } 1493 } 1494 } 1495