1 /* 2 * Copyright (C) 2014 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.misc.cts; 17 18 import android.Manifest; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.content.res.Resources; 24 import android.media.AudioManager; 25 import android.media.MediaSession2; 26 import android.media.Session2CommandGroup; 27 import android.media.Session2Token; 28 import android.media.cts.NonMediaMainlineTest; 29 import android.media.cts.Utils; 30 import android.media.session.MediaController; 31 import android.media.session.MediaSession; 32 import android.media.session.MediaSessionManager; 33 import android.media.session.PlaybackState; 34 import android.os.Build; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.Looper; 38 import android.os.Process; 39 import android.platform.test.annotations.AppModeFull; 40 import android.provider.Settings; 41 import android.test.InstrumentationTestCase; 42 import android.test.UiThreadTest; 43 import android.text.TextUtils; 44 import android.view.KeyEvent; 45 46 import androidx.test.filters.SdkSuppress; 47 48 import com.android.compatibility.common.util.ApiLevelUtil; 49 import com.android.compatibility.common.util.MediaUtils; 50 import com.android.compatibility.common.util.SystemUtil; 51 52 import java.io.IOException; 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.Executor; 57 import java.util.concurrent.Executors; 58 import java.util.concurrent.TimeUnit; 59 60 @AppModeFull(reason = "TODO: evaluate and port to instant") 61 public class MediaSessionManagerTest extends InstrumentationTestCase { 62 private static final String TAG = "MediaSessionManagerTest"; 63 private static final int TIMEOUT_MS = 3000; 64 private static final int WAIT_MS = 500; 65 private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; 66 67 private Context mContext; 68 private AudioManager mAudioManager; 69 private MediaSessionManager mSessionManager; 70 71 private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S); 72 private static boolean sIsAtLeastT = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU); 73 74 @Override setUp()75 protected void setUp() throws Exception { 76 super.setUp(); 77 mContext = getInstrumentation().getTargetContext(); 78 mAudioManager = (AudioManager) getInstrumentation().getTargetContext() 79 .getSystemService(Context.AUDIO_SERVICE); 80 mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext() 81 .getSystemService(Context.MEDIA_SESSION_SERVICE); 82 } 83 84 @Override tearDown()85 protected void tearDown() throws Exception { 86 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 87 super.tearDown(); 88 } 89 testGetActiveSessions()90 public void testGetActiveSessions() throws Exception { 91 try { 92 List<MediaController> controllers = mSessionManager.getActiveSessions(null); 93 fail("Expected security exception for unauthorized call to getActiveSessions"); 94 } catch (SecurityException e) { 95 // Expected 96 } 97 // TODO enable a notification listener, test again, disable, test again 98 } 99 testGetMediaKeyEventSession_throwsSecurityException()100 public void testGetMediaKeyEventSession_throwsSecurityException() { 101 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 102 try { 103 mSessionManager.getMediaKeyEventSession(); 104 fail("Expected security exception for call to getMediaKeyEventSession"); 105 } catch (SecurityException ex) { 106 // Expected 107 } 108 } 109 testGetMediaKeyEventSessionPackageName_throwsSecurityException()110 public void testGetMediaKeyEventSessionPackageName_throwsSecurityException() { 111 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 112 try { 113 mSessionManager.getMediaKeyEventSessionPackageName(); 114 fail("Expected security exception for call to getMediaKeyEventSessionPackageName"); 115 } catch (SecurityException ex) { 116 // Expected 117 } 118 } 119 testOnMediaKeyEventSessionChangedListener()120 public void testOnMediaKeyEventSessionChangedListener() throws Exception { 121 // The permission can be held only on S+ 122 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 123 124 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 125 Manifest.permission.MEDIA_CONTENT_CONTROL, 126 Manifest.permission.MANAGE_EXTERNAL_STORAGE); 127 128 MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener(); 129 mSessionManager.addOnMediaKeyEventSessionChangedListener( 130 Executors.newSingleThreadExecutor(), keyEventSessionListener); 131 132 MediaSession session = createMediaKeySession(); 133 assertTrue(keyEventSessionListener.mCountDownLatch 134 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 135 136 assertEquals(session.getSessionToken(), keyEventSessionListener.mSessionToken); 137 assertEquals(session.getSessionToken(), mSessionManager.getMediaKeyEventSession()); 138 assertEquals(getInstrumentation().getTargetContext().getPackageName(), 139 mSessionManager.getMediaKeyEventSessionPackageName()); 140 141 mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener); 142 keyEventSessionListener.resetCountDownLatch(); 143 144 session.release(); 145 // This shouldn't be called because the callback is removed 146 assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); 147 } 148 testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased()149 public void testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased() throws Exception { 150 // The permission can be held only on S+ 151 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 152 153 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 154 Manifest.permission.MEDIA_CONTENT_CONTROL, 155 Manifest.permission.MANAGE_EXTERNAL_STORAGE); 156 157 MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener(); 158 mSessionManager.addOnMediaKeyEventSessionChangedListener( 159 Executors.newSingleThreadExecutor(), keyEventSessionListener); 160 161 MediaSession session = createMediaKeySession(); 162 assertTrue(keyEventSessionListener.mCountDownLatch 163 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 164 165 // Check that this is called when the session is released. 166 keyEventSessionListener.resetCountDownLatch(); 167 session.release(); 168 assertTrue(keyEventSessionListener.mCountDownLatch 169 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 170 assertNull(keyEventSessionListener.mSessionToken); 171 if (ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU)) { 172 assertEquals("", keyEventSessionListener.mPackageName); 173 } 174 175 assertNull(mSessionManager.getMediaKeyEventSession()); 176 assertEquals("", mSessionManager.getMediaKeyEventSessionPackageName()); 177 } 178 179 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) 180 @NonMediaMainlineTest testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken()181 public void testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken() 182 throws InterruptedException { 183 // The permission can be held only on S+ 184 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 185 Manifest.permission.MEDIA_CONTENT_CONTROL); 186 187 MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener(); 188 mSessionManager.addOnMediaKeyEventSessionChangedListener( 189 Executors.newSingleThreadExecutor(), keyEventSessionListener); 190 191 assertTrue(keyEventSessionListener.mCountDownLatch 192 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 193 assertNull(keyEventSessionListener.mSessionToken); 194 assertEquals("", keyEventSessionListener.mPackageName); 195 196 // Clean up listener. 197 mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener); 198 } 199 createMediaKeySession()200 private MediaSession createMediaKeySession() { 201 MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG); 202 session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 203 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 204 PlaybackState state = new PlaybackState.Builder() 205 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); 206 // Fake the media session service so this session can take the media key events. 207 session.setPlaybackState(state); 208 session.setActive(true); 209 Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); 210 211 return session; 212 } 213 testOnMediaKeyEventSessionChangedListener_noPermission_throwsSecurityException()214 public void testOnMediaKeyEventSessionChangedListener_noPermission_throwsSecurityException() { 215 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 216 MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener(); 217 try { 218 mSessionManager.addOnMediaKeyEventSessionChangedListener( 219 Executors.newSingleThreadExecutor(), keyEventSessionListener); 220 fail("Expected security exception for call to" 221 + " addOnMediaKeyEventSessionChangedListener"); 222 } catch (SecurityException ex) { 223 // Expected 224 } 225 } 226 testOnMediaKeyEventDispatchedListener()227 public void testOnMediaKeyEventDispatchedListener() throws Exception { 228 // The permission can be held only on S+ 229 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 230 231 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 232 Manifest.permission.MEDIA_CONTENT_CONTROL, 233 Manifest.permission.MANAGE_EXTERNAL_STORAGE); 234 235 MediaKeyEventDispatchedListener keyEventDispatchedListener = 236 new MediaKeyEventDispatchedListener(); 237 mSessionManager.addOnMediaKeyEventDispatchedListener(Executors.newSingleThreadExecutor(), 238 keyEventDispatchedListener); 239 240 MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG); 241 session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 242 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 243 PlaybackState state = new PlaybackState.Builder() 244 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); 245 // Fake the media session service so this session can take the media key events. 246 session.setPlaybackState(state); 247 session.setActive(true); 248 Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); 249 250 final int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; 251 simulateMediaKeyInput(keyCode); 252 assertTrue(keyEventDispatchedListener.mCountDownLatch 253 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 254 255 assertEquals(keyCode, keyEventDispatchedListener.mKeyEvent.getKeyCode()); 256 assertEquals(getInstrumentation().getTargetContext().getPackageName(), 257 keyEventDispatchedListener.mPackageName); 258 assertEquals(session.getSessionToken(), keyEventDispatchedListener.mSessionToken); 259 260 mSessionManager.removeOnMediaKeyEventDispatchedListener(keyEventDispatchedListener); 261 keyEventDispatchedListener.resetCountDownLatch(); 262 263 simulateMediaKeyInput(keyCode); 264 // This shouldn't be called because the callback is removed 265 assertFalse(keyEventDispatchedListener.mCountDownLatch 266 .await(WAIT_MS, TimeUnit.MILLISECONDS)); 267 268 session.release(); 269 } 270 271 @UiThreadTest testAddOnActiveSessionsListener()272 public void testAddOnActiveSessionsListener() throws Exception { 273 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 274 try { 275 mSessionManager.addOnActiveSessionsChangedListener(null, null); 276 fail("Expected NPE for call to addOnActiveSessionsChangedListener"); 277 } catch (NullPointerException e) { 278 // Expected 279 } 280 281 MediaSessionManager.OnActiveSessionsChangedListener listener 282 = new MediaSessionManager.OnActiveSessionsChangedListener() { 283 @Override 284 public void onActiveSessionsChanged(List<MediaController> controllers) { 285 286 } 287 }; 288 try { 289 mSessionManager.addOnActiveSessionsChangedListener(listener, null); 290 fail("Expected security exception for call to addOnActiveSessionsChangedListener"); 291 } catch (SecurityException e) { 292 // Expected 293 } 294 } 295 assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount)296 private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) { 297 assertTrue(lhs.getKeyCode() == keyCode 298 && lhs.getAction() == action 299 && lhs.getRepeatCount() == repeatCount); 300 } 301 injectInputEvent(int keyCode, boolean longPress)302 private void injectInputEvent(int keyCode, boolean longPress) throws IOException { 303 // Injecting key with instrumentation requires a window/view, but we don't have it. 304 // Inject key event through the adb commend to workaround. 305 final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode; 306 SystemUtil.runShellCommand(getInstrumentation(), command); 307 } 308 testSetOnVolumeKeyLongPressListener()309 public void testSetOnVolumeKeyLongPressListener() throws Exception { 310 Context context = getInstrumentation().getTargetContext(); 311 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) 312 || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) 313 || context.getResources().getBoolean(Resources.getSystem().getIdentifier( 314 "config_handleVolumeKeysInWindowManager", "bool", "android"))) { 315 // Skip this test, because the PhoneWindowManager dispatches volume key 316 // events directly to the audio service to change the system volume. 317 return; 318 } 319 Handler handler = createHandler(); 320 321 // Ensure that the listener is called for long-press. 322 VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler); 323 mSessionManager.setOnVolumeKeyLongPressListener(listener, handler); 324 injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); 325 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 326 assertEquals(listener.mKeyEvents.size(), 3); 327 assertKeyEventEquals(listener.mKeyEvents.get(0), 328 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0); 329 assertKeyEventEquals(listener.mKeyEvents.get(1), 330 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1); 331 assertKeyEventEquals(listener.mKeyEvents.get(2), 332 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0); 333 334 // Ensure the the listener isn't called for short-press. 335 listener = new VolumeKeyLongPressListener(1, handler); 336 mSessionManager.setOnVolumeKeyLongPressListener(listener, handler); 337 injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false); 338 assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); 339 assertEquals(listener.mKeyEvents.size(), 0); 340 341 // Ensure that the listener isn't called anymore. 342 mSessionManager.setOnVolumeKeyLongPressListener(null, handler); 343 injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); 344 assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); 345 assertEquals(listener.mKeyEvents.size(), 0); 346 347 removeHandler(handler); 348 } 349 testSetOnMediaKeyListener()350 public void testSetOnMediaKeyListener() throws Exception { 351 Handler handler = createHandler(); 352 MediaSession session = null; 353 try { 354 session = new MediaSession(getInstrumentation().getTargetContext(), TAG); 355 MediaSessionCallback callback = new MediaSessionCallback(2, session); 356 session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 357 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 358 session.setCallback(callback, handler); 359 PlaybackState state = new PlaybackState.Builder() 360 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); 361 // Fake the media session service so this session can take the media key events. 362 session.setPlaybackState(state); 363 session.setActive(true); 364 365 // A media playback is also needed to receive media key events. 366 Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); 367 368 // Ensure that the listener is called for media key event, 369 // and any other media sessions don't get the key. 370 MediaKeyListener listener = new MediaKeyListener(2, true, handler); 371 mSessionManager.setOnMediaKeyListener(listener, handler); 372 injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); 373 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 374 assertEquals(listener.mKeyEvents.size(), 2); 375 assertKeyEventEquals(listener.mKeyEvents.get(0), 376 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); 377 assertKeyEventEquals(listener.mKeyEvents.get(1), 378 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); 379 assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); 380 assertEquals(callback.mKeyEvents.size(), 0); 381 382 // Ensure that the listener is called for media key event, 383 // and another media session gets the key. 384 listener = new MediaKeyListener(2, false, handler); 385 mSessionManager.setOnMediaKeyListener(listener, handler); 386 injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); 387 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 388 assertEquals(listener.mKeyEvents.size(), 2); 389 assertKeyEventEquals(listener.mKeyEvents.get(0), 390 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); 391 assertKeyEventEquals(listener.mKeyEvents.get(1), 392 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); 393 assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 394 assertEquals(callback.mKeyEvents.size(), 2); 395 assertKeyEventEquals(callback.mKeyEvents.get(0), 396 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); 397 assertKeyEventEquals(callback.mKeyEvents.get(1), 398 KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); 399 400 // Ensure that the listener isn't called anymore. 401 listener = new MediaKeyListener(1, true, handler); 402 mSessionManager.setOnMediaKeyListener(listener, handler); 403 mSessionManager.setOnMediaKeyListener(null, handler); 404 injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); 405 assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); 406 assertEquals(listener.mKeyEvents.size(), 0); 407 } finally { 408 if (session != null) { 409 session.release(); 410 } 411 removeHandler(handler); 412 } 413 } 414 testRemoteUserInfo()415 public void testRemoteUserInfo() throws Exception { 416 final Context context = getInstrumentation().getTargetContext(); 417 Handler handler = createHandler(); 418 419 MediaSession session = null; 420 try { 421 session = new MediaSession(context , TAG); 422 MediaSessionCallback callback = new MediaSessionCallback(5, session); 423 session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 424 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 425 session.setCallback(callback, handler); 426 PlaybackState state = new PlaybackState.Builder() 427 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); 428 // Fake the media session service so this session can take the media key events. 429 session.setPlaybackState(state); 430 session.setActive(true); 431 432 // A media playback is also needed to receive media key events. 433 Utils.assertMediaPlaybackStarted(context); 434 435 // Dispatch key events 5 times. 436 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); 437 // (1), (2): dispatch through key -- this will trigger event twice for up & down. 438 injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); 439 // (3): dispatch through controller 440 session.getController().dispatchMediaButtonEvent(event); 441 442 // Creating another controller. 443 MediaController controller = new MediaController(context, session.getSessionToken()); 444 // (4): dispatch through different controller. 445 controller.dispatchMediaButtonEvent(event); 446 // (5): dispatch through the same controller 447 controller.dispatchMediaButtonEvent(event); 448 449 // Wait. 450 assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 451 452 // Caller of (1) ~ (4) shouldn't be the same as any others. 453 for (int i = 0; i < 4; i ++) { 454 for (int j = 0; j < i; j++) { 455 assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j)); 456 } 457 } 458 // Caller of (5) should be the same as (4), since they're called from the same 459 assertEquals(callback.mCallers.get(3), callback.mCallers.get(4)); 460 } finally { 461 if (session != null) { 462 session.release(); 463 } 464 removeHandler(handler); 465 } 466 } 467 testGetSession2Tokens()468 public void testGetSession2Tokens() throws Exception { 469 final Context context = getInstrumentation().getTargetContext(); 470 Handler handler = createHandler(); 471 Executor handlerExecutor = new HandlerExecutor(handler); 472 473 Session2TokenListener listener = new Session2TokenListener(); 474 mSessionManager.addOnSession2TokensChangedListener(listener, handler); 475 476 Session2Callback sessionCallback = new Session2Callback(); 477 try (MediaSession2 session = new MediaSession2.Builder(context) 478 .setSessionCallback(handlerExecutor, sessionCallback) 479 .build()) { 480 assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 481 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 482 483 Session2Token currentToken = session.getToken(); 484 assertTrue(listContainsToken(listener.mTokens, currentToken)); 485 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken)); 486 } 487 } 488 testGetSession2TokensWithTwoSessions()489 public void testGetSession2TokensWithTwoSessions() throws Exception { 490 final Context context = getInstrumentation().getTargetContext(); 491 Handler handler = createHandler(); 492 Executor handlerExecutor = new HandlerExecutor(handler); 493 494 Session2TokenListener listener = new Session2TokenListener(); 495 mSessionManager.addOnSession2TokensChangedListener(listener, handler); 496 497 try (MediaSession2 session1 = new MediaSession2.Builder(context) 498 .setSessionCallback(handlerExecutor, new Session2Callback()) 499 .setId("testGetSession2TokensWithTwoSessions_session1") 500 .build()) { 501 502 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 503 Session2Token session1Token = session1.getToken(); 504 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); 505 506 // Create another session and check the result of getSession2Token(). 507 listener.resetCountDownLatch(); 508 Session2Token session2Token = null; 509 try (MediaSession2 session2 = new MediaSession2.Builder(context) 510 .setSessionCallback(handlerExecutor, new Session2Callback()) 511 .setId("testGetSession2TokensWithTwoSessions_session2") 512 .build()) { 513 514 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 515 session2Token = session2.getToken(); 516 assertNotNull(session2Token); 517 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); 518 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token)); 519 520 listener.resetCountDownLatch(); 521 } 522 523 // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token. 524 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 525 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); 526 assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token)); 527 } 528 } 529 testAddAndRemoveSession2TokensListener()530 public void testAddAndRemoveSession2TokensListener() throws Exception { 531 final Context context = getInstrumentation().getTargetContext(); 532 Handler handler = createHandler(); 533 Executor handlerExecutor = new HandlerExecutor(handler); 534 535 Session2TokenListener listener1 = new Session2TokenListener(); 536 mSessionManager.addOnSession2TokensChangedListener(listener1, handler); 537 538 Session2Callback sessionCallback = new Session2Callback(); 539 try (MediaSession2 session = new MediaSession2.Builder(context) 540 .setSessionCallback(handlerExecutor, sessionCallback) 541 .build()) { 542 assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 543 Session2Token currentToken = session.getToken(); 544 assertTrue(listContainsToken(listener1.mTokens, currentToken)); 545 546 // Test removing listener 547 listener1.resetCountDownLatch(); 548 Session2TokenListener listener2 = new Session2TokenListener(); 549 mSessionManager.addOnSession2TokensChangedListener(listener2, handler); 550 mSessionManager.removeOnSession2TokensChangedListener(listener1); 551 552 session.close(); 553 assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 554 assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 555 } 556 } 557 testSession2TokensNotChangedBySession1()558 public void testSession2TokensNotChangedBySession1() throws Exception { 559 final Context context = getInstrumentation().getTargetContext(); 560 Handler handler = createHandler(); 561 562 Session2TokenListener listener = new Session2TokenListener(); 563 List<Session2Token> initialSession2Tokens = mSessionManager.getSession2Tokens(); 564 mSessionManager.addOnSession2TokensChangedListener(listener, handler); 565 MediaSession session = null; 566 try { 567 session = new MediaSession(context, TAG); 568 session.setActive(true); 569 session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 570 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 571 assertFalse(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); 572 List<Session2Token> laterSession2Tokens = mSessionManager.getSession2Tokens(); 573 574 assertEquals(initialSession2Tokens.size(), laterSession2Tokens.size()); 575 } finally { 576 if (session != null) { 577 session.release(); 578 } 579 } 580 } 581 testCustomClassConfigValuesAreValid()582 public void testCustomClassConfigValuesAreValid() throws Exception { 583 if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; 584 final Context context = getInstrumentation().getTargetContext(); 585 String customMediaKeyDispatcher = context.getString( 586 android.R.string.config_customMediaKeyDispatcher); 587 String customMediaSessionPolicyProvider = context.getString( 588 android.R.string.config_customMediaSessionPolicyProvider); 589 // MediaSessionService will call Class.forName(String) with the existing config value. 590 // If the config value is not valid (i.e. given class doesn't exist), the following 591 // methods will return false. 592 if (!customMediaKeyDispatcher.isEmpty()) { 593 assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher)); 594 } 595 if (!customMediaSessionPolicyProvider.isEmpty()) { 596 assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider( 597 customMediaSessionPolicyProvider)); 598 } 599 } 600 testIsTrustedForMediaControl_withEnabledNotificationListener()601 public void testIsTrustedForMediaControl_withEnabledNotificationListener() throws Exception { 602 List<String> packageNames = getEnabledNotificationListenerPackages(); 603 for (String packageName : packageNames) { 604 int packageUid = 605 mContext.getPackageManager().getPackageUid(packageName, /* flags= */ 0); 606 MediaSessionManager.RemoteUserInfo info = 607 new MediaSessionManager.RemoteUserInfo(packageName, /* pid= */ 0, packageUid); 608 assertTrue(mSessionManager.isTrustedForMediaControl(info)); 609 } 610 } 611 612 @NonMediaMainlineTest testIsTrustedForMediaControl_withInvalidUid()613 public void testIsTrustedForMediaControl_withInvalidUid() throws Exception { 614 List<String> packageNames = getEnabledNotificationListenerPackages(); 615 for (String packageName : packageNames) { 616 MediaSessionManager.RemoteUserInfo info = 617 new MediaSessionManager.RemoteUserInfo( 618 packageName, /* pid= */ 0, Process.myUid()); 619 assertFalse(mSessionManager.isTrustedForMediaControl(info)); 620 } 621 } 622 listContainsToken(List<Session2Token> tokens, Session2Token token)623 private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) { 624 for (int i = 0; i < tokens.size(); i++) { 625 if (tokens.get(i).equals(token)) { 626 return true; 627 } 628 } 629 return false; 630 } 631 createHandler()632 private Handler createHandler() { 633 HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest"); 634 handlerThread.start(); 635 return new Handler(handlerThread.getLooper()); 636 } 637 removeHandler(Handler handler)638 private void removeHandler(Handler handler) { 639 if (handler == null) { 640 return; 641 } 642 handler.getLooper().quitSafely(); 643 } 644 645 // This uses public APIs to dispatch key events, so sessions would consider this as 646 // 'media key event from this application'. simulateMediaKeyInput(int keyCode)647 private void simulateMediaKeyInput(int keyCode) { 648 long downTime = System.currentTimeMillis(); 649 mAudioManager.dispatchMediaKeyEvent( 650 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0)); 651 mAudioManager.dispatchMediaKeyEvent( 652 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0)); 653 } 654 getEnabledNotificationListenerPackages()655 private List<String> getEnabledNotificationListenerPackages() { 656 List<String> listeners = new ArrayList<>(); 657 String enabledNotificationListeners = 658 Settings.Secure.getString( 659 mContext.getContentResolver(), 660 ENABLED_NOTIFICATION_LISTENERS); 661 if (!TextUtils.isEmpty(enabledNotificationListeners)) { 662 String[] components = enabledNotificationListeners.split(":"); 663 for (String componentString : components) { 664 ComponentName component = ComponentName.unflattenFromString(componentString); 665 if (component != null) { 666 listeners.add(component.getPackageName()); 667 } 668 } 669 } 670 return listeners; 671 } 672 673 private class VolumeKeyLongPressListener 674 implements MediaSessionManager.OnVolumeKeyLongPressListener { 675 private final List<KeyEvent> mKeyEvents = new ArrayList<>(); 676 private final CountDownLatch mCountDownLatch; 677 private final Handler mHandler; 678 VolumeKeyLongPressListener(int count, Handler handler)679 public VolumeKeyLongPressListener(int count, Handler handler) { 680 mCountDownLatch = new CountDownLatch(count); 681 mHandler = handler; 682 } 683 684 @Override onVolumeKeyLongPress(KeyEvent event)685 public void onVolumeKeyLongPress(KeyEvent event) { 686 mKeyEvents.add(event); 687 // Ensure the listener is called on the thread. 688 assertEquals(mHandler.getLooper(), Looper.myLooper()); 689 mCountDownLatch.countDown(); 690 } 691 } 692 693 private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener { 694 private final CountDownLatch mCountDownLatch; 695 private final boolean mConsume; 696 private final Handler mHandler; 697 private final List<KeyEvent> mKeyEvents = new ArrayList<>(); 698 MediaKeyListener(int count, boolean consume, Handler handler)699 public MediaKeyListener(int count, boolean consume, Handler handler) { 700 mCountDownLatch = new CountDownLatch(count); 701 mConsume = consume; 702 mHandler = handler; 703 } 704 705 @Override onMediaKey(KeyEvent event)706 public boolean onMediaKey(KeyEvent event) { 707 mKeyEvents.add(event); 708 // Ensure the listener is called on the thread. 709 assertEquals(mHandler.getLooper(), Looper.myLooper()); 710 mCountDownLatch.countDown(); 711 return mConsume; 712 } 713 } 714 715 private class MediaSessionCallback extends MediaSession.Callback { 716 private final CountDownLatch mCountDownLatch; 717 private final MediaSession mSession; 718 private final List<KeyEvent> mKeyEvents = new ArrayList<>(); 719 private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>(); 720 MediaSessionCallback(int count, MediaSession session)721 private MediaSessionCallback(int count, MediaSession session) { 722 mCountDownLatch = new CountDownLatch(count); 723 mSession = session; 724 } 725 onMediaButtonEvent(Intent mediaButtonIntent)726 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 727 KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra( 728 Intent.EXTRA_KEY_EVENT); 729 assertNotNull(event); 730 mKeyEvents.add(event); 731 mCallers.add(mSession.getCurrentControllerInfo()); 732 mCountDownLatch.countDown(); 733 return true; 734 } 735 } 736 737 private class Session2Callback extends MediaSession2.SessionCallback { 738 private CountDownLatch mCountDownLatch; 739 Session2Callback()740 private Session2Callback() { 741 mCountDownLatch = new CountDownLatch(1); 742 } 743 744 @Override onConnect(MediaSession2 session, MediaSession2.ControllerInfo controller)745 public Session2CommandGroup onConnect(MediaSession2 session, 746 MediaSession2.ControllerInfo controller) { 747 if (controller.getUid() == Process.SYSTEM_UID) { 748 // System server will try to connect here for monitor session. 749 mCountDownLatch.countDown(); 750 } 751 return new Session2CommandGroup.Builder().build(); 752 } 753 } 754 755 private class Session2TokenListener implements 756 MediaSessionManager.OnSession2TokensChangedListener { 757 private CountDownLatch mCountDownLatch; 758 private List<Session2Token> mTokens; 759 Session2TokenListener()760 private Session2TokenListener() { 761 mCountDownLatch = new CountDownLatch(1); 762 } 763 764 @Override onSession2TokensChanged(List<Session2Token> tokens)765 public void onSession2TokensChanged(List<Session2Token> tokens) { 766 mTokens = tokens; 767 mCountDownLatch.countDown(); 768 } 769 resetCountDownLatch()770 public void resetCountDownLatch() { 771 mCountDownLatch = new CountDownLatch(1); 772 } 773 } 774 775 private class MediaKeyEventSessionListener 776 implements MediaSessionManager.OnMediaKeyEventSessionChangedListener { 777 CountDownLatch mCountDownLatch; 778 MediaSession.Token mSessionToken; 779 String mPackageName; 780 MediaKeyEventSessionListener()781 MediaKeyEventSessionListener() { 782 mCountDownLatch = new CountDownLatch(1); 783 } 784 resetCountDownLatch()785 void resetCountDownLatch() { 786 mCountDownLatch = new CountDownLatch(1); 787 mSessionToken = null; 788 mPackageName = null; 789 } 790 791 @Override onMediaKeyEventSessionChanged(String packageName, MediaSession.Token sessionToken)792 public void onMediaKeyEventSessionChanged(String packageName, 793 MediaSession.Token sessionToken) { 794 if (ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU)) { 795 assertNotNull("The package name cannot be null.", packageName); 796 } 797 mSessionToken = sessionToken; 798 mPackageName = packageName; 799 mCountDownLatch.countDown(); 800 } 801 } 802 803 private class MediaKeyEventDispatchedListener 804 implements MediaSessionManager.OnMediaKeyEventDispatchedListener { 805 CountDownLatch mCountDownLatch; 806 KeyEvent mKeyEvent; 807 String mPackageName; 808 MediaSession.Token mSessionToken; 809 MediaKeyEventDispatchedListener()810 MediaKeyEventDispatchedListener() { 811 mCountDownLatch = new CountDownLatch(1); 812 } 813 resetCountDownLatch()814 void resetCountDownLatch() { 815 mCountDownLatch = new CountDownLatch(1); 816 } 817 818 @Override onMediaKeyEventDispatched(KeyEvent event, String packageName, MediaSession.Token sessionToken)819 public void onMediaKeyEventDispatched(KeyEvent event, String packageName, 820 MediaSession.Token sessionToken) { 821 mKeyEvent = event; 822 mPackageName = packageName; 823 mSessionToken = sessionToken; 824 825 mCountDownLatch.countDown(); 826 } 827 } 828 829 private static class HandlerExecutor implements Executor { 830 private final Handler mHandler; 831 HandlerExecutor(Handler handler)832 HandlerExecutor(Handler handler) { 833 mHandler = handler; 834 } 835 836 @Override execute(Runnable command)837 public void execute(Runnable command) { 838 mHandler.post(command); 839 } 840 } 841 } 842