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.session.cts; 17 18 import static android.media.AudioAttributes.USAGE_GAME; 19 import static android.media.AudioAttributes.USAGE_MEDIA; 20 import static android.media.cts.Utils.compareRemoteUserInfo; 21 import static android.media.session.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE; 22 import static android.media.session.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS; 23 import static android.media.session.cts.MediaSessionTestService.KEY_SESSION_TOKEN; 24 import static android.media.session.cts.MediaSessionTestService.STEP_CHECK; 25 import static android.media.session.cts.MediaSessionTestService.STEP_CLEAN_UP; 26 import static android.media.session.cts.MediaSessionTestService.STEP_SET_UP; 27 import static android.media.session.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE; 28 import static android.media.session.cts.MediaSessionTestService.TEST_SET_QUEUE; 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.platform.app.InstrumentationRegistry; 65 66 import com.android.bedstead.harrier.BedsteadJUnit4; 67 import com.android.bedstead.harrier.UserType; 68 import com.android.bedstead.harrier.annotations.UserTest; 69 import com.android.compatibility.common.util.FrameworkSpecificTest; 70 71 import org.junit.After; 72 import org.junit.Assume; 73 import org.junit.Before; 74 import org.junit.Ignore; 75 import org.junit.Test; 76 import org.junit.runner.RunWith; 77 78 import java.util.ArrayList; 79 import java.util.Collections; 80 import java.util.List; 81 import java.util.Optional; 82 import java.util.concurrent.CountDownLatch; 83 import java.util.concurrent.TimeUnit; 84 85 @FrameworkSpecificTest 86 @AppModeFull(reason = "TODO: evaluate and port to instant") 87 @RunWith(BedsteadJUnit4.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 /** Tests that a session can be created and that all the fields are initialized correctly. */ 173 @Test 174 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testCreateSession()175 public void testCreateSession() throws Exception { 176 assertThat(mSession.getSessionToken()).isNotNull(); 177 assertWithMessage("New session should not be active").that(mSession.isActive()).isFalse(); 178 179 // Verify by getting the controller and checking all its fields 180 MediaController controller = mSession.getController(); 181 assertThat(controller).isNotNull(); 182 verifyNewSession(controller); 183 } 184 185 @Test 186 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) 187 // Needed for assertThat(sessionToken.equals(mSession)).isFalse(). 188 @SuppressWarnings("EqualsIncompatibleType") testSessionTokenEquals()189 public void testSessionTokenEquals() { 190 MediaSession anotherSession = null; 191 try { 192 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 193 MediaSession.Token sessionToken = mSession.getSessionToken(); 194 MediaSession.Token anotherSessionToken = anotherSession.getSessionToken(); 195 196 // Explicitly checks equals as Guava's EqualsTester is not yet supported (b/236153976). 197 assertThat(sessionToken.equals(sessionToken)).isTrue(); 198 assertThat(sessionToken.equals(null)).isFalse(); 199 assertThat(sessionToken.equals(mSession)).isFalse(); 200 assertThat(sessionToken.equals(anotherSessionToken)).isFalse(); 201 } finally { 202 if (anotherSession != null) { 203 anotherSession.release(); 204 } 205 } 206 } 207 208 /** Tests MediaSession.Token created in the constructor of MediaSession. */ 209 @Test 210 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionToken()211 public void testSessionToken() throws Exception { 212 MediaSession.Token sessionToken = mSession.getSessionToken(); 213 214 assertThat(sessionToken).isNotNull(); 215 assertThat(sessionToken.describeContents()).isEqualTo(0); 216 217 // Test writeToParcel 218 Parcel p = Parcel.obtain(); 219 sessionToken.writeToParcel(p, 0); 220 p.setDataPosition(0); 221 MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p); 222 assertThat(tokenFromParcel).isEqualTo(sessionToken); 223 p.recycle(); 224 225 final int arraySize = 5; 226 MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize); 227 assertThat(tokenArray).isNotNull(); 228 assertThat(tokenArray.length).isEqualTo(arraySize); 229 for (MediaSession.Token tokenElement : tokenArray) { 230 assertThat(tokenElement).isNull(); 231 } 232 } 233 234 /** Tests that the various configuration bits on a session get passed to the controller. */ 235 @Test 236 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testConfigureSession()237 public void testConfigureSession() throws Exception { 238 MediaController controller = mSession.getController(); 239 controller.registerCallback(mCallback, mHandler); 240 final MediaController.Callback callback = (MediaController.Callback) mCallback; 241 242 synchronized (mWaitLock) { 243 // test setExtras 244 mCallback.resetLocked(); 245 final Bundle extras = new Bundle(); 246 extras.putString(TEST_KEY, TEST_VALUE); 247 mSession.setExtras(extras); 248 mWaitLock.wait(TIME_OUT_MS); 249 assertThat(mCallback.mOnExtraChangedCalled).isTrue(); 250 // just call the callback once directly so it's marked as tested 251 callback.onExtrasChanged(mCallback.mExtras); 252 253 Bundle extrasOut = mCallback.mExtras; 254 assertThat(extrasOut).isNotNull(); 255 assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE); 256 257 extrasOut = controller.getExtras(); 258 assertThat(extrasOut).isNotNull(); 259 assertThat(extrasOut.get(TEST_KEY)).isEqualTo(TEST_VALUE); 260 261 // test setFlags 262 mSession.setFlags(5); 263 assertThat(controller.getFlags()).isEqualTo(5); 264 265 // test setMetadata 266 mCallback.resetLocked(); 267 MediaMetadata metadata = 268 new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build(); 269 mSession.setMetadata(metadata); 270 mWaitLock.wait(TIME_OUT_MS); 271 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 272 // just call the callback once directly so it's marked as tested 273 callback.onMetadataChanged(mCallback.mMediaMetadata); 274 275 MediaMetadata metadataOut = mCallback.mMediaMetadata; 276 assertThat(metadataOut).isNotNull(); 277 assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 278 279 metadataOut = controller.getMetadata(); 280 assertThat(metadataOut).isNotNull(); 281 assertThat(metadataOut.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 282 283 // test setPlaybackState 284 mCallback.resetLocked(); 285 PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build(); 286 mSession.setPlaybackState(state); 287 mWaitLock.wait(TIME_OUT_MS); 288 assertThat(mCallback.mOnPlaybackStateChangedCalled).isTrue(); 289 // just call the callback once directly so it's marked as tested 290 callback.onPlaybackStateChanged(mCallback.mPlaybackState); 291 292 PlaybackState stateOut = mCallback.mPlaybackState; 293 assertThat(stateOut).isNotNull(); 294 assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION); 295 296 stateOut = controller.getPlaybackState(); 297 assertThat(stateOut).isNotNull(); 298 assertThat(stateOut.getActions()).isEqualTo(TEST_ACTION); 299 300 // test setQueue and setQueueTitle 301 mCallback.resetLocked(); 302 List<QueueItem> queue = new ArrayList<>(); 303 QueueItem item = new QueueItem(new MediaDescription.Builder() 304 .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID); 305 queue.add(item); 306 mSession.setQueue(queue); 307 mWaitLock.wait(TIME_OUT_MS); 308 assertThat(mCallback.mOnQueueChangedCalled).isTrue(); 309 // just call the callback once directly so it's marked as tested 310 callback.onQueueChanged(mCallback.mQueue); 311 312 mSession.setQueueTitle(TEST_VALUE); 313 mWaitLock.wait(TIME_OUT_MS); 314 assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue(); 315 316 assertThat(mCallback.mTitle).isEqualTo(TEST_VALUE); 317 assertThat(mCallback.mQueue.size()).isEqualTo(queue.size()); 318 assertThat(mCallback.mQueue.get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID); 319 assertThat(mCallback.mQueue.get(0).getDescription().getMediaId()).isEqualTo(TEST_VALUE); 320 321 assertThat(controller.getQueueTitle()).isEqualTo(TEST_VALUE); 322 assertThat(controller.getQueue().size()).isEqualTo(queue.size()); 323 assertThat(controller.getQueue().get(0).getQueueId()).isEqualTo(TEST_QUEUE_ID); 324 assertThat(controller.getQueue().get(0).getDescription().getMediaId()) 325 .isEqualTo(TEST_VALUE); 326 327 mCallback.resetLocked(); 328 mSession.setQueue(null); 329 mWaitLock.wait(TIME_OUT_MS); 330 assertThat(mCallback.mOnQueueChangedCalled).isTrue(); 331 // just call the callback once directly so it's marked as tested 332 callback.onQueueChanged(mCallback.mQueue); 333 334 mSession.setQueueTitle(null); 335 mWaitLock.wait(TIME_OUT_MS); 336 assertThat(mCallback.mOnQueueTitleChangedCalled).isTrue(); 337 // just call the callback once directly so it's marked as tested 338 callback.onQueueTitleChanged(mCallback.mTitle); 339 340 assertThat(mCallback.mTitle).isNull(); 341 assertThat(mCallback.mQueue).isNull(); 342 assertThat(controller.getQueueTitle()).isNull(); 343 assertThat(controller.getQueue()).isNull(); 344 345 // test setSessionActivity 346 Intent intent = new Intent("cts.MEDIA_SESSION_ACTION"); 347 PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 348 PendingIntent.FLAG_MUTABLE_UNAUDITED); 349 mSession.setSessionActivity(pi); 350 assertThat(controller.getSessionActivity()).isEqualTo(pi); 351 352 // test setActivity 353 mSession.setActive(true); 354 assertThat(mSession.isActive()).isTrue(); 355 356 // test sendSessionEvent 357 mCallback.resetLocked(); 358 mSession.sendSessionEvent(TEST_SESSION_EVENT, extras); 359 mWaitLock.wait(TIME_OUT_MS); 360 361 assertThat(mCallback.mOnSessionEventCalled).isTrue(); 362 assertThat(mCallback.mEvent).isEqualTo(TEST_SESSION_EVENT); 363 assertThat(mCallback.mExtras.getString(TEST_KEY)).isEqualTo(TEST_VALUE); 364 // just call the callback once directly so it's marked as tested 365 callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras); 366 367 // test release 368 mCallback.resetLocked(); 369 mSession.release(); 370 mWaitLock.wait(TIME_OUT_MS); 371 assertThat(mCallback.mOnSessionDestroyedCalled).isTrue(); 372 // just call the callback once directly so it's marked as tested 373 callback.onSessionDestroyed(); 374 } 375 } 376 377 @Test 378 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) setMediaSession_withInaccessibleUri_uriCleared()379 public void setMediaSession_withInaccessibleUri_uriCleared() throws Exception { 380 createCloneProfile(); 381 Assume.assumeTrue(mCloneProfileId.isPresent()); 382 String testMediaUri = 383 "content://" + mCloneProfileId.get() + "@media/external/images/media/"; 384 // Save a screenshot in second user files. 385 runShellCommand("screencap -p " + testMediaUri); 386 387 MediaController controller = mSession.getController(); 388 controller.registerCallback(mCallback, mHandler); 389 final MediaController.Callback callback = mCallback; 390 391 synchronized (mWaitLock) { 392 // test setMetadata 393 mCallback.resetLocked(); 394 MediaMetadata metadata = 395 new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI, 396 testMediaUri).build(); 397 mSession.setMetadata(metadata); 398 mWaitLock.wait(TIME_OUT_MS); 399 400 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 401 // just call the callback once directly so it's marked as tested 402 callback.onMetadataChanged(mCallback.mMediaMetadata); 403 404 MediaMetadata metadataOut = mCallback.mMediaMetadata; 405 assertThat(metadataOut).isNotNull(); 406 assertThat(TextUtils.isEmpty(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI))) 407 .isTrue(); 408 } 409 } 410 411 @Test 412 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) setMediaSession_withUri_uriExists()413 public void setMediaSession_withUri_uriExists() throws Exception { 414 String testMediaUri = "content://media/external/images/media/"; 415 MediaController controller = mSession.getController(); 416 controller.registerCallback(mCallback, mHandler); 417 final MediaController.Callback callback = mCallback; 418 419 synchronized (mWaitLock) { 420 // test setMetadata 421 mCallback.resetLocked(); 422 MediaMetadata metadata = 423 new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_ART_URI, 424 testMediaUri).build(); 425 mSession.setMetadata(metadata); 426 mWaitLock.wait(TIME_OUT_MS); 427 428 assertThat(mCallback.mOnMetadataChangedCalled).isTrue(); 429 // just call the callback once directly so it's marked as tested 430 callback.onMetadataChanged(mCallback.mMediaMetadata); 431 432 MediaMetadata metadataOut = mCallback.mMediaMetadata; 433 assertThat(metadataOut).isNotNull(); 434 assertThat(metadataOut.getString(MediaMetadata.METADATA_KEY_ART_URI)) 435 .isEqualTo(testMediaUri); 436 } 437 } 438 439 /** 440 * Test whether media button receiver can be a explicit broadcast receiver via 441 * MediaSession.setMediaButtonReceiver(PendingIntent). 442 */ 443 @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable. 444 @Test 445 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetMediaButtonReceiver_broadcastReceiver()446 public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception { 447 Intent intent = new Intent(mContext.getApplicationContext(), 448 MediaButtonBroadcastReceiver.class); 449 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 450 PendingIntent.FLAG_MUTABLE_UNAUDITED); 451 452 // Play a sound so this session can get the priority. 453 Utils.assertMediaPlaybackStarted(getContext()); 454 455 // There is a different MediaSession that's created from StubMediaBrowserService class. 456 // This session takes over all the callbacks. we need to change the state of our session 457 // to STATE_PLAYING so it has higher priority. 458 setPlaybackState(PlaybackState.STATE_PLAYING); 459 460 // Sets the media button receiver. Framework will keep the broadcast receiver component name 461 // from the pending intent in persistent storage. 462 mSession.setMediaButtonReceiver(pi); 463 464 // Call explicit release, so change in the media key event session can be notified with the 465 // pending intent. 466 mSession.release(); 467 468 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 469 try { 470 CountDownLatch latch = new CountDownLatch(2); 471 MediaButtonBroadcastReceiver.setCallback((keyEvent) -> { 472 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 473 switch ((int) latch.getCount()) { 474 case 2: 475 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 476 break; 477 case 1: 478 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 479 break; 480 } 481 latch.countDown(); 482 }); 483 // Also try to dispatch media key event. 484 // System would try to dispatch event. 485 simulateMediaKeyInput(keyCode); 486 487 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 488 } finally { 489 MediaButtonBroadcastReceiver.setCallback(null); 490 } 491 } 492 493 /** Test whether media button receiver can be a explicit service. */ 494 @Test 495 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetMediaButtonReceiver_service()496 public void testSetMediaButtonReceiver_service() throws Exception { 497 Intent intent = new Intent(mContext.getApplicationContext(), 498 MediaButtonReceiverService.class); 499 PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 500 PendingIntent.FLAG_MUTABLE_UNAUDITED); 501 502 // Play a sound so this session can get the priority. 503 Utils.assertMediaPlaybackStarted(getContext()); 504 505 // There is a different MediaSession that's created from StubMediaBrowserService class. 506 // This session takes over all the callbacks. we need to change the state of our session 507 // to STATE_PLAYING so it has higher priority. 508 setPlaybackState(PlaybackState.STATE_PLAYING); 509 510 // Sets the media button receiver. Framework would try to keep the pending intent in the 511 // persistent store. 512 mSession.setMediaButtonReceiver(pi); 513 514 // Call explicit release, so change in the media key event session can be notified with the 515 // pending intent. 516 mSession.release(); 517 518 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 519 try { 520 CountDownLatch latch = new CountDownLatch(2); 521 MediaButtonReceiverService.setCallback((keyEvent) -> { 522 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 523 switch ((int) latch.getCount()) { 524 case 2: 525 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 526 break; 527 case 1: 528 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 529 break; 530 } 531 latch.countDown(); 532 }); 533 // Also try to dispatch media key event. 534 // System would try to dispatch event. 535 simulateMediaKeyInput(keyCode); 536 537 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 538 } finally { 539 MediaButtonReceiverService.setCallback(null); 540 } 541 } 542 543 /** 544 * Test whether system doesn't crash by {@link 545 * MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent. 546 */ 547 @Test 548 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetMediaButtonReceiver_implicitIntent()549 public void testSetMediaButtonReceiver_implicitIntent() throws Exception { 550 // Note: No such broadcast receiver exists. 551 Intent intent = new Intent("android.media.session.cts.ACTION_MEDIA_TEST"); 552 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 553 PendingIntent.FLAG_MUTABLE_UNAUDITED); 554 555 // Play a sound so this session can get the priority. 556 Utils.assertMediaPlaybackStarted(getContext()); 557 558 // Sets the media button receiver. Framework would try to keep the pending intent in the 559 // persistent store. 560 mSession.setMediaButtonReceiver(pi); 561 562 // Call explicit release, so change in the media key event session can be notified with the 563 // pending intent. 564 mSession.release(); 565 566 // Also try to dispatch media key event. System would try to send key event via pending 567 // intent, but it would no-op because there's no receiver. 568 simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); 569 } 570 571 @Test 572 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) 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 587 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetMediaButtonBroadcastReceiver_broadcastReceiver()588 public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception { 589 // Play a sound so this session can get the priority. 590 Utils.assertMediaPlaybackStarted(getContext()); 591 592 // Sets the broadcast receiver's component name. Framework will keep the component name in 593 // persistent storage. 594 mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext, 595 MediaButtonBroadcastReceiver.class)); 596 597 // There is a different MediaSession that's created from StubMediaBrowserService class. 598 // This session takes over all the callbacks. we need to change the state of our session 599 // to STATE_PLAYING so it has higher priority. 600 setPlaybackState(PlaybackState.STATE_PLAYING); 601 602 // Call explicit release, so change in the media key event session can be notified using the 603 // component name. 604 mSession.release(); 605 606 int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 607 try { 608 CountDownLatch latch = new CountDownLatch(2); 609 MediaButtonBroadcastReceiver.setCallback((keyEvent) -> { 610 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 611 switch ((int) latch.getCount()) { 612 case 2: 613 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 614 break; 615 case 1: 616 assertThat(keyEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP); 617 break; 618 } 619 latch.countDown(); 620 }); 621 // Also try to dispatch media key event. 622 // System would try to dispatch event. 623 simulateMediaKeyInput(keyCode); 624 625 assertThat(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 626 } finally { 627 MediaButtonBroadcastReceiver.setCallback(null); 628 } 629 } 630 631 /** Test public APIs of {@link VolumeProvider}. */ 632 @Test 633 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) 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 647 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testPlaybackToLocalAndRemote()648 public void testPlaybackToLocalAndRemote() throws Exception { 649 MediaController controller = mSession.getController(); 650 controller.registerCallback(mCallback, mHandler); 651 652 synchronized (mWaitLock) { 653 // test setPlaybackToRemote, do this before testing setPlaybackToLocal 654 // to ensure it switches correctly. 655 mCallback.resetLocked(); 656 try { 657 mSession.setPlaybackToRemote(null); 658 assertWithMessage("Expected IAE for setPlaybackToRemote(null)").fail(); 659 } catch (IllegalArgumentException e) { 660 // expected 661 } 662 VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED, 663 TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {}; 664 mSession.setPlaybackToRemote(vp); 665 666 MediaController.PlaybackInfo info = null; 667 for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) { 668 mCallback.mOnAudioInfoChangedCalled = false; 669 mWaitLock.wait(TIME_OUT_MS); 670 assertThat(mCallback.mOnAudioInfoChangedCalled).isTrue(); 671 info = mCallback.mPlaybackInfo; 672 if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME 673 && info.getMaxVolume() == TEST_MAX_VOLUME 674 && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED 675 && info.getPlaybackType() 676 == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE 677 && TextUtils.equals(info.getVolumeControlId(), TEST_VOLUME_CONTROL_ID)) { 678 break; 679 } 680 } 681 assertThat(info).isNotNull(); 682 assertThat(info.getPlaybackType()) 683 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE); 684 assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME); 685 assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME); 686 assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED); 687 assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID); 688 689 info = controller.getPlaybackInfo(); 690 assertThat(info).isNotNull(); 691 assertThat(info.getPlaybackType()) 692 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE); 693 assertThat(info.getMaxVolume()).isEqualTo(TEST_MAX_VOLUME); 694 assertThat(info.getCurrentVolume()).isEqualTo(TEST_CURRENT_VOLUME); 695 assertThat(info.getVolumeControl()).isEqualTo(VolumeProvider.VOLUME_CONTROL_FIXED); 696 assertThat(info.getVolumeControlId()).isEqualTo(TEST_VOLUME_CONTROL_ID); 697 698 // test setPlaybackToLocal 699 AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build(); 700 mSession.setPlaybackToLocal(attrs); 701 702 info = controller.getPlaybackInfo(); 703 assertThat(info).isNotNull(); 704 assertThat(info.getPlaybackType()) 705 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL); 706 assertThat(info.getAudioAttributes()).isEqualTo(attrs); 707 assertThat(info.getVolumeControlId()).isNull(); 708 } 709 } 710 711 /** Test {@link MediaSession.Callback#onMediaButtonEvent}. */ 712 @Test 713 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) 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 once 825 * {@code setCallback(null)} is done. 826 */ 827 @Test 828 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetCallbackWithNull()829 public void testSetCallbackWithNull() throws Exception { 830 MediaSessionCallback sessionCallback = new MediaSessionCallback(); 831 mSession.setCallback(sessionCallback, mHandler); 832 mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 833 mSession.setActive(true); 834 835 MediaController controller = mSession.getController(); 836 setPlaybackState(PlaybackState.STATE_PLAYING); 837 838 sessionCallback.reset(1); 839 mSession.setCallback(null, mHandler); 840 841 controller.getTransportControls().pause(); 842 assertThat(sessionCallback.await(WAIT_MS)).isFalse(); 843 assertWithMessage("Callback shouldn't be called.") 844 .that(sessionCallback.mOnPauseCalled).isFalse(); 845 } 846 setPlaybackState(int state)847 private void setPlaybackState(int state) { 848 final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE 849 | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP 850 | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS 851 | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND; 852 PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions) 853 .setState(state, 0L, 0.0f).build(); 854 synchronized (mWaitLock) { 855 mSession.setPlaybackState(playbackState); 856 } 857 } 858 859 /** 860 * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app 861 * which receives the media key events. See: b/36669550 862 */ 863 @Test 864 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testReleaseNoCrashWithMultipleSessions()865 public void testReleaseNoCrashWithMultipleSessions() throws Exception { 866 // Start a media playback for this app to receive media key events. 867 Utils.assertMediaPlaybackStarted(getContext()); 868 869 MediaSession anotherSession = null; 870 try { 871 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 872 mSession.release(); 873 anotherSession.release(); 874 875 // Try release with the different order. 876 mSession = new MediaSession(getContext(), TEST_SESSION_TAG); 877 anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG); 878 anotherSession.release(); 879 mSession.release(); 880 } finally { 881 if (anotherSession != null) { 882 anotherSession.release(); 883 anotherSession = null; 884 } 885 } 886 } 887 888 // This uses public APIs to dispatch key events, so sessions would consider this as 889 // 'media key event from this application'. simulateMediaKeyInput(int keyCode)890 private void simulateMediaKeyInput(int keyCode) { 891 long downTime = System.currentTimeMillis(); 892 mAudioManager.dispatchMediaKeyEvent( 893 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0)); 894 mAudioManager.dispatchMediaKeyEvent( 895 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0)); 896 } 897 898 /** Tests {@link MediaSession.QueueItem}. */ 899 @Test 900 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) 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 938 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testQueueItemEquals()939 public void testQueueItemEquals() { 940 MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder() 941 .setMediaId("media-id") 942 .setTitle("title"); 943 944 QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID); 945 assertThat(item.getQueueId()).isEqualTo(TEST_QUEUE_ID); 946 assertThat(item.getDescription().getMediaId()).isEqualTo("media-id"); 947 assertThat(item.getDescription().getTitle()).isEqualTo("title"); 948 assertThat(item.describeContents()).isEqualTo(0); 949 950 assertThat(item.equals(null)).isFalse(); 951 assertThat(item).isNotEqualTo(descriptionBuilder.build()); 952 953 QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID); 954 assertThat(item.equals(sameItem)).isTrue(); 955 956 QueueItem differentQueueId = new QueueItem( 957 descriptionBuilder.build(), TEST_QUEUE_ID + 1); 958 assertThat(item.equals(differentQueueId)).isFalse(); 959 960 QueueItem differentDescription = new QueueItem( 961 descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID); 962 assertThat(item.equals(differentDescription)).isFalse(); 963 } 964 965 @Test 966 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionInfoWithFrameworkParcelable()967 public void testSessionInfoWithFrameworkParcelable() { 968 final String testKey = "test_key"; 969 final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build(); 970 971 Bundle sessionInfo = new Bundle(); 972 sessionInfo.putParcelable(testKey, frameworkParcelable); 973 974 MediaSession session = null; 975 try { 976 session = new MediaSession( 977 mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo); 978 Bundle sessionInfoOut = session.getController().getSessionInfo(); 979 assertThat(sessionInfoOut.containsKey(testKey)).isTrue(); 980 assertThat((AudioAttributes) sessionInfoOut.getParcelable(testKey)) 981 .isEqualTo(frameworkParcelable); 982 } finally { 983 if (session != null) { 984 session.release(); 985 } 986 } 987 988 } 989 990 @Test 991 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionInfoWithCustomParcelable()992 public void testSessionInfoWithCustomParcelable() { 993 final String testKey = "test_key"; 994 final MediaSession2Test.CustomParcelable customParcelable = 995 new MediaSession2Test.CustomParcelable(1); 996 997 Bundle sessionInfo = new Bundle(); 998 sessionInfo.putParcelable(testKey, customParcelable); 999 1000 MediaSession session = null; 1001 try { 1002 session = new MediaSession( 1003 mContext, "testSessionInfoWithCustomParcelable", sessionInfo); 1004 assertWithMessage("Custom Parcelable shouldn't be accepted!").fail(); 1005 } catch (IllegalArgumentException e) { 1006 // Expected 1007 } finally { 1008 if (session != null) { 1009 session.release(); 1010 } 1011 } 1012 } 1013 1014 /** 1015 * An app should not be able to create too many sessions. See 1016 * MediaSessionService#SESSION_CREATION_LIMIT_PER_UID 1017 */ 1018 @Test 1019 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionCreationLimit()1020 public void testSessionCreationLimit() { 1021 List<MediaSession> sessions = new ArrayList<>(); 1022 try { 1023 for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) { 1024 sessions.add(new MediaSession(mContext, "testSessionCreationLimit")); 1025 } 1026 assertWithMessage("The number of session should be limited!").fail(); 1027 } catch (RuntimeException e) { 1028 // Expected 1029 } finally { 1030 for (MediaSession session : sessions) { 1031 session.release(); 1032 } 1033 } 1034 } 1035 1036 /** 1037 * Check that calling {@link MediaSession#release()} multiple times for the same session does 1038 * not decrement current session count multiple times. 1039 */ 1040 @Test 1041 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionCreationLimitWithMediaSessionRelease()1042 public void testSessionCreationLimitWithMediaSessionRelease() { 1043 List<MediaSession> sessions = new ArrayList<>(); 1044 MediaSession sessionToReleaseMultipleTimes = null; 1045 try { 1046 sessionToReleaseMultipleTimes = new MediaSession( 1047 mContext, "testSessionCreationLimitWithMediaSessionRelease"); 1048 for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) { 1049 sessions.add(new MediaSession( 1050 mContext, "testSessionCreationLimitWithMediaSessionRelease")); 1051 // Call release() many times with the same session. 1052 sessionToReleaseMultipleTimes.release(); 1053 } 1054 assertWithMessage("The number of session should be limited!").fail(); 1055 } catch (RuntimeException e) { 1056 // Expected 1057 } finally { 1058 for (MediaSession session : sessions) { 1059 session.release(); 1060 } 1061 if (sessionToReleaseMultipleTimes != null) { 1062 sessionToReleaseMultipleTimes.release(); 1063 } 1064 } 1065 } 1066 1067 /** 1068 * Check that calling {@link MediaSession2#close()} does not decrement current session count. 1069 */ 1070 @Test 1071 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSessionCreationLimitWithMediaSession2Release()1072 public void testSessionCreationLimitWithMediaSession2Release() { 1073 List<MediaSession> sessions = new ArrayList<>(); 1074 try { 1075 for (int i = 0; i < 1000; i++) { 1076 sessions.add(new MediaSession( 1077 mContext, "testSessionCreationLimitWithMediaSession2Release")); 1078 1079 try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) { 1080 // Do nothing 1081 } 1082 } 1083 assertWithMessage("The number of session should be limited!").fail(); 1084 } catch (RuntimeException e) { 1085 // Expected 1086 } finally { 1087 for (MediaSession session : sessions) { 1088 session.release(); 1089 } 1090 } 1091 } 1092 1093 /** 1094 * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController} 1095 * on the remote process due to binder buffer overflow. 1096 */ 1097 @Test 1098 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSeriesOfSetQueue()1099 public void testSeriesOfSetQueue() throws Exception { 1100 int numberOfCalls = 100; 1101 int queueSize = 1_000; 1102 List<QueueItem> queue = new ArrayList<>(); 1103 for (int id = 0; id < queueSize; id++) { 1104 MediaDescription description = new MediaDescription.Builder() 1105 .setMediaId(Integer.toString(id)).build(); 1106 queue.add(new QueueItem(description, id)); 1107 } 1108 1109 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1110 MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) { 1111 Bundle args = new Bundle(); 1112 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1113 args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize); 1114 invoker.run(STEP_SET_UP, args); 1115 for (int i = 0; i < numberOfCalls; i++) { 1116 mSession.setQueue(queue); 1117 } 1118 invoker.run(STEP_CHECK); 1119 invoker.run(STEP_CLEAN_UP); 1120 } 1121 } 1122 1123 @Test 1124 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetQueueWithLargeNumberOfItems()1125 public void testSetQueueWithLargeNumberOfItems() throws Exception { 1126 int queueSize = 500_000; 1127 List<QueueItem> queue = new ArrayList<>(); 1128 for (int id = 0; id < queueSize; id++) { 1129 MediaDescription description = new MediaDescription.Builder() 1130 .setMediaId(Integer.toString(id)).build(); 1131 queue.add(new QueueItem(description, id)); 1132 } 1133 1134 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1135 MediaSessionTestService.class, TEST_SET_QUEUE)) { 1136 Bundle args = new Bundle(); 1137 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1138 args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize); 1139 invoker.run(STEP_SET_UP, args); 1140 mSession.setQueue(queue); 1141 invoker.run(STEP_CHECK); 1142 invoker.run(STEP_CLEAN_UP); 1143 } 1144 } 1145 1146 @Test 1147 @UserTest({UserType.INITIAL_USER, UserType.WORK_PROFILE, UserType.SECONDARY_USER}) testSetQueueWithEmptyQueue()1148 public void testSetQueueWithEmptyQueue() throws Exception { 1149 try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext, 1150 MediaSessionTestService.class, TEST_SET_QUEUE)) { 1151 Bundle args = new Bundle(); 1152 args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken()); 1153 args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0); 1154 invoker.run(STEP_SET_UP, args); 1155 mSession.setQueue(Collections.emptyList()); 1156 invoker.run(STEP_CHECK); 1157 invoker.run(STEP_CLEAN_UP); 1158 } 1159 } 1160 1161 /** 1162 * Verifies that a new session hasn't had any configuration bits set yet. 1163 * 1164 * @param controller The controller for the session 1165 */ 1166 @SuppressWarnings("ReturnValueIgnored") verifyNewSession(MediaController controller)1167 private void verifyNewSession(MediaController controller) { 1168 assertWithMessage("New session has unexpected configuration") 1169 .that(controller.getFlags()).isEqualTo(0); 1170 assertWithMessage("New session has unexpected configuration") 1171 .that(controller.getExtras()).isNull(); 1172 assertWithMessage("New session has unexpected configuration") 1173 .that(controller.getMetadata()).isNull(); 1174 assertWithMessage("New session has unexpected configuration") 1175 .that(controller.getPackageName()).isEqualTo(getContext().getPackageName()); 1176 assertWithMessage("New session has unexpected configuration") 1177 .that(controller.getPlaybackState()).isNull(); 1178 assertWithMessage("New session has unexpected configuration") 1179 .that(controller.getQueue()).isNull(); 1180 assertWithMessage("New session has unexpected configuration") 1181 .that(controller.getQueueTitle()).isNull(); 1182 assertWithMessage("New session has unexpected configuration") 1183 .that(controller.getRatingType()).isEqualTo(Rating.RATING_NONE); 1184 assertWithMessage("New session has unexpected configuration") 1185 .that(controller.getSessionActivity()).isNull(); 1186 1187 assertThat(controller.getSessionToken()).isNotNull(); 1188 assertThat(controller.getTransportControls()).isNotNull(); 1189 1190 MediaController.PlaybackInfo info = controller.getPlaybackInfo(); 1191 assertThat(info).isNotNull(); 1192 info.toString(); // Test that calling PlaybackInfo.toString() does not crash. 1193 assertThat(info.getPlaybackType()) 1194 .isEqualTo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL); 1195 AudioAttributes attrs = info.getAudioAttributes(); 1196 assertThat(attrs).isNotNull(); 1197 assertThat(attrs.getUsage()).isEqualTo(USAGE_MEDIA); 1198 assertThat(info.getCurrentVolume()) 1199 .isEqualTo(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)); 1200 } 1201 1202 private class MediaControllerCallback extends MediaController.Callback { 1203 private volatile boolean mOnPlaybackStateChangedCalled; 1204 private volatile boolean mOnMetadataChangedCalled; 1205 private volatile boolean mOnQueueChangedCalled; 1206 private volatile boolean mOnQueueTitleChangedCalled; 1207 private volatile boolean mOnExtraChangedCalled; 1208 private volatile boolean mOnAudioInfoChangedCalled; 1209 private volatile boolean mOnSessionDestroyedCalled; 1210 private volatile boolean mOnSessionEventCalled; 1211 1212 private volatile PlaybackState mPlaybackState; 1213 private volatile MediaMetadata mMediaMetadata; 1214 private volatile List<QueueItem> mQueue; 1215 private volatile CharSequence mTitle; 1216 private volatile String mEvent; 1217 private volatile Bundle mExtras; 1218 private volatile MediaController.PlaybackInfo mPlaybackInfo; 1219 resetLocked()1220 public void resetLocked() { 1221 mOnPlaybackStateChangedCalled = false; 1222 mOnMetadataChangedCalled = false; 1223 mOnQueueChangedCalled = false; 1224 mOnQueueTitleChangedCalled = false; 1225 mOnExtraChangedCalled = false; 1226 mOnAudioInfoChangedCalled = false; 1227 mOnSessionDestroyedCalled = false; 1228 mOnSessionEventCalled = false; 1229 1230 mPlaybackState = null; 1231 mMediaMetadata = null; 1232 mQueue = null; 1233 mTitle = null; 1234 mExtras = null; 1235 mPlaybackInfo = null; 1236 } 1237 1238 @Override onPlaybackStateChanged(PlaybackState state)1239 public void onPlaybackStateChanged(PlaybackState state) { 1240 synchronized (mWaitLock) { 1241 mOnPlaybackStateChangedCalled = true; 1242 mPlaybackState = state; 1243 mWaitLock.notify(); 1244 } 1245 } 1246 1247 @Override onMetadataChanged(MediaMetadata metadata)1248 public void onMetadataChanged(MediaMetadata metadata) { 1249 synchronized (mWaitLock) { 1250 mOnMetadataChangedCalled = true; 1251 mMediaMetadata = metadata; 1252 mWaitLock.notify(); 1253 } 1254 } 1255 1256 @Override onQueueChanged(List<QueueItem> queue)1257 public void onQueueChanged(List<QueueItem> queue) { 1258 synchronized (mWaitLock) { 1259 mOnQueueChangedCalled = true; 1260 mQueue = queue; 1261 mWaitLock.notify(); 1262 } 1263 } 1264 1265 @Override onQueueTitleChanged(CharSequence title)1266 public void onQueueTitleChanged(CharSequence title) { 1267 synchronized (mWaitLock) { 1268 mOnQueueTitleChangedCalled = true; 1269 mTitle = title; 1270 mWaitLock.notify(); 1271 } 1272 } 1273 1274 @Override onExtrasChanged(Bundle extras)1275 public void onExtrasChanged(Bundle extras) { 1276 synchronized (mWaitLock) { 1277 mOnExtraChangedCalled = true; 1278 mExtras = extras; 1279 mWaitLock.notify(); 1280 } 1281 } 1282 1283 @Override onAudioInfoChanged(MediaController.PlaybackInfo info)1284 public void onAudioInfoChanged(MediaController.PlaybackInfo info) { 1285 synchronized (mWaitLock) { 1286 mOnAudioInfoChangedCalled = true; 1287 mPlaybackInfo = info; 1288 mWaitLock.notify(); 1289 } 1290 } 1291 1292 @Override onSessionDestroyed()1293 public void onSessionDestroyed() { 1294 synchronized (mWaitLock) { 1295 mOnSessionDestroyedCalled = true; 1296 mWaitLock.notify(); 1297 } 1298 } 1299 1300 @Override onSessionEvent(String event, Bundle extras)1301 public void onSessionEvent(String event, Bundle extras) { 1302 synchronized (mWaitLock) { 1303 mOnSessionEventCalled = true; 1304 mEvent = event; 1305 mExtras = (Bundle) extras.clone(); 1306 mWaitLock.notify(); 1307 } 1308 } 1309 } 1310 1311 private class MediaSessionCallback extends MediaSession.Callback { 1312 private CountDownLatch mLatch; 1313 private int mOnPlayCalledCount; 1314 private boolean mOnPauseCalled; 1315 private boolean mOnStopCalled; 1316 private boolean mOnFastForwardCalled; 1317 private boolean mOnRewindCalled; 1318 private boolean mOnSkipToPreviousCalled; 1319 private boolean mOnSkipToNextCalled; 1320 private RemoteUserInfo mCallerInfo; 1321 reset(int count)1322 public void reset(int count) { 1323 mLatch = new CountDownLatch(count); 1324 mOnPlayCalledCount = 0; 1325 mOnPauseCalled = false; 1326 mOnStopCalled = false; 1327 mOnFastForwardCalled = false; 1328 mOnRewindCalled = false; 1329 mOnSkipToPreviousCalled = false; 1330 mOnSkipToNextCalled = false; 1331 } 1332 await(long waitMs)1333 public boolean await(long waitMs) { 1334 try { 1335 return mLatch.await(waitMs, TimeUnit.MILLISECONDS); 1336 } catch (InterruptedException e) { 1337 return false; 1338 } 1339 } 1340 1341 @Override onPlay()1342 public void onPlay() { 1343 mOnPlayCalledCount++; 1344 mCallerInfo = mSession.getCurrentControllerInfo(); 1345 setPlaybackState(PlaybackState.STATE_PLAYING); 1346 mLatch.countDown(); 1347 } 1348 1349 @Override onPause()1350 public void onPause() { 1351 mOnPauseCalled = true; 1352 mCallerInfo = mSession.getCurrentControllerInfo(); 1353 setPlaybackState(PlaybackState.STATE_PAUSED); 1354 mLatch.countDown(); 1355 } 1356 1357 @Override onStop()1358 public void onStop() { 1359 mOnStopCalled = true; 1360 mCallerInfo = mSession.getCurrentControllerInfo(); 1361 setPlaybackState(PlaybackState.STATE_STOPPED); 1362 mLatch.countDown(); 1363 } 1364 1365 @Override onFastForward()1366 public void onFastForward() { 1367 mOnFastForwardCalled = true; 1368 mCallerInfo = mSession.getCurrentControllerInfo(); 1369 mLatch.countDown(); 1370 } 1371 1372 @Override onRewind()1373 public void onRewind() { 1374 mOnRewindCalled = true; 1375 mCallerInfo = mSession.getCurrentControllerInfo(); 1376 mLatch.countDown(); 1377 } 1378 1379 @Override onSkipToPrevious()1380 public void onSkipToPrevious() { 1381 mOnSkipToPreviousCalled = true; 1382 mCallerInfo = mSession.getCurrentControllerInfo(); 1383 mLatch.countDown(); 1384 } 1385 1386 @Override onSkipToNext()1387 public void onSkipToNext() { 1388 mOnSkipToNextCalled = true; 1389 mCallerInfo = mSession.getCurrentControllerInfo(); 1390 mLatch.countDown(); 1391 } 1392 } 1393 } 1394