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 android.media.AudioAttributes.USAGE_GAME; 19 import static android.media.AudioAttributes.USAGE_MEDIA; 20 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE; 21 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS; 22 import static android.media.bettertogether.cts.MediaSessionTestService.KEY_SESSION_TOKEN; 23 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_CHECK; 24 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_CLEAN_UP; 25 import static android.media.bettertogether.cts.MediaSessionTestService.STEP_SET_UP; 26 import static android.media.bettertogether.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE; 27 import static android.media.bettertogether.cts.MediaSessionTestService.TEST_SET_QUEUE; 28 import static android.media.cts.Utils.compareRemoteUserInfo; 29 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; 30 31 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 32 33 import static com.google.common.truth.Truth.assertThat; 34 import static com.google.common.truth.Truth.assertWithMessage; 35 36 import android.app.PendingIntent; 37 import android.content.ComponentName; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.media.AudioAttributes; 41 import android.media.AudioManager; 42 import android.media.MediaDescription; 43 import android.media.MediaMetadata; 44 import android.media.MediaSession2; 45 import android.media.Rating; 46 import android.media.VolumeProvider; 47 import android.media.cts.Utils; 48 import android.media.session.MediaController; 49 import android.media.session.MediaSession; 50 import android.media.session.MediaSession.QueueItem; 51 import android.media.session.MediaSessionManager; 52 import android.media.session.MediaSessionManager.RemoteUserInfo; 53 import android.media.session.PlaybackState; 54 import android.os.Bundle; 55 import android.os.Handler; 56 import android.os.Looper; 57 import android.os.Parcel; 58 import android.os.Process; 59 import android.os.UserManager; 60 import android.platform.test.annotations.AppModeFull; 61 import android.text.TextUtils; 62 import android.view.KeyEvent; 63 64 import androidx.test.ext.junit.runners.AndroidJUnit4; 65 import androidx.test.platform.app.InstrumentationRegistry; 66 67 import com.android.compatibility.common.util.FrameworkSpecificTest; 68 import com.android.compatibility.common.util.NonMainlineTest; 69 70 import org.junit.After; 71 import org.junit.Assume; 72 import org.junit.Before; 73 import org.junit.Ignore; 74 import org.junit.Test; 75 import org.junit.runner.RunWith; 76 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Optional; 81 import java.util.concurrent.CountDownLatch; 82 import java.util.concurrent.TimeUnit; 83 84 @FrameworkSpecificTest 85 @NonMainlineTest 86 @AppModeFull(reason = "TODO: evaluate and port to instant") 87 @RunWith(AndroidJUnit4.class) 88 public class MediaSessionTest { 89 // The maximum time to wait for an operation that is expected to succeed. 90 private static final long TIME_OUT_MS = 3000L; 91 // The maximum time to wait for an operation that is expected to fail. 92 private static final long WAIT_MS = 100L; 93 private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10; 94 private static final String TEST_SESSION_TAG = "test-session-tag"; 95 private static final String TEST_KEY = "test-key"; 96 private static final String TEST_VALUE = "test-val"; 97 private static final String TEST_SESSION_EVENT = "test-session-event"; 98 private static final String TEST_VOLUME_CONTROL_ID = "test-volume-control-id"; 99 private static final int TEST_CURRENT_VOLUME = 10; 100 private static final int TEST_MAX_VOLUME = 11; 101 private static final long TEST_QUEUE_ID = 12L; 102 private static final long TEST_ACTION = 55L; 103 private static final int TEST_TOO_MANY_SESSION_COUNT = 1000; 104 private static final boolean SUPPORTS_MULTIPLE_USERS = UserManager.supportsMultipleUsers(); 105 106 private AudioManager mAudioManager; 107 private Handler mHandler = new Handler(Looper.getMainLooper()); 108 private Object mWaitLock = new Object(); 109 private MediaControllerCallback mCallback = new MediaControllerCallback(); 110 private MediaSession mSession; 111 private RemoteUserInfo mKeyDispatcherInfo; 112 private Context mContext; 113 private Optional<Integer> mCloneProfileId = Optional.empty(); 114 getContext()115 private Context getContext() { 116 return InstrumentationRegistry.getInstrumentation().getContext(); 117 } 118 119 @Before setUp()120 public void setUp() { 121 mContext = getContext(); 122 mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 123 mSession = new MediaSession(getContext(), TEST_SESSION_TAG); 124 mKeyDispatcherInfo = new MediaSessionManager.RemoteUserInfo( 125 getContext().getPackageName(), Process.myPid(), Process.myUid()); 126 } 127 128 @After tearDown()129 public void tearDown() throws Exception { 130 // It is OK to call release() twice. 131 if (mSession != null) { 132 mSession.release(); 133 mSession = null; 134 } 135 removeCloneProfile(); 136 } 137 createCloneProfile()138 private void createCloneProfile() { 139 Assume.assumeTrue(SUPPORTS_MULTIPLE_USERS); 140 141 InstrumentationRegistry 142 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(); 143 UserManager userManager = mContext.getSystemService(UserManager.class); 144 boolean isCloneProfileEnabled = userManager.isUserTypeEnabled(USER_TYPE_PROFILE_CLONE); 145 InstrumentationRegistry 146 .getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 147 Assume.assumeTrue(isCloneProfileEnabled); 148 149 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 150 final String output = runShellCommand( 151 "pm create-user --user-type android.os.usertype.profile.CLONE --profileOf " 152 + context.getUserId() + " user2"); 153 154 // On a successful run the output will be like 155 // Success: created user id 11 156 // Hence we use the last index of " " to fetch the cloned profile id. 157 int userIdIndex = output.lastIndexOf(" "); 158 if (userIdIndex != -1) { 159 mCloneProfileId = Optional.of( 160 Integer.parseInt(output.substring(userIdIndex).trim())); 161 } 162 } 163 removeCloneProfile()164 private void removeCloneProfile() { 165 mCloneProfileId.ifPresent(cloneProfileId -> { 166 runShellCommand("am stop-user -w -f " + cloneProfileId); 167 runShellCommand("pm remove-user " + cloneProfileId); 168 mCloneProfileId = Optional.empty(); 169 }); 170 } 171 172 /** 173 * Tests that a session can be created and that all the fields are 174 * initialized correctly. 175 */ 176 @Test testCreateSession()177 public void testCreateSession() throws Exception { 178 assertThat(mSession.getSessionToken()).isNotNull(); 179 assertWithMessage("New session should not be active").that(mSession.isActive()).isFalse(); 180 181 // Verify by getting the controller and checking all its fields 182 MediaController controller = mSession.getController(); 183 assertThat(controller).isNotNull(); 184 verifyNewSession(controller); 185 } 186 187 @Test 188 // Needed for assertThat(sessionToken.equals(mSession)).isFalse(). 189 @SuppressWarnings("EqualsIncompatibleType") testSessionTokenEquals()190 public void testSessionTokenEquals() { 191 MediaSession anotherSession = null; 192 try { 193 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 194 MediaSession.Token sessionToken = mSession.getSessionToken(); 195 MediaSession.Token anotherSessionToken = anotherSession.getSessionToken(); 196 197 // Explicitly checks equals as Guava's EqualsTester is not yet supported (b/236153976). 198 assertThat(sessionToken.equals(sessionToken)).isTrue(); 199 assertThat(sessionToken.equals(null)).isFalse(); 200 assertThat(sessionToken.equals(mSession)).isFalse(); 201 assertThat(sessionToken.equals(anotherSessionToken)).isFalse(); 202 } finally { 203 if (anotherSession != null) { 204 anotherSession.release(); 205 } 206 } 207 } 208 209 /** 210 * Tests MediaSession.Token created in the constructor of MediaSession. 211 */ 212 @Test testSessionToken()213 public void testSessionToken() throws Exception { 214 MediaSession.Token sessionToken = mSession.getSessionToken(); 215 216 assertThat(sessionToken).isNotNull(); 217 assertThat(sessionToken.describeContents()).isEqualTo(0); 218 219 // Test writeToParcel 220 Parcel p = Parcel.obtain(); 221 sessionToken.writeToParcel(p, 0); 222 p.setDataPosition(0); 223 MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p); 224 assertThat(tokenFromParcel).isEqualTo(sessionToken); 225 p.recycle(); 226 227 final int arraySize = 5; 228 MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize); 229 assertThat(tokenArray).isNotNull(); 230 assertThat(tokenArray.length).isEqualTo(arraySize); 231 for (MediaSession.Token tokenElement : tokenArray) { 232 assertThat(tokenElement).isNull(); 233 } 234 } 235 236 /** 237 * Tests that the various configuration bits on a session get passed to the 238 * controller. 239 */ 240 @Test testConfigureSession()241 public void testConfigureSession() throws Exception { 242 MediaController controller = mSession.getController(); 243 controller.registerCallback(mCallback, mHandler); 244 final MediaController.Callback callback = (MediaController.Callback) mCallback; 245 246 synchronized (mWaitLock) { 247 // test setExtras 248 mCallback.resetLocked(); 249 final Bundle extras = new Bundle(); 250 extras.putString(TEST_KEY, TEST_VALUE); 251 mSession.setExtras(extras); 252 mWaitLock.wait(TIME_OUT_MS); 253 assertThat(mCallback.mOnExtraChangedCalled).isTrue(); 254 // just call the callback once directly so it's marked as tested 255 callback.onExtrasChanged(mCallback.mExtras); 256 257 Bundle extrasOut = mCallback.mExtras; 258 assertThat(extrasOut).isNotNull(); 259 assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE); 260 261 extrasOut = controller.getExtras(); 262 assertThat(extrasOut).isNotNull(); 263 assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE); 264 265 // test setFlags 266 mSession.setFlags(5); 267 assertThat(controller.getFlags()).isEqualTo(5); 268 269 // test setMetadata 270 mCallback.resetLocked(); 271 MediaMetadata metadata = 272 new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build(); 273 mSession.setMetadata(metadata); 274 mWaitLock.wait(TIME_OUT_MS); 275 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 276 // just call the callback once directly so it's marked as tested 277 callback.onMetadataChanged(mCallback.mMediaMetadata); 278 279 MediaMetadata metadataOut = mCallback.mMediaMetadata; 280 assertThat(metadataOut).isNotNull(); 281 assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 282 283 metadataOut = controller.getMetadata(); 284 assertThat(metadataOut).isNotNull(); 285 assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 286 287 // test setPlaybackState 288 mCallback.resetLocked(); 289 PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build(); 290 mSession.setPlaybackState(state); 291 mWaitLock.wait(TIME_OUT_MS); 292 assertThat(mCallback.mOnPlaybackStateChangedCalled).isTrue(); 293 // just call the callback once directly so it's marked as tested 294 callback.onPlaybackStateChanged(mCallback.mPlaybackState); 295 296 PlaybackState stateOut = mCallback.mPlaybackState; 297 assertThat(stateOut).isNotNull(); 298 assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION); 299 300 stateOut = controller.getPlaybackState(); 301 assertThat(stateOut).isNotNull(); 302 assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION); 303 304 // test setQueue and setQueueTitle 305 mCallback.resetLocked(); 306 List<QueueItem> queue = new ArrayList<>(); 307 QueueItem item = new QueueItem(new MediaDescription.Builder() 308 .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID); 309 queue.add(item); 310 mSession.setQueue(queue); 311 mWaitLock.wait(TIME_OUT_MS); 312 assertThat(mCallback.mOnQueueChangedCalled).isTrue(); 313 // just call the callback once directly so it's marked as tested 314 callback.onQueueChanged(mCallback.mQueue); 315 316 mSession.setQueueTitle(TEST_VALUE); 317 mWaitLock.wait(TIME_OUT_MS); 318 assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue(); 319 320 assertThat(mCallback.mTitle).isEqualTo(TEST_VALUE); 321 assertThat(mCallback.mQueue.size()).isEqualTo(queue.size()); 322 assertThat(mCallback.mQueue.get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID); 323 assertThat(mCallback.mQueue.get(0).getDescription().getMediaId()).isEqualTo(TEST_VALUE); 324 325 assertThat(controller.getQueueTitle()).isEqualTo(TEST_VALUE); 326 assertThat(controller.getQueue().size()).isEqualTo(queue.size()); 327 assertThat(controller.getQueue().get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID); 328 assertThat(controller.getQueue().get(0).getDescription().getMediaId()) 329 .isEqualTo(TEST_VALUE); 330 331 mCallback.resetLocked(); 332 mSession.setQueue(null); 333 mWaitLock.wait(TIME_OUT_MS); 334 assertThat(mCallback.mOnQueueChangedCalled).isTrue(); 335 // just call the callback once directly so it's marked as tested 336 callback.onQueueChanged(mCallback.mQueue); 337 338 mSession.setQueueTitle(null); 339 mWaitLock.wait(TIME_OUT_MS); 340 assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue(); 341 // just call the callback once directly so it's marked as tested 342 callback.onQueueTitleChanged(mCallback.mTitle); 343 344 assertThat(mCallback.mTitle).isNull(); 345 assertThat(mCallback.mQueue).isNull(); 346 assertThat(controller.getQueueTitle()).isNull(); 347 assertThat(controller.getQueue()).isNull(); 348 349 // test setSessionActivity 350 Intent intent = new Intent("cts.MEDIA_SESSION_ACTION"); 351 PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 352 PendingIntent.FLAG_MUTABLE_UNAUDITED); 353 mSession.setSessionActivity(pi); 354 assertThat(controller.getSessionActivity()).isEqualTo(pi); 355 356 // test setActivity 357 mSession.setActive(true); 358 assertThat(mSession.isActive()).isTrue(); 359 360 // test sendSessionEvent 361 mCallback.resetLocked(); 362 mSession.sendSessionEvent(TEST_SESSION_EVENT, extras); 363 mWaitLock.wait(TIME_OUT_MS); 364 365 assertThat(mCallback.mOnSessionEventCalled).isTrue(); 366 assertThat(mCallback.mEvent).isEqualTo(TEST_SESSION_EVENT); 367 assertThat(mCallback.mExtras.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 368 // just call the callback once directly so it's marked as tested 369 callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras); 370 371 // test release 372 mCallback.resetLocked(); 373 mSession.release(); 374 mWaitLock.wait(TIME_OUT_MS); 375 assertThat(mCallback.mOnSessionDestroyedCalled).isTrue(); 376 // just call the callback once directly so it's marked as tested 377 callback.onSessionDestroyed(); 378 } 379 } 380 381 @Test setMediaSession_withInaccessibleUri_uriCleared()382 public void setMediaSession_withInaccessibleUri_uriCleared() throws Exception { 383 createCloneProfile(); 384 Assume.assumeTrue(mCloneProfileId.isPresent()); 385 String testMediaUri = 386 "content://" + mCloneProfileId.get() + "@media/external/images/media/"; 387 // Save a screenshot in second user files. 388 runShellCommand("screencap -p " + testMediaUri); 389 390 MediaController controller = mSession.getController(); 391 controller.registerCallback(mCallback, mHandler); 392 final MediaController.Callback callback = mCallback; 393 394 synchronized (mWaitLock) { 395 // test setMetadata 396 mCallback.resetLocked(); 397 MediaMetadata metadata = 398 new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI, 399 testMediaUri).build(); 400 mSession.setMetadata(metadata); 401 mWaitLock.wait(TIME_OUT_MS); 402 403 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 404 // just call the callback once directly so it's marked as tested 405 callback.onMetadataChanged(mCallback.mMediaMetadata); 406 407 MediaMetadata metadataOut = mCallback.mMediaMetadata; 408 assertThat(metadataOut).isNotNull(); 409 assertThat(TextUtils.isEmpty(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI))) 410 .isTrue(); 411 } 412 } 413 414 @Test setMediaSession_withUri_uriExists()415 public void setMediaSession_withUri_uriExists() throws Exception { 416 String testMediaUri = "content://media/external/images/media/"; 417 MediaController controller = mSession.getController(); 418 controller.registerCallback(mCallback, mHandler); 419 final MediaController.Callback callback = mCallback; 420 421 synchronized (mWaitLock) { 422 // test setMetadata 423 mCallback.resetLocked(); 424 MediaMetadata metadata = 425 new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI, 426 testMediaUri).build(); 427 mSession.setMetadata(metadata); 428 mWaitLock.wait(TIME_OUT_MS); 429 430 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 431 // just call the callback once directly so it's marked as tested 432 callback.onMetadataChanged(mCallback.mMediaMetadata); 433 434 MediaMetadata metadataOut = mCallback.mMediaMetadata; 435 assertThat(metadataOut).isNotNull(); 436 assertThat(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI)) 437 .isEqualTo(testMediaUri); 438 } 439 } 440 441 /** 442 * Test whether media button receiver can be a explicit broadcast receiver via 443 * MediaSession.setMediaButtonReceiver(PendingIntent). 444 */ 445 @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable. 446 @Test testSetMediaButtonReceiver_broadcastReceiver()447 public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception { 448 Intent intent = new Intent(mContext.getApplicationContext(), 449 MediaButtonBroadcastReceiver.class); 450 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 451 PendingIntent.FLAG_MUTABLE_UNAUDITED); 452 453 // Play a sound so this session can get the priority. 454 Utils.assertMediaPlaybackStarted(getContext()); 455 456 // There is a different MediaSession that's created from StubMediaBrowserService class. 457 // This session takes over all the callbacks. we need to change the state of our session 458 // to STATE_PLAYING so it has higher priority. 459 setPlaybackState(PlaybackState.STATE_PLAYING); 460 461 // Sets the media button receiver. Framework will keep the broadcast receiver component name 462 // from the pending intent in persistent storage. 463 mSession.setMediaButtonReceiver(pi); 464 465 // Call explicit release, so change in the media key event session can be notified with the 466 // pending intent. 467 mSession.release(); 468 469 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 470 try { 471 CountDownLatch latch = new CountDownLatch(2); 472 MediaButtonBroadcastReceiver.setCallback((keyEvent) -> { 473 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 474 switch ((int) latch.getCount()) { 475 case 2: 476 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 477 break; 478 case 1: 479 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 480 break; 481 } 482 latch.countDown(); 483 }); 484 // Also try to dispatch media key event. 485 // System would try to dispatch event. 486 simulateMediaKeyInput(keyCode); 487 488 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 489 } finally { 490 MediaButtonBroadcastReceiver.setCallback(null); 491 } 492 } 493 494 /** 495 * Test whether media button receiver can be a explicit service. 496 */ 497 @Test testSetMediaButtonReceiver_service()498 public void testSetMediaButtonReceiver_service() throws Exception { 499 Intent intent = new Intent(mContext.getApplicationContext(), 500 MediaButtonReceiverService.class); 501 PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 502 PendingIntent.FLAG_MUTABLE_UNAUDITED); 503 504 // Play a sound so this session can get the priority. 505 Utils.assertMediaPlaybackStarted(getContext()); 506 507 // There is a different MediaSession that's created from StubMediaBrowserService class. 508 // This session takes over all the callbacks. we need to change the state of our session 509 // to STATE_PLAYING so it has higher priority. 510 setPlaybackState(PlaybackState.STATE_PLAYING); 511 512 // Sets the media button receiver. Framework would try to keep the pending intent in the 513 // persistent store. 514 mSession.setMediaButtonReceiver(pi); 515 516 // Call explicit release, so change in the media key event session can be notified with the 517 // pending intent. 518 mSession.release(); 519 520 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 521 try { 522 CountDownLatch latch = new CountDownLatch(2); 523 MediaButtonReceiverService.setCallback((keyEvent) -> { 524 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 525 switch ((int) latch.getCount()) { 526 case 2: 527 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 528 break; 529 case 1: 530 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 531 break; 532 } 533 latch.countDown(); 534 }); 535 // Also try to dispatch media key event. 536 // System would try to dispatch event. 537 simulateMediaKeyInput(keyCode); 538 539 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 540 } finally { 541 MediaButtonReceiverService.setCallback(null); 542 } 543 } 544 545 /** 546 * Test whether system doesn't crash by 547 * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent. 548 */ 549 @Test testSetMediaButtonReceiver_implicitIntent()550 public void testSetMediaButtonReceiver_implicitIntent() throws Exception { 551 // Note: No such broadcast receiver exists. 552 Intent intent = new Intent("android.media.bettertogether.cts.ACTION_MEDIA_TEST"); 553 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 554 PendingIntent.FLAG_MUTABLE_UNAUDITED); 555 556 // Play a sound so this session can get the priority. 557 Utils.assertMediaPlaybackStarted(getContext()); 558 559 // Sets the media button receiver. Framework would try to keep the pending intent in the 560 // persistent store. 561 mSession.setMediaButtonReceiver(pi); 562 563 // Call explicit release, so change in the media key event session can be notified with the 564 // pending intent. 565 mSession.release(); 566 567 // Also try to dispatch media key event. System would try to send key event via pending 568 // intent, but it would no-op because there's no receiver. 569 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 570 } 571 572 @Test testSetMediaButtonReceiver_withNull_doesNotThrow()573 public void testSetMediaButtonReceiver_withNull_doesNotThrow() { 574 try { 575 mSession.setMediaButtonReceiver(null); 576 } finally { 577 mSession.release(); 578 } 579 } 580 581 /** 582 * Test whether media button receiver can be a explicit broadcast receiver via 583 * MediaSession.setMediaButtonBroadcastReceiver(ComponentName) 584 */ 585 @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable. 586 @Test testSetMediaButtonBroadcastReceiver_broadcastReceiver()587 public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception { 588 // Play a sound so this session can get the priority. 589 Utils.assertMediaPlaybackStarted(getContext()); 590 591 // Sets the broadcast receiver's component name. Framework will keep the component name in 592 // persistent storage. 593 mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext, 594 MediaButtonBroadcastReceiver.class)); 595 596 // There is a different MediaSession that's created from StubMediaBrowserService class. 597 // This session takes over all the callbacks. we need to change the state of our session 598 // to STATE_PLAYING so it has higher priority. 599 setPlaybackState(PlaybackState.STATE_PLAYING); 600 601 // Call explicit release, so change in the media key event session can be notified using the 602 // component name. 603 mSession.release(); 604 605 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 606 try { 607 CountDownLatch latch = new CountDownLatch(2); 608 MediaButtonBroadcastReceiver.setCallback((keyEvent) -> { 609 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 610 switch ((int) latch.getCount()) { 611 case 2: 612 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 613 break; 614 case 1: 615 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 616 break; 617 } 618 latch.countDown(); 619 }); 620 // Also try to dispatch media key event. 621 // System would try to dispatch event. 622 simulateMediaKeyInput(keyCode); 623 624 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 625 } finally { 626 MediaButtonBroadcastReceiver.setCallback(null); 627 } 628 } 629 630 /** 631 * Test public APIs of {@link VolumeProvider}. 632 */ 633 @Test testVolumeProvider()634 public void testVolumeProvider() { 635 VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_RELATIVE, 636 TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {}; 637 assertThat(vp.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_RELATIVE); 638 assertThat(vp.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME); 639 assertThat(vp.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME); 640 assertThat(vp.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID); 641 } 642 643 /** 644 * Test {@link MediaSession#setPlaybackToLocal} and {@link MediaSession#setPlaybackToRemote}. 645 */ 646 @Test testPlaybackToLocalAndRemote()647 public void testPlaybackToLocalAndRemote() throws Exception { 648 MediaController controller = mSession.getController(); 649 controller.registerCallback(mCallback, mHandler); 650 651 synchronized (mWaitLock) { 652 // test setPlaybackToRemote, do this before testing setPlaybackToLocal 653 // to ensure it switches correctly. 654 mCallback.resetLocked(); 655 try { 656 mSession.setPlaybackToRemote(null); 657 assertWithMessage("Expected IAE for setPlaybackToRemote(null)").fail(); 658 } catch (IllegalArgumentException e) { 659 // expected 660 } 661 VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED, 662 TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {}; 663 mSession.setPlaybackToRemote(vp); 664 665 MediaController.PlaybackInfo info = null; 666 for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) { 667 mCallback.mOnAudioInfoChangedCalled = false; 668 mWaitLock.wait(TIME_OUT_MS); 669 assertThat(mCallback.mOnAudioInfoChangedCalled).isTrue(); 670 info = mCallback.mPlaybackInfo; 671 if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME 672 && info.getMaxVolume() == TEST_MAX_VOLUME 673 && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED 674 && info.getPlaybackType() 675 == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE 676 && TextUtils.equals(info.getVolumeControlId(), TEST_VOLUME_CONTROL_ID)) { 677 break; 678 } 679 } 680 assertThat(info).isNotNull(); 681 assertThat(info.getPlaybackType()) 682 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE); 683 assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME); 684 assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME); 685 assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED); 686 assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID); 687 688 info = controller.getPlaybackInfo(); 689 assertThat(info).isNotNull(); 690 assertThat(info.getPlaybackType()) 691 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE); 692 assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME); 693 assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME); 694 assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED); 695 assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID); 696 697 // test setPlaybackToLocal 698 AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build(); 699 mSession.setPlaybackToLocal(attrs); 700 701 info = controller.getPlaybackInfo(); 702 assertThat(info).isNotNull(); 703 assertThat(info.getPlaybackType()) 704 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL); 705 assertThat(info.getAudioAttributes()).isEqualTo(attrs); 706 assertThat(info.getVolumeControlId()).isNull(); 707 } 708 } 709 710 /** 711 * Test {@link MediaSession.Callback#onMediaButtonEvent}. 712 */ 713 @Test testCallbackOnMediaButtonEvent()714 public void testCallbackOnMediaButtonEvent() throws Exception { 715 MediaSessionCallback sessionCallback = new MediaSessionCallback(); 716 mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper())); 717 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); 718 mSession.setActive(true); 719 720 // Set state to STATE_PLAYING to get higher priority. 721 setPlaybackState(PlaybackState.STATE_PLAYING); 722 723 // A media playback is also needed to receive media key events. 724 Utils.assertMediaPlaybackStarted(getContext()); 725 726 sessionCallback.reset(1); 727 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY); 728 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 729 assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1); 730 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 731 732 sessionCallback.reset(1); 733 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PAUSE); 734 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 735 assertThat(sessionCallback.mOnPauseCalled).isTrue(); 736 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 737 738 sessionCallback.reset(1); 739 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_NEXT); 740 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 741 assertThat(sessionCallback.mOnSkipToNextCalled).isTrue(); 742 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 743 744 sessionCallback.reset(1); 745 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PREVIOUS); 746 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 747 assertThat(sessionCallback.mOnSkipToPreviousCalled).isTrue(); 748 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 749 750 sessionCallback.reset(1); 751 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP); 752 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 753 assertThat(sessionCallback.mOnStopCalled).isTrue(); 754 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 755 756 sessionCallback.reset(1); 757 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); 758 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 759 assertThat(sessionCallback.mOnFastForwardCalled).isTrue(); 760 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 761 762 sessionCallback.reset(1); 763 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_REWIND); 764 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 765 assertThat(sessionCallback.mOnRewindCalled).isTrue(); 766 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 767 768 // Test PLAY_PAUSE button twice. 769 // First, simulate PLAY_PAUSE button while in STATE_PAUSED. 770 sessionCallback.reset(1); 771 setPlaybackState(PlaybackState.STATE_PAUSED); 772 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 773 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 774 assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1); 775 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 776 777 // Next, simulate PLAY_PAUSE button while in STATE_PLAYING. 778 sessionCallback.reset(1); 779 setPlaybackState(PlaybackState.STATE_PLAYING); 780 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 781 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 782 assertThat(sessionCallback.mOnPauseCalled).isTrue(); 783 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 784 785 // Double tap of PLAY_PAUSE is the next track instead of changing PLAY/PAUSE. 786 sessionCallback.reset(2); 787 setPlaybackState(PlaybackState.STATE_PLAYING); 788 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 789 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 790 assertThat(sessionCallback.await(WAIT_MS)).isFalse(); 791 assertThat(sessionCallback.mOnSkipToNextCalled).isTrue(); 792 assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(0); 793 assertThat(sessionCallback.mOnPauseCalled).isFalse(); 794 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 795 796 // Test if PLAY_PAUSE double tap is considered as two single taps when another media 797 // key is pressed. 798 sessionCallback.reset(3); 799 setPlaybackState(PlaybackState.STATE_PAUSED); 800 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 801 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP); 802 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 803 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 804 assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(2); 805 assertThat(sessionCallback.mOnStopCalled).isTrue(); 806 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 807 808 // Test if media keys are handled in order. 809 sessionCallback.reset(2); 810 setPlaybackState(PlaybackState.STATE_PAUSED); 811 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 812 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP); 813 assertThat(sessionCallback.await(TIME_OUT_MS)).isTrue(); 814 assertThat(sessionCallback.mOnPlayCalledCount).isEqualTo(1); 815 assertThat(sessionCallback.mOnStopCalled).isTrue(); 816 assertThat(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo)).isTrue(); 817 synchronized (mWaitLock) { 818 assertThat(mSession.getController().getPlaybackState().getState()) 819 .isEqualTo(PlaybackState.STATE_STOPPED); 820 } 821 } 822 823 /** 824 * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called 825 * once {@code setCallback(null)} is done. 826 */ 827 @Test testSetCallbackWithNull()828 public void testSetCallbackWithNull() throws Exception { 829 MediaSessionCallback sessionCallback = new MediaSessionCallback(); 830 mSession.setCallback(sessionCallback, mHandler); 831 mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 832 mSession.setActive(true); 833 834 MediaController controller = mSession.getController(); 835 setPlaybackState(PlaybackState.STATE_PLAYING); 836 837 sessionCallback.reset(1); 838 mSession.setCallback(null, mHandler); 839 840 controller.getTransportControls().pause(); 841 assertThat(sessionCallback.await(WAIT_MS)).isFalse(); 842 assertWithMessage("Callback shouldn't be called.") 843 .that(sessionCallback.mOnPauseCalled).isFalse(); 844 } 845 setPlaybackState(int state)846 private void setPlaybackState(int state) { 847 final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE 848 | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP 849 | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS 850 | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND; 851 PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions) 852 .setState(state, 0L, 0.0f).build(); 853 synchronized (mWaitLock) { 854 mSession.setPlaybackState(playbackState); 855 } 856 } 857 858 /** 859 * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app 860 * which receives the media key events. 861 * See: b/36669550 862 */ 863 @Test testReleaseNoCrashWithMultipleSessions()864 public void testReleaseNoCrashWithMultipleSessions() throws Exception { 865 // Start a media playback for this app to receive media key events. 866 Utils.assertMediaPlaybackStarted(getContext()); 867 868 MediaSession anotherSession = null; 869 try { 870 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 871 mSession.release(); 872 anotherSession.release(); 873 874 // Try release with the different order. 875 mSession = new MediaSession(getContext(), TEST_SESSION_TAG); 876 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 877 anotherSession.release(); 878 mSession.release(); 879 } finally { 880 if (anotherSession != null) { 881 anotherSession.release(); 882 anotherSession = null; 883 } 884 } 885 } 886 887 // This uses public APIs to dispatch key events, so sessions would consider this as 888 // 'media key event from this application'. simulateMediaKeyInput(int keyCode)889 private void simulateMediaKeyInput(int keyCode) { 890 long downTime = System.currentTimeMillis(); 891 mAudioManager.dispatchMediaKeyEvent( 892 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0)); 893 mAudioManager.dispatchMediaKeyEvent( 894 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0)); 895 } 896 897 /** 898 * Tests {@link MediaSession.QueueItem}. 899 */ 900 @Test testQueueItem()901 public void testQueueItem() { 902 MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder() 903 .setMediaId("media-id") 904 .setTitle("title"); 905 906 try { 907 new QueueItem(/*description=*/null, TEST_QUEUE_ID); 908 assertWithMessage("Unreachable statement.").fail(); 909 } catch (IllegalArgumentException e) { 910 // Expected 911 } 912 try { 913 new QueueItem(descriptionBuilder.build(), QueueItem.UNKNOWN_ID); 914 assertWithMessage("Unreachable statement.").fail(); 915 } catch (IllegalArgumentException e) { 916 // Expected 917 } 918 919 QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID); 920 921 Parcel p = Parcel.obtain(); 922 item.writeToParcel(p, 0); 923 p.setDataPosition(0); 924 QueueItem other = QueueItem.CREATOR.createFromParcel(p); 925 assertThat(other.toString()).isEqualTo(item.toString()); 926 p.recycle(); 927 928 final int arraySize = 5; 929 QueueItem[] queueItemArray = QueueItem.CREATOR.newArray(arraySize); 930 assertThat(queueItemArray).isNotNull(); 931 assertThat(queueItemArray.length).isEqualTo(arraySize); 932 for (QueueItem elem : queueItemArray) { 933 assertThat(elem).isNull(); 934 } 935 } 936 937 @Test testQueueItemEquals()938 public void testQueueItemEquals() { 939 MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder() 940 .setMediaId("media-id") 941 .setTitle("title"); 942 943 QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID); 944 assertThat(item.getQueueId()).isEqualTo(TEST_QUEUE_ID); 945 assertThat(item.getDescription().getMediaId()).isEqualTo("media-id"); 946 assertThat(item.getDescription().getTitle()).isEqualTo("title"); 947 assertThat(item.describeContents()).isEqualTo(0); 948 949 assertThat(item.equals(null)).isFalse(); 950 assertThat(item).isNotEqualTo(descriptionBuilder.build()); 951 952 QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID); 953 assertThat(item.equals(sameItem)).isTrue(); 954 955 QueueItem differentQueueId = new QueueItem( 956 descriptionBuilder.build(), TEST_QUEUE_ID + 1); 957 assertThat(item.equals(differentQueueId)).isFalse(); 958 959 QueueItem differentDescription = new QueueItem( 960 descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID); 961 assertThat(item.equals(differentDescription)).isFalse(); 962 } 963 964 @Test testSessionInfoWithFrameworkParcelable()965 public void testSessionInfoWithFrameworkParcelable() { 966 final String testKey = "test_key"; 967 final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build(); 968 969 Bundle sessionInfo = new Bundle(); 970 sessionInfo.putParcelable(testKey, frameworkParcelable); 971 972 MediaSession session = null; 973 try { 974 session = new MediaSession( 975 mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo); 976 Bundle sessionInfoOut = session.getController().getSessionInfo(); 977 assertThat(sessionInfoOut.containsKey(testKey)).isTrue(); 978 assertThat((AudioAttributes) sessionInfoOut.getParcelable(testKey)) 979 .isEqualTo(frameworkParcelable); 980 } finally { 981 if (session != null) { 982 session.release(); 983 } 984 } 985 986 } 987 988 @Test testSessionInfoWithCustomParcelable()989 public void testSessionInfoWithCustomParcelable() { 990 final String testKey = "test_key"; 991 final MediaSession2Test.CustomParcelable customParcelable = 992 new MediaSession2Test.CustomParcelable(1); 993 994 Bundle sessionInfo = new Bundle(); 995 sessionInfo.putParcelable(testKey, customParcelable); 996 997 MediaSession session = null; 998 try { 999 session = new MediaSession( 1000 mContext, "testSessionInfoWithCustomParcelable", sessionInfo); 1001 assertWithMessage("Custom Parcelable shouldn't be accepted!").fail(); 1002 } catch (IllegalArgumentException e) { 1003 // Expected 1004 } finally { 1005 if (session != null) { 1006 session.release(); 1007 } 1008 } 1009 } 1010 1011 /** 1012 * An app should not be able to create too many sessions. 1013 * See MediaSessionService#SESSION_CREATION_LIMIT_PER_UID 1014 */ 1015 @Test testSessionCreationLimit()1016 public void testSessionCreationLimit() { 1017 List<MediaSession> sessions = new ArrayList<>(); 1018 try { 1019 for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) { 1020 sessions.add(new MediaSession(mContext, "testSessionCreationLimit")); 1021 } 1022 assertWithMessage("The number of session should be limited!").fail(); 1023 } catch (RuntimeException e) { 1024 // Expected 1025 } finally { 1026 for (MediaSession session : sessions) { 1027 session.release(); 1028 } 1029 } 1030 } 1031 1032 /** 1033 * Check that calling {@link MediaSession#release()} multiple times for the same session 1034 * does not decrement current session count multiple times. 1035 */ 1036 @Test testSessionCreationLimitWithMediaSessionRelease()1037 public void testSessionCreationLimitWithMediaSessionRelease() { 1038 List<MediaSession> sessions = new ArrayList<>(); 1039 MediaSession sessionToReleaseMultipleTimes = null; 1040 try { 1041 sessionToReleaseMultipleTimes = new MediaSession( 1042 mContext, "testSessionCreationLimitWithMediaSessionRelease"); 1043 for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) { 1044 sessions.add(new MediaSession( 1045 mContext, "testSessionCreationLimitWithMediaSessionRelease")); 1046 // Call release() many times with the same session. 1047 sessionToReleaseMultipleTimes.release(); 1048 } 1049 assertWithMessage("The number of session should be limited!").fail(); 1050 } catch (RuntimeException e) { 1051 // Expected 1052 } finally { 1053 for (MediaSession session : sessions) { 1054 session.release(); 1055 } 1056 if (sessionToReleaseMultipleTimes != null) { 1057 sessionToReleaseMultipleTimes.release(); 1058 } 1059 } 1060 } 1061 1062 /** 1063 * Check that calling {@link MediaSession2#close()} does not decrement current session count. 1064 */ 1065 @Test testSessionCreationLimitWithMediaSession2Release()1066 public void testSessionCreationLimitWithMediaSession2Release() { 1067 List<MediaSession> sessions = new ArrayList<>(); 1068 try { 1069 for (int i = 0; i < 1000; i++) { 1070 sessions.add(new MediaSession( 1071 mContext, "testSessionCreationLimitWithMediaSession2Release")); 1072 1073 try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) { 1074 // Do nothing 1075 } 1076 } 1077 assertWithMessage("The number of session should be limited!").fail(); 1078 } catch (RuntimeException e) { 1079 // Expected 1080 } finally { 1081 for (MediaSession session : sessions) { 1082 session.release(); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController} 1089 * on the remote process due to binder buffer overflow. 1090 */ 1091 @Test testSeriesOfSetQueue()1092 public void testSeriesOfSetQueue() throws Exception { 1093 int numberOfCalls = 100; 1094 int queueSize = 1_000; 1095 List<QueueItem> queue = new ArrayList<>(); 1096 for (int id = 0; id < queueSize; id++) { 1097 MediaDescription description = new MediaDescription.Builder() 1098 .setMediaId(Integer.toString(id)).build(); 1099 queue.add(new QueueItem(description, id)); 1100 } 1101 1102 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1103 MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) { 1104 Bundle args = new Bundle(); 1105 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1106 args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize); 1107 invoker.run(STEP_SET_UP, args); 1108 for (int i = 0; i < numberOfCalls; i++) { 1109 mSession.setQueue(queue); 1110 } 1111 invoker.run(STEP_CHECK); 1112 invoker.run(STEP_CLEAN_UP); 1113 } 1114 } 1115 1116 @Test testSetQueueWithLargeNumberOfItems()1117 public void testSetQueueWithLargeNumberOfItems() throws Exception { 1118 int queueSize = 500_000; 1119 List<QueueItem> queue = new ArrayList<>(); 1120 for (int id = 0; id < queueSize; id++) { 1121 MediaDescription description = new MediaDescription.Builder() 1122 .setMediaId(Integer.toString(id)).build(); 1123 queue.add(new QueueItem(description, id)); 1124 } 1125 1126 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1127 MediaSessionTestService.class, TEST_SET_QUEUE)) { 1128 Bundle args = new Bundle(); 1129 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1130 args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize); 1131 invoker.run(STEP_SET_UP, args); 1132 mSession.setQueue(queue); 1133 invoker.run(STEP_CHECK); 1134 invoker.run(STEP_CLEAN_UP); 1135 } 1136 } 1137 1138 @Test testSetQueueWithEmptyQueue()1139 public void testSetQueueWithEmptyQueue() throws Exception { 1140 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1141 MediaSessionTestService.class, TEST_SET_QUEUE)) { 1142 Bundle args = new Bundle(); 1143 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1144 args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0); 1145 invoker.run(STEP_SET_UP, args); 1146 mSession.setQueue(Collections.emptyList()); 1147 invoker.run(STEP_CHECK); 1148 invoker.run(STEP_CLEAN_UP); 1149 } 1150 } 1151 1152 /** 1153 * Verifies that a new session hasn't had any configuration bits set yet. 1154 * 1155 * @param controller The controller for the session 1156 */ 1157 @SuppressWarnings("ReturnValueIgnored") verifyNewSession(MediaController controller)1158 private void verifyNewSession(MediaController controller) { 1159 assertWithMessage("New session has unexpected configuration") 1160 .that(controller.getFlags()).isEqualTo(0); 1161 assertWithMessage("New session has unexpected configuration") 1162 .that(controller.getExtras()).isNull(); 1163 assertWithMessage("New session has unexpected configuration") 1164 .that(controller.getMetadata()).isNull(); 1165 assertWithMessage("New session has unexpected configuration") 1166 .that(controller.getPackageName()).isEqualTo(getContext().getPackageName()); 1167 assertWithMessage("New session has unexpected configuration") 1168 .that(controller.getPlaybackState()).isNull(); 1169 assertWithMessage("New session has unexpected configuration") 1170 .that(controller.getQueue()).isNull(); 1171 assertWithMessage("New session has unexpected configuration") 1172 .that(controller.getQueueTitle()).isNull(); 1173 assertWithMessage("New session has unexpected configuration") 1174 .that(controller.getRatingType()).isEqualTo(Rating.RATING_NONE); 1175 assertWithMessage("New session has unexpected configuration") 1176 .that(controller.getSessionActivity()).isNull(); 1177 1178 assertThat(controller.getSessionToken()).isNotNull(); 1179 assertThat(controller.getTransportControls()).isNotNull(); 1180 1181 MediaController.PlaybackInfo info = controller.getPlaybackInfo(); 1182 assertThat(info).isNotNull(); 1183 info.toString(); // Test that calling PlaybackInfo.toString() does not crash. 1184 assertThat(info.getPlaybackType()) 1185 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL); 1186 AudioAttributes attrs = info.getAudioAttributes(); 1187 assertThat(attrs).isNotNull(); 1188 assertThat(attrs.getUsage()).isEqualTo(USAGE_MEDIA); 1189 assertThat(info.getCurrentVolume()) 1190 .isEqualTo(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)); 1191 } 1192 1193 private class MediaControllerCallback extends MediaController.Callback { 1194 private volatile boolean mOnPlaybackStateChangedCalled; 1195 private volatile boolean mOnMetadataChangedCalled; 1196 private volatile boolean mOnQueueChangedCalled; 1197 private volatile boolean mOnQueueTitleChangedCalled; 1198 private volatile boolean mOnExtraChangedCalled; 1199 private volatile boolean mOnAudioInfoChangedCalled; 1200 private volatile boolean mOnSessionDestroyedCalled; 1201 private volatile boolean mOnSessionEventCalled; 1202 1203 private volatile PlaybackState mPlaybackState; 1204 private volatile MediaMetadata mMediaMetadata; 1205 private volatile List<QueueItem> mQueue; 1206 private volatile CharSequence mTitle; 1207 private volatile String mEvent; 1208 private volatile Bundle mExtras; 1209 private volatile MediaController.PlaybackInfo mPlaybackInfo; 1210 resetLocked()1211 public void resetLocked() { 1212 mOnPlaybackStateChangedCalled = false; 1213 mOnMetadataChangedCalled = false; 1214 mOnQueueChangedCalled = false; 1215 mOnQueueTitleChangedCalled = false; 1216 mOnExtraChangedCalled = false; 1217 mOnAudioInfoChangedCalled = false; 1218 mOnSessionDestroyedCalled = false; 1219 mOnSessionEventCalled = false; 1220 1221 mPlaybackState = null; 1222 mMediaMetadata = null; 1223 mQueue = null; 1224 mTitle = null; 1225 mExtras = null; 1226 mPlaybackInfo = null; 1227 } 1228 1229 @Override onPlaybackStateChanged(PlaybackState state)1230 public void onPlaybackStateChanged(PlaybackState state) { 1231 synchronized (mWaitLock) { 1232 mOnPlaybackStateChangedCalled = true; 1233 mPlaybackState = state; 1234 mWaitLock.notify(); 1235 } 1236 } 1237 1238 @Override onMetadataChanged(MediaMetadata metadata)1239 public void onMetadataChanged(MediaMetadata metadata) { 1240 synchronized (mWaitLock) { 1241 mOnMetadataChangedCalled = true; 1242 mMediaMetadata = metadata; 1243 mWaitLock.notify(); 1244 } 1245 } 1246 1247 @Override onQueueChanged(List<QueueItem> queue)1248 public void onQueueChanged(List<QueueItem> queue) { 1249 synchronized (mWaitLock) { 1250 mOnQueueChangedCalled = true; 1251 mQueue = queue; 1252 mWaitLock.notify(); 1253 } 1254 } 1255 1256 @Override onQueueTitleChanged(CharSequence title)1257 public void onQueueTitleChanged(CharSequence title) { 1258 synchronized (mWaitLock) { 1259 mOnQueueTitleChangedCalled = true; 1260 mTitle = title; 1261 mWaitLock.notify(); 1262 } 1263 } 1264 1265 @Override onExtrasChanged(Bundle extras)1266 public void onExtrasChanged(Bundle extras) { 1267 synchronized (mWaitLock) { 1268 mOnExtraChangedCalled = true; 1269 mExtras = extras; 1270 mWaitLock.notify(); 1271 } 1272 } 1273 1274 @Override onAudioInfoChanged(MediaController.PlaybackInfo info)1275 public void onAudioInfoChanged(MediaController.PlaybackInfo info) { 1276 synchronized (mWaitLock) { 1277 mOnAudioInfoChangedCalled = true; 1278 mPlaybackInfo = info; 1279 mWaitLock.notify(); 1280 } 1281 } 1282 1283 @Override onSessionDestroyed()1284 public void onSessionDestroyed() { 1285 synchronized (mWaitLock) { 1286 mOnSessionDestroyedCalled = true; 1287 mWaitLock.notify(); 1288 } 1289 } 1290 1291 @Override onSessionEvent(String event, Bundle extras)1292 public void onSessionEvent(String event, Bundle extras) { 1293 synchronized (mWaitLock) { 1294 mOnSessionEventCalled = true; 1295 mEvent = event; 1296 mExtras = (Bundle) extras.clone(); 1297 mWaitLock.notify(); 1298 } 1299 } 1300 } 1301 1302 private class MediaSessionCallback extends MediaSession.Callback { 1303 private CountDownLatch mLatch; 1304 private int mOnPlayCalledCount; 1305 private boolean mOnPauseCalled; 1306 private boolean mOnStopCalled; 1307 private boolean mOnFastForwardCalled; 1308 private boolean mOnRewindCalled; 1309 private boolean mOnSkipToPreviousCalled; 1310 private boolean mOnSkipToNextCalled; 1311 private RemoteUserInfo mCallerInfo; 1312 reset(int count)1313 public void reset(int count) { 1314 mLatch = new CountDownLatch(count); 1315 mOnPlayCalledCount = 0; 1316 mOnPauseCalled = false; 1317 mOnStopCalled = false; 1318 mOnFastForwardCalled = false; 1319 mOnRewindCalled = false; 1320 mOnSkipToPreviousCalled = false; 1321 mOnSkipToNextCalled = false; 1322 } 1323 await(long waitMs)1324 public boolean await(long waitMs) { 1325 try { 1326 return mLatch.await(waitMs, TimeUnit.MILLISECONDS); 1327 } catch (InterruptedException e) { 1328 return false; 1329 } 1330 } 1331 1332 @Override onPlay()1333 public void onPlay() { 1334 mOnPlayCalledCount++; 1335 mCallerInfo = mSession.getCurrentControllerInfo(); 1336 setPlaybackState(PlaybackState.STATE_PLAYING); 1337 mLatch.countDown(); 1338 } 1339 1340 @Override onPause()1341 public void onPause() { 1342 mOnPauseCalled = true; 1343 mCallerInfo = mSession.getCurrentControllerInfo(); 1344 setPlaybackState(PlaybackState.STATE_PAUSED); 1345 mLatch.countDown(); 1346 } 1347 1348 @Override onStop()1349 public void onStop() { 1350 mOnStopCalled = true; 1351 mCallerInfo = mSession.getCurrentControllerInfo(); 1352 setPlaybackState(PlaybackState.STATE_STOPPED); 1353 mLatch.countDown(); 1354 } 1355 1356 @Override onFastForward()1357 public void onFastForward() { 1358 mOnFastForwardCalled = true; 1359 mCallerInfo = mSession.getCurrentControllerInfo(); 1360 mLatch.countDown(); 1361 } 1362 1363 @Override onRewind()1364 public void onRewind() { 1365 mOnRewindCalled = true; 1366 mCallerInfo = mSession.getCurrentControllerInfo(); 1367 mLatch.countDown(); 1368 } 1369 1370 @Override onSkipToPrevious()1371 public void onSkipToPrevious() { 1372 mOnSkipToPreviousCalled = true; 1373 mCallerInfo = mSession.getCurrentControllerInfo(); 1374 mLatch.countDown(); 1375 } 1376 1377 @Override onSkipToNext()1378 public void onSkipToNext() { 1379 mOnSkipToNextCalled = true; 1380 mCallerInfo = mSession.getCurrentControllerInfo(); 1381 mLatch.countDown(); 1382 } 1383 } 1384 } 1385