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 17 package android.media.session; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SystemApi; 24 import android.annotation.SystemService; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.ParceledListSlice; 29 import android.media.AudioManager; 30 import android.media.IRemoteVolumeController; 31 import android.media.MediaSession2; 32 import android.media.Session2Token; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.os.ResultReceiver; 38 import android.os.ServiceManager; 39 import android.os.UserHandle; 40 import android.service.media.MediaBrowserService; 41 import android.service.notification.NotificationListenerService; 42 import android.text.TextUtils; 43 import android.util.ArrayMap; 44 import android.util.Log; 45 import android.view.KeyEvent; 46 47 import com.android.internal.annotations.GuardedBy; 48 49 import java.util.ArrayList; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Objects; 54 import java.util.concurrent.Executor; 55 56 /** 57 * Provides support for interacting with {@link MediaSession media sessions} 58 * that applications have published to express their ongoing media playback 59 * state. 60 * 61 * @see MediaSession 62 * @see MediaController 63 */ 64 @SystemService(Context.MEDIA_SESSION_SERVICE) 65 public final class MediaSessionManager { 66 private static final String TAG = "SessionManager"; 67 68 /** 69 * Used by IOnMediaKeyListener to indicate that the media key event isn't handled. 70 * @hide 71 */ 72 public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; 73 74 /** 75 * Used by IOnMediaKeyListener to indicate that the media key event is handled. 76 * @hide 77 */ 78 public static final int RESULT_MEDIA_KEY_HANDLED = 1; 79 private final ISessionManager mService; 80 private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub = 81 new OnMediaKeyEventDispatchedListenerStub(); 82 private final OnMediaKeyEventSessionChangedListenerStub 83 mOnMediaKeyEventSessionChangedListenerStub = 84 new OnMediaKeyEventSessionChangedListenerStub(); 85 86 private final Object mLock = new Object(); 87 @GuardedBy("mLock") 88 private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners = 89 new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>(); 90 @GuardedBy("mLock") 91 private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper> 92 mSession2TokensListeners = new ArrayMap<>(); 93 @GuardedBy("mLock") 94 private final Map<OnMediaKeyEventDispatchedListener, Executor> 95 mOnMediaKeyEventDispatchedListeners = new HashMap<>(); 96 @GuardedBy("mLock") 97 private final Map<OnMediaKeyEventSessionChangedListener, Executor> 98 mMediaKeyEventSessionChangedCallbacks = new HashMap<>(); 99 @GuardedBy("mLock") 100 private String mCurMediaKeyEventSessionPackage; 101 @GuardedBy("mLock") 102 private MediaSession.Token mCurMediaKeyEventSession; 103 104 private Context mContext; 105 private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener; 106 private OnMediaKeyListenerImpl mOnMediaKeyListener; 107 108 /** 109 * @hide 110 */ MediaSessionManager(Context context)111 public MediaSessionManager(Context context) { 112 // Consider rewriting like DisplayManagerGlobal 113 // Decide if we need context 114 mContext = context; 115 IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE); 116 mService = ISessionManager.Stub.asInterface(b); 117 } 118 119 /** 120 * Create a new session in the system and get the binder for it. 121 * 122 * @param tag A short name for debugging purposes. 123 * @param sessionInfo A bundle for additional information about this session. 124 * @return The binder object from the system 125 * @hide 126 */ 127 @NonNull createSession(@onNull MediaSession.CallbackStub cbStub, @NonNull String tag, @Nullable Bundle sessionInfo)128 public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag, 129 @Nullable Bundle sessionInfo) { 130 try { 131 return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo, 132 UserHandle.myUserId()); 133 } catch (RemoteException e) { 134 throw new RuntimeException(e); 135 } 136 } 137 138 /** 139 * This API is not generally intended for third party application developers. 140 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 141 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 142 * Library</a> for consistent behavior across all devices. 143 * <p> 144 * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is 145 * created. 146 * <p> 147 * Do not use this API directly, but create a new instance through the 148 * {@link MediaSession2.Builder} instead. 149 * 150 * @param token newly created session2 token 151 */ notifySession2Created(@onNull Session2Token token)152 public void notifySession2Created(@NonNull Session2Token token) { 153 if (token == null) { 154 throw new IllegalArgumentException("token shouldn't be null"); 155 } 156 if (token.getType() != Session2Token.TYPE_SESSION) { 157 throw new IllegalArgumentException("token's type should be TYPE_SESSION"); 158 } 159 try { 160 mService.notifySession2Created(token); 161 } catch (RemoteException e) { 162 e.rethrowFromSystemServer(); 163 } 164 } 165 166 /** 167 * Get a list of controllers for all ongoing sessions. The controllers will 168 * be provided in priority order with the most important controller at index 169 * 0. 170 * <p> 171 * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL 172 * permission be held by the calling app. You may also retrieve this list if 173 * your app is an enabled notification listener using the 174 * {@link NotificationListenerService} APIs, in which case you must pass the 175 * {@link ComponentName} of your enabled listener. 176 * 177 * @param notificationListener The enabled notification listener component. 178 * May be null. 179 * @return A list of controllers for ongoing sessions. 180 */ getActiveSessions( @ullable ComponentName notificationListener)181 public @NonNull List<MediaController> getActiveSessions( 182 @Nullable ComponentName notificationListener) { 183 return getActiveSessionsForUser(notificationListener, UserHandle.myUserId()); 184 } 185 186 /** 187 * Get active sessions for a specific user. To retrieve actions for a user 188 * other than your own you must hold the 189 * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission 190 * in addition to any other requirements. If you are an enabled notification 191 * listener you may only get sessions for the users you are enabled for. 192 * 193 * @param notificationListener The enabled notification listener component. 194 * May be null. 195 * @param userId The user id to fetch sessions for. 196 * @return A list of controllers for ongoing sessions. 197 * @hide 198 */ 199 @UnsupportedAppUsage getActiveSessionsForUser( @ullable ComponentName notificationListener, int userId)200 public @NonNull List<MediaController> getActiveSessionsForUser( 201 @Nullable ComponentName notificationListener, int userId) { 202 ArrayList<MediaController> controllers = new ArrayList<MediaController>(); 203 try { 204 List<MediaSession.Token> tokens = mService.getSessions(notificationListener, userId); 205 int size = tokens.size(); 206 for (int i = 0; i < size; i++) { 207 MediaController controller = new MediaController(mContext, tokens.get(i)); 208 controllers.add(controller); 209 } 210 } catch (RemoteException e) { 211 Log.e(TAG, "Failed to get active sessions: ", e); 212 } 213 return controllers; 214 } 215 216 /** 217 * This API is not generally intended for third party application developers. 218 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 219 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 220 * Library</a> for consistent behavior across all devices. 221 * <p> 222 * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the 223 * current user. 224 * <p> 225 * Although this API can be used without any restriction, each session owners can accept or 226 * reject your uses of {@link MediaSession2}. 227 * 228 * @return A list of {@link Session2Token}. 229 */ 230 @NonNull getSession2Tokens()231 public List<Session2Token> getSession2Tokens() { 232 return getSession2Tokens(UserHandle.myUserId()); 233 } 234 235 /** 236 * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the 237 * given user. 238 * <p> 239 * If you want to get tokens for another user, you must hold the 240 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL permission. 241 * 242 * @param userId The user id to fetch sessions for. 243 * @return A list of {@link Session2Token} 244 * @hide 245 */ 246 @NonNull getSession2Tokens(int userId)247 public List<Session2Token> getSession2Tokens(int userId) { 248 try { 249 ParceledListSlice slice = mService.getSession2Tokens(userId); 250 return slice == null ? new ArrayList<>() : slice.getList(); 251 } catch (RemoteException e) { 252 Log.e(TAG, "Failed to get session tokens", e); 253 } 254 return new ArrayList<>(); 255 } 256 257 /** 258 * Add a listener to be notified when the list of active sessions 259 * changes.This requires the 260 * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by 261 * the calling app. You may also retrieve this list if your app is an 262 * enabled notification listener using the 263 * {@link NotificationListenerService} APIs, in which case you must pass the 264 * {@link ComponentName} of your enabled listener. Updates will be posted to 265 * the thread that registered the listener. 266 * 267 * @param sessionListener The listener to add. 268 * @param notificationListener The enabled notification listener component. 269 * May be null. 270 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener)271 public void addOnActiveSessionsChangedListener( 272 @NonNull OnActiveSessionsChangedListener sessionListener, 273 @Nullable ComponentName notificationListener) { 274 addOnActiveSessionsChangedListener(sessionListener, notificationListener, null); 275 } 276 277 /** 278 * Add a listener to be notified when the list of active sessions 279 * changes.This requires the 280 * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by 281 * the calling app. You may also retrieve this list if your app is an 282 * enabled notification listener using the 283 * {@link NotificationListenerService} APIs, in which case you must pass the 284 * {@link ComponentName} of your enabled listener. Updates will be posted to 285 * the handler specified or to the caller's thread if the handler is null. 286 * 287 * @param sessionListener The listener to add. 288 * @param notificationListener The enabled notification listener component. 289 * May be null. 290 * @param handler The handler to post events to. 291 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler)292 public void addOnActiveSessionsChangedListener( 293 @NonNull OnActiveSessionsChangedListener sessionListener, 294 @Nullable ComponentName notificationListener, @Nullable Handler handler) { 295 addOnActiveSessionsChangedListener(sessionListener, notificationListener, 296 UserHandle.myUserId(), handler); 297 } 298 299 /** 300 * Add a listener to be notified when the list of active sessions 301 * changes.This requires the 302 * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by 303 * the calling app. You may also retrieve this list if your app is an 304 * enabled notification listener using the 305 * {@link NotificationListenerService} APIs, in which case you must pass the 306 * {@link ComponentName} of your enabled listener. 307 * 308 * @param sessionListener The listener to add. 309 * @param notificationListener The enabled notification listener component. 310 * May be null. 311 * @param userId The userId to listen for changes on. 312 * @param handler The handler to post updates on. 313 * @hide 314 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler)315 public void addOnActiveSessionsChangedListener( 316 @NonNull OnActiveSessionsChangedListener sessionListener, 317 @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) { 318 if (sessionListener == null) { 319 throw new IllegalArgumentException("listener may not be null"); 320 } 321 if (handler == null) { 322 handler = new Handler(); 323 } 324 synchronized (mLock) { 325 if (mListeners.get(sessionListener) != null) { 326 Log.w(TAG, "Attempted to add session listener twice, ignoring."); 327 return; 328 } 329 SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener, 330 handler); 331 try { 332 mService.addSessionsListener(wrapper.mStub, notificationListener, userId); 333 mListeners.put(sessionListener, wrapper); 334 } catch (RemoteException e) { 335 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e); 336 } 337 } 338 } 339 340 /** 341 * Stop receiving active sessions updates on the specified listener. 342 * 343 * @param listener The listener to remove. 344 */ removeOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener listener)345 public void removeOnActiveSessionsChangedListener( 346 @NonNull OnActiveSessionsChangedListener listener) { 347 if (listener == null) { 348 throw new IllegalArgumentException("listener may not be null"); 349 } 350 synchronized (mLock) { 351 SessionsChangedWrapper wrapper = mListeners.remove(listener); 352 if (wrapper != null) { 353 try { 354 mService.removeSessionsListener(wrapper.mStub); 355 } catch (RemoteException e) { 356 Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e); 357 } finally { 358 wrapper.release(); 359 } 360 } 361 } 362 } 363 364 /** 365 * This API is not generally intended for third party application developers. 366 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 367 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 368 * Library</a> for consistent behavior across all devices. 369 * <p> 370 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 371 * 372 * @param listener The listener to add 373 */ addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)374 public void addOnSession2TokensChangedListener( 375 @NonNull OnSession2TokensChangedListener listener) { 376 addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler()); 377 } 378 379 /** 380 * This API is not generally intended for third party application developers. 381 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 382 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 383 * Library</a> for consistent behavior across all devices. 384 * <p> 385 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 386 * 387 * @param listener The listener to add 388 * @param handler The handler to call listener on. 389 */ addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener, @NonNull Handler handler)390 public void addOnSession2TokensChangedListener( 391 @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) { 392 addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler); 393 } 394 395 /** 396 * This API is not generally intended for third party application developers. 397 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 398 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 399 * Library</a> for consistent behavior across all devices. 400 * <p> 401 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 402 * 403 * @param userId The userId to listen for changes on 404 * @param listener The listener to add 405 * @param handler The handler to call listener on. If {@code null}, calling thread's looper will 406 * be used. 407 * @hide 408 */ addOnSession2TokensChangedListener(int userId, @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler)409 public void addOnSession2TokensChangedListener(int userId, 410 @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) { 411 if (listener == null) { 412 throw new IllegalArgumentException("listener shouldn't be null"); 413 } 414 synchronized (mLock) { 415 if (mSession2TokensListeners.get(listener) != null) { 416 Log.w(TAG, "Attempted to add session listener twice, ignoring."); 417 return; 418 } 419 Session2TokensChangedWrapper wrapper = 420 new Session2TokensChangedWrapper(listener, handler); 421 try { 422 mService.addSession2TokensListener(wrapper.getStub(), userId); 423 mSession2TokensListeners.put(listener, wrapper); 424 } catch (RemoteException e) { 425 Log.e(TAG, "Error in addSessionTokensListener.", e); 426 e.rethrowFromSystemServer(); 427 } 428 } 429 } 430 431 /** 432 * This API is not generally intended for third party application developers. 433 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 434 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 435 * Library</a> for consistent behavior across all devices. 436 * <p> 437 * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates. 438 * 439 * @param listener The listener to remove. 440 */ removeOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)441 public void removeOnSession2TokensChangedListener( 442 @NonNull OnSession2TokensChangedListener listener) { 443 if (listener == null) { 444 throw new IllegalArgumentException("listener may not be null"); 445 } 446 final Session2TokensChangedWrapper wrapper; 447 synchronized (mLock) { 448 wrapper = mSession2TokensListeners.remove(listener); 449 } 450 if (wrapper != null) { 451 try { 452 mService.removeSession2TokensListener(wrapper.getStub()); 453 } catch (RemoteException e) { 454 Log.e(TAG, "Error in removeSessionTokensListener.", e); 455 e.rethrowFromSystemServer(); 456 } 457 } 458 } 459 460 /** 461 * Set the remote volume controller to receive volume updates on. 462 * Only for use by System UI and Settings application. 463 * 464 * @param rvc The volume controller to receive updates on. 465 * @hide 466 */ registerRemoteVolumeController(IRemoteVolumeController rvc)467 public void registerRemoteVolumeController(IRemoteVolumeController rvc) { 468 try { 469 mService.registerRemoteVolumeController(rvc); 470 } catch (RemoteException e) { 471 Log.e(TAG, "Error in registerRemoteVolumeController.", e); 472 } 473 } 474 475 /** 476 * Unregisters the remote volume controller which was previously registered with 477 * {@link #registerRemoteVolumeController(IRemoteVolumeController)}. 478 * Only for use by System UI and Settings application. 479 * 480 * @param rvc The volume controller which was registered. 481 * @hide 482 */ unregisterRemoteVolumeController(IRemoteVolumeController rvc)483 public void unregisterRemoteVolumeController(IRemoteVolumeController rvc) { 484 try { 485 mService.unregisterRemoteVolumeController(rvc); 486 } catch (RemoteException e) { 487 Log.e(TAG, "Error in unregisterRemoteVolumeController.", e); 488 } 489 } 490 491 /** 492 * Send a media key event. The receiver will be selected automatically. 493 * 494 * @param keyEvent The KeyEvent to send. 495 * @hide 496 */ dispatchMediaKeyEvent(@onNull KeyEvent keyEvent)497 public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) { 498 dispatchMediaKeyEvent(keyEvent, false); 499 } 500 501 /** 502 * Send a media key event. The receiver will be selected automatically. 503 * 504 * @param keyEvent The KeyEvent to send. 505 * @param needWakeLock True if a wake lock should be held while sending the key. 506 * @hide 507 */ dispatchMediaKeyEvent(@onNull KeyEvent keyEvent, boolean needWakeLock)508 public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) { 509 dispatchMediaKeyEventInternal(false, keyEvent, needWakeLock); 510 } 511 512 /** 513 * Send a media key event as system component. The receiver will be selected automatically. 514 * <p> 515 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or 516 * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key 517 * from the hardware devices. 518 * 519 * @param keyEvent The KeyEvent to send. 520 * @hide 521 */ dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent)522 public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) { 523 dispatchMediaKeyEventInternal(true, keyEvent, false); 524 } 525 dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, boolean needWakeLock)526 private void dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, 527 boolean needWakeLock) { 528 try { 529 mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent, 530 needWakeLock); 531 } catch (RemoteException e) { 532 Log.e(TAG, "Failed to send key event.", e); 533 } 534 } 535 536 /** 537 * Dispatches the media button event as system service to the session. 538 * <p> 539 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the 540 * foreground activity didn't consume the key from the hardware devices. 541 * 542 * @param sessionToken session token 543 * @param keyEvent media key event 544 * @return {@code true} if the event was sent to the session, {@code false} otherwise 545 * @hide 546 */ dispatchMediaKeyEventAsSystemService(@onNull MediaSession.Token sessionToken, @NonNull KeyEvent keyEvent)547 public boolean dispatchMediaKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken, 548 @NonNull KeyEvent keyEvent) { 549 if (sessionToken == null) { 550 throw new IllegalArgumentException("sessionToken shouldn't be null"); 551 } 552 if (keyEvent == null) { 553 throw new IllegalArgumentException("keyEvent shouldn't be null"); 554 } 555 if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { 556 return false; 557 } 558 try { 559 return mService.dispatchMediaKeyEventToSessionAsSystemService(mContext.getPackageName(), 560 sessionToken, keyEvent); 561 } catch (RemoteException e) { 562 Log.e(TAG, "Failed to send key event.", e); 563 } 564 return false; 565 } 566 567 /** 568 * Send a volume key event. The receiver will be selected automatically. 569 * 570 * @param keyEvent The volume KeyEvent to send. 571 * @hide 572 */ dispatchVolumeKeyEvent(@onNull KeyEvent keyEvent, int stream, boolean musicOnly)573 public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) { 574 dispatchVolumeKeyEventInternal(false, keyEvent, stream, musicOnly); 575 } 576 577 /** 578 * Dispatches the volume button event as system service to the session. This only effects the 579 * {@link MediaSession.Callback#getCurrentControllerInfo()} and doesn't bypass any permission 580 * check done by the system service. 581 * <p> 582 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or 583 * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key 584 * from the hardware devices. 585 * 586 * @param keyEvent The KeyEvent to send. 587 * @hide 588 */ dispatchVolumeKeyEventAsSystemService(@onNull KeyEvent keyEvent, int streamType)589 public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) { 590 dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false); 591 } 592 dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, int stream, boolean musicOnly)593 private void dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, 594 int stream, boolean musicOnly) { 595 try { 596 mService.dispatchVolumeKeyEvent(mContext.getPackageName(), mContext.getOpPackageName(), 597 asSystemService, keyEvent, stream, musicOnly); 598 } catch (RemoteException e) { 599 Log.e(TAG, "Failed to send volume key event.", e); 600 } 601 } 602 603 /** 604 * Dispatches the volume key event as system service to the session. 605 * <p> 606 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the 607 * foreground activity didn't consume the key from the hardware devices. 608 * 609 * @param sessionToken sessionToken 610 * @param keyEvent volume key event 611 * @hide 612 */ dispatchVolumeKeyEventAsSystemService(@onNull MediaSession.Token sessionToken, @NonNull KeyEvent keyEvent)613 public void dispatchVolumeKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken, 614 @NonNull KeyEvent keyEvent) { 615 if (sessionToken == null) { 616 throw new IllegalArgumentException("sessionToken shouldn't be null"); 617 } 618 if (keyEvent == null) { 619 throw new IllegalArgumentException("keyEvent shouldn't be null"); 620 } 621 try { 622 mService.dispatchVolumeKeyEventToSessionAsSystemService(mContext.getPackageName(), 623 mContext.getOpPackageName(), sessionToken, keyEvent); 624 } catch (RemoteException e) { 625 Log.wtf(TAG, "Error calling dispatchVolumeKeyEventAsSystemService", e); 626 } 627 } 628 629 /** 630 * Dispatch an adjust volume request to the system. It will be sent to the 631 * most relevant audio stream or media session. The direction must be one of 632 * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, 633 * {@link AudioManager#ADJUST_SAME}. 634 * 635 * @param suggestedStream The stream to fall back to if there isn't a 636 * relevant stream 637 * @param direction The direction to adjust volume in. 638 * @param flags Any flags to include with the volume change. 639 * @hide 640 */ dispatchAdjustVolume(int suggestedStream, int direction, int flags)641 public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) { 642 try { 643 mService.dispatchAdjustVolume(mContext.getPackageName(), mContext.getOpPackageName(), 644 suggestedStream, direction, flags); 645 } catch (RemoteException e) { 646 Log.e(TAG, "Failed to send adjust volume.", e); 647 } 648 } 649 650 /** 651 * Checks whether the remote user is a trusted app. 652 * <p> 653 * An app is trusted if the app holds the android.Manifest.permission.MEDIA_CONTENT_CONTROL 654 * permission or has an enabled notification listener. 655 * 656 * @param userInfo The remote user info from either 657 * {@link MediaSession#getCurrentControllerInfo()} or 658 * {@link MediaBrowserService#getCurrentBrowserInfo()}. 659 * @return {@code true} if the remote user is trusted and its package name matches with the UID. 660 * {@code false} otherwise. 661 */ isTrustedForMediaControl(@onNull RemoteUserInfo userInfo)662 public boolean isTrustedForMediaControl(@NonNull RemoteUserInfo userInfo) { 663 if (userInfo == null) { 664 throw new IllegalArgumentException("userInfo may not be null"); 665 } 666 if (userInfo.getPackageName() == null) { 667 return false; 668 } 669 try { 670 return mService.isTrusted( 671 userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid()); 672 } catch (RemoteException e) { 673 Log.wtf(TAG, "Cannot communicate with the service.", e); 674 } 675 return false; 676 } 677 678 /** 679 * Check if the global priority session is currently active. This can be 680 * used to decide if media keys should be sent to the session or to the app. 681 * 682 * @hide 683 */ isGlobalPriorityActive()684 public boolean isGlobalPriorityActive() { 685 try { 686 return mService.isGlobalPriorityActive(); 687 } catch (RemoteException e) { 688 Log.e(TAG, "Failed to check if the global priority is active.", e); 689 } 690 return false; 691 } 692 693 /** 694 * Set the volume key long-press listener. While the listener is set, the listener 695 * gets the volume key long-presses instead of changing volume. 696 * 697 * <p>System can only have a single volume key long-press listener. 698 * 699 * @param listener The volume key long-press listener. {@code null} to reset. 700 * @param handler The handler on which the listener should be invoked, or {@code null} 701 * if the listener should be invoked on the calling thread's looper. 702 * @hide 703 */ 704 @SystemApi 705 @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) setOnVolumeKeyLongPressListener( OnVolumeKeyLongPressListener listener, @Nullable Handler handler)706 public void setOnVolumeKeyLongPressListener( 707 OnVolumeKeyLongPressListener listener, @Nullable Handler handler) { 708 synchronized (mLock) { 709 try { 710 if (listener == null) { 711 mOnVolumeKeyLongPressListener = null; 712 mService.setOnVolumeKeyLongPressListener(null); 713 } else { 714 if (handler == null) { 715 handler = new Handler(); 716 } 717 mOnVolumeKeyLongPressListener = 718 new OnVolumeKeyLongPressListenerImpl(listener, handler); 719 mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener); 720 } 721 } catch (RemoteException e) { 722 Log.e(TAG, "Failed to set volume key long press listener", e); 723 } 724 } 725 } 726 727 /** 728 * Set the media key listener. While the listener is set, the listener 729 * gets the media key before any other media sessions but after the global priority session. 730 * If the listener handles the key (i.e. returns {@code true}), 731 * other sessions will not get the event. 732 * 733 * <p>System can only have a single media key listener. 734 * 735 * @param listener The media key listener. {@code null} to reset. 736 * @param handler The handler on which the listener should be invoked, or {@code null} 737 * if the listener should be invoked on the calling thread's looper. 738 * @hide 739 */ 740 @SystemApi 741 @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler)742 public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) { 743 synchronized (mLock) { 744 try { 745 if (listener == null) { 746 mOnMediaKeyListener = null; 747 mService.setOnMediaKeyListener(null); 748 } else { 749 if (handler == null) { 750 handler = new Handler(); 751 } 752 mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler); 753 mService.setOnMediaKeyListener(mOnMediaKeyListener); 754 } 755 } catch (RemoteException e) { 756 Log.e(TAG, "Failed to set media key listener", e); 757 } 758 } 759 } 760 761 /** 762 * Add a {@link OnMediaKeyEventDispatchedListener}. 763 * 764 * @param executor The executor on which the callback should be invoked 765 * @param listener A {@link OnMediaKeyEventDispatchedListener}. 766 * @hide 767 */ 768 @SystemApi 769 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) addOnMediaKeyEventDispatchedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventDispatchedListener listener)770 public void addOnMediaKeyEventDispatchedListener( 771 @NonNull @CallbackExecutor Executor executor, 772 @NonNull OnMediaKeyEventDispatchedListener listener) { 773 if (executor == null) { 774 throw new NullPointerException("executor shouldn't be null"); 775 } 776 if (listener == null) { 777 throw new NullPointerException("listener shouldn't be null"); 778 } 779 synchronized (mLock) { 780 try { 781 mOnMediaKeyEventDispatchedListeners.put(listener, executor); 782 if (mOnMediaKeyEventDispatchedListeners.size() == 1) { 783 mService.addOnMediaKeyEventDispatchedListener( 784 mOnMediaKeyEventDispatchedListenerStub); 785 } 786 } catch (RemoteException e) { 787 Log.e(TAG, "Failed to set media key listener", e); 788 } 789 } 790 } 791 792 /** 793 * Remove a {@link OnMediaKeyEventDispatchedListener}. 794 * 795 * @param listener A {@link OnMediaKeyEventDispatchedListener}. 796 * @hide 797 */ 798 @SystemApi 799 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) removeOnMediaKeyEventDispatchedListener( @onNull OnMediaKeyEventDispatchedListener listener)800 public void removeOnMediaKeyEventDispatchedListener( 801 @NonNull OnMediaKeyEventDispatchedListener listener) { 802 if (listener == null) { 803 throw new NullPointerException("listener shouldn't be null"); 804 } 805 synchronized (mLock) { 806 try { 807 mOnMediaKeyEventDispatchedListeners.remove(listener); 808 if (mOnMediaKeyEventDispatchedListeners.size() == 0) { 809 mService.removeOnMediaKeyEventDispatchedListener( 810 mOnMediaKeyEventDispatchedListenerStub); 811 } 812 } catch (RemoteException e) { 813 Log.e(TAG, "Failed to set media key event dispatched listener", e); 814 } 815 } 816 } 817 818 /** 819 * Add a {@link OnMediaKeyEventDispatchedListener}. 820 * 821 * @param executor The executor on which the callback should be invoked 822 * @param listener A {@link OnMediaKeyEventSessionChangedListener}. 823 * @hide 824 */ 825 @SystemApi 826 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) addOnMediaKeyEventSessionChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventSessionChangedListener listener)827 public void addOnMediaKeyEventSessionChangedListener( 828 @NonNull @CallbackExecutor Executor executor, 829 @NonNull OnMediaKeyEventSessionChangedListener listener) { 830 if (executor == null) { 831 throw new NullPointerException("executor shouldn't be null"); 832 } 833 if (listener == null) { 834 throw new NullPointerException("listener shouldn't be null"); 835 } 836 synchronized (mLock) { 837 try { 838 mMediaKeyEventSessionChangedCallbacks.put(listener, executor); 839 executor.execute( 840 () -> listener.onMediaKeyEventSessionChanged( 841 mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession)); 842 if (mMediaKeyEventSessionChangedCallbacks.size() == 1) { 843 mService.addOnMediaKeyEventSessionChangedListener( 844 mOnMediaKeyEventSessionChangedListenerStub); 845 } 846 } catch (RemoteException e) { 847 Log.e(TAG, "Failed to set media key listener", e); 848 } 849 } 850 } 851 852 /** 853 * Remove a {@link OnMediaKeyEventSessionChangedListener}. 854 * 855 * @param listener A {@link OnMediaKeyEventSessionChangedListener}. 856 * @hide 857 */ 858 @SystemApi 859 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) removeOnMediaKeyEventSessionChangedListener( @onNull OnMediaKeyEventSessionChangedListener listener)860 public void removeOnMediaKeyEventSessionChangedListener( 861 @NonNull OnMediaKeyEventSessionChangedListener listener) { 862 if (listener == null) { 863 throw new NullPointerException("listener shouldn't be null"); 864 } 865 synchronized (mLock) { 866 try { 867 mMediaKeyEventSessionChangedCallbacks.remove(listener); 868 if (mMediaKeyEventSessionChangedCallbacks.size() == 0) { 869 mService.removeOnMediaKeyEventSessionChangedListener( 870 mOnMediaKeyEventSessionChangedListenerStub); 871 } 872 } catch (RemoteException e) { 873 Log.e(TAG, "Failed to set media key listener", e); 874 } 875 } 876 } 877 878 /** 879 * Listens for changes to the list of active sessions. This can be added 880 * using {@link #addOnActiveSessionsChangedListener}. 881 */ 882 public interface OnActiveSessionsChangedListener { onActiveSessionsChanged(@ullable List<MediaController> controllers)883 public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); 884 } 885 886 /** 887 * This API is not generally intended for third party application developers. 888 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 889 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 890 * Library</a> for consistent behavior across all devices. 891 * <p> 892 * Listens for changes to the {@link #getSession2Tokens()}. This can be added 893 * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}. 894 */ 895 public interface OnSession2TokensChangedListener { 896 /** 897 * Called when the {@link #getSession2Tokens()} is changed. 898 * 899 * @param tokens list of {@link Session2Token} 900 */ onSession2TokensChanged(@onNull List<Session2Token> tokens)901 void onSession2TokensChanged(@NonNull List<Session2Token> tokens); 902 } 903 904 /** 905 * Listens the volume key long-presses. 906 * @hide 907 */ 908 @SystemApi 909 public interface OnVolumeKeyLongPressListener { 910 /** 911 * Called when the volume key is long-pressed. 912 * <p>This will be called for both down and up events. 913 */ onVolumeKeyLongPress(KeyEvent event)914 void onVolumeKeyLongPress(KeyEvent event); 915 } 916 917 /** 918 * Listens the media key. 919 * @hide 920 */ 921 @SystemApi 922 public interface OnMediaKeyListener { 923 /** 924 * Called when the media key is pressed. 925 * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with 926 * repeat count zero), it must also comsume all following key events. 927 * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP). 928 * <p>If it takes more than 1s to return, the key event will be sent to 929 * other media sessions. 930 */ onMediaKey(KeyEvent event)931 boolean onMediaKey(KeyEvent event); 932 } 933 934 /** 935 * Listener to receive when the media session service 936 * @hide 937 */ 938 @SystemApi 939 public interface OnMediaKeyEventDispatchedListener { 940 /** 941 * Called when a media key event is dispatched through the media session service. The 942 * session token can be {@link null} if the framework has sent the media key event to the 943 * media button receiver to revive the media app's playback. 944 * 945 * the session is dead when , but the framework sent 946 * 947 * @param event Dispatched media key event. 948 * @param packageName Package 949 * @param sessionToken The media session's token. Can be {@code null}. 950 */ onMediaKeyEventDispatched(@onNull KeyEvent event, @NonNull String packageName, @NonNull MediaSession.Token sessionToken)951 default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName, 952 @NonNull MediaSession.Token sessionToken) { } 953 } 954 955 /** 956 * Listener to receive changes in the media key event session, which would receive the media key 957 * event unless specified. 958 * @hide 959 */ 960 @SystemApi 961 public interface OnMediaKeyEventSessionChangedListener { 962 /** 963 * Called when the media key session is changed to the given media session. The key event 964 * session is the media session which would receive key event by default, unless the caller 965 * has specified the target. 966 * <p> 967 * The session token can be {@link null} if the media button session is unset. In that case, 968 * framework would dispatch to the last sessions's media button receiver. 969 * 970 * @param packageName The package name who would receive the media key event. Can be empty. 971 * @param sessionToken The media session's token. Can be {@code null.} 972 */ onMediaKeyEventSessionChanged(@onNull String packageName, @Nullable MediaSession.Token sessionToken)973 default void onMediaKeyEventSessionChanged(@NonNull String packageName, 974 @Nullable MediaSession.Token sessionToken) { } 975 } 976 977 /** 978 * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}. 979 * This can be used to decide whether the remote user is trusted app, and also differentiate 980 * caller of {@link MediaSession} and {@link MediaBrowserService} callbacks. 981 * <p> 982 * See {@link #equals(Object)} to take a look at how it differentiate media controller. 983 * 984 * @see #isTrustedForMediaControl(RemoteUserInfo) 985 */ 986 public static final class RemoteUserInfo { 987 private final String mPackageName; 988 private final int mPid; 989 private final int mUid; 990 991 /** 992 * Create a new remote user information. 993 * 994 * @param packageName The package name of the remote user 995 * @param pid The pid of the remote user 996 * @param uid The uid of the remote user 997 */ RemoteUserInfo(@onNull String packageName, int pid, int uid)998 public RemoteUserInfo(@NonNull String packageName, int pid, int uid) { 999 mPackageName = packageName; 1000 mPid = pid; 1001 mUid = uid; 1002 } 1003 1004 /** 1005 * @return package name of the controller 1006 */ getPackageName()1007 public String getPackageName() { 1008 return mPackageName; 1009 } 1010 1011 /** 1012 * @return pid of the controller 1013 */ getPid()1014 public int getPid() { 1015 return mPid; 1016 } 1017 1018 /** 1019 * @return uid of the controller 1020 */ getUid()1021 public int getUid() { 1022 return mUid; 1023 } 1024 1025 /** 1026 * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal 1027 * if and only if they have the same package name, same pid, and same uid. 1028 * 1029 * @param obj the reference object with which to compare. 1030 * @return {@code true} if equals, {@code false} otherwise 1031 */ 1032 @Override equals(@ullable Object obj)1033 public boolean equals(@Nullable Object obj) { 1034 if (!(obj instanceof RemoteUserInfo)) { 1035 return false; 1036 } 1037 if (this == obj) { 1038 return true; 1039 } 1040 RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj; 1041 return TextUtils.equals(mPackageName, otherUserInfo.mPackageName) 1042 && mPid == otherUserInfo.mPid 1043 && mUid == otherUserInfo.mUid; 1044 } 1045 1046 @Override hashCode()1047 public int hashCode() { 1048 return Objects.hash(mPackageName, mPid, mUid); 1049 } 1050 } 1051 1052 private static final class SessionsChangedWrapper { 1053 private Context mContext; 1054 private OnActiveSessionsChangedListener mListener; 1055 private Handler mHandler; 1056 SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, Handler handler)1057 public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, 1058 Handler handler) { 1059 mContext = context; 1060 mListener = listener; 1061 mHandler = handler; 1062 } 1063 1064 private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() { 1065 @Override 1066 public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) { 1067 final Handler handler = mHandler; 1068 if (handler != null) { 1069 handler.post(new Runnable() { 1070 @Override 1071 public void run() { 1072 final Context context = mContext; 1073 if (context != null) { 1074 ArrayList<MediaController> controllers = new ArrayList<>(); 1075 int size = tokens.size(); 1076 for (int i = 0; i < size; i++) { 1077 controllers.add(new MediaController(context, tokens.get(i))); 1078 } 1079 final OnActiveSessionsChangedListener listener = mListener; 1080 if (listener != null) { 1081 listener.onActiveSessionsChanged(controllers); 1082 } 1083 } 1084 } 1085 }); 1086 } 1087 } 1088 }; 1089 release()1090 private void release() { 1091 mListener = null; 1092 mContext = null; 1093 mHandler = null; 1094 } 1095 } 1096 1097 private static final class Session2TokensChangedWrapper { 1098 private final OnSession2TokensChangedListener mListener; 1099 private final Handler mHandler; 1100 private final ISession2TokensListener.Stub mStub = 1101 new ISession2TokensListener.Stub() { 1102 @Override 1103 public void onSession2TokensChanged(final List<Session2Token> tokens) { 1104 mHandler.post(() -> mListener.onSession2TokensChanged(tokens)); 1105 } 1106 }; 1107 Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler)1108 Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler) { 1109 mListener = listener; 1110 mHandler = (handler == null) ? new Handler() : new Handler(handler.getLooper()); 1111 } 1112 getStub()1113 public ISession2TokensListener.Stub getStub() { 1114 return mStub; 1115 } 1116 } 1117 1118 private static final class OnVolumeKeyLongPressListenerImpl 1119 extends IOnVolumeKeyLongPressListener.Stub { 1120 private OnVolumeKeyLongPressListener mListener; 1121 private Handler mHandler; 1122 OnVolumeKeyLongPressListenerImpl( OnVolumeKeyLongPressListener listener, Handler handler)1123 public OnVolumeKeyLongPressListenerImpl( 1124 OnVolumeKeyLongPressListener listener, Handler handler) { 1125 mListener = listener; 1126 mHandler = handler; 1127 } 1128 1129 @Override onVolumeKeyLongPress(KeyEvent event)1130 public void onVolumeKeyLongPress(KeyEvent event) { 1131 if (mListener == null || mHandler == null) { 1132 Log.w(TAG, "Failed to call volume key long-press listener." + 1133 " Either mListener or mHandler is null"); 1134 return; 1135 } 1136 mHandler.post(new Runnable() { 1137 @Override 1138 public void run() { 1139 mListener.onVolumeKeyLongPress(event); 1140 } 1141 }); 1142 } 1143 } 1144 1145 private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub { 1146 private OnMediaKeyListener mListener; 1147 private Handler mHandler; 1148 OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler)1149 public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) { 1150 mListener = listener; 1151 mHandler = handler; 1152 } 1153 1154 @Override onMediaKey(KeyEvent event, ResultReceiver result)1155 public void onMediaKey(KeyEvent event, ResultReceiver result) { 1156 if (mListener == null || mHandler == null) { 1157 Log.w(TAG, "Failed to call media key listener." + 1158 " Either mListener or mHandler is null"); 1159 return; 1160 } 1161 mHandler.post(new Runnable() { 1162 @Override 1163 public void run() { 1164 boolean handled = mListener.onMediaKey(event); 1165 Log.d(TAG, "The media key listener is returned " + handled); 1166 if (result != null) { 1167 result.send( 1168 handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED, 1169 null); 1170 } 1171 } 1172 }); 1173 } 1174 } 1175 1176 private final class OnMediaKeyEventDispatchedListenerStub 1177 extends IOnMediaKeyEventDispatchedListener.Stub { 1178 1179 @Override onMediaKeyEventDispatched(KeyEvent event, String packageName, MediaSession.Token sessionToken)1180 public void onMediaKeyEventDispatched(KeyEvent event, String packageName, 1181 MediaSession.Token sessionToken) { 1182 synchronized (mLock) { 1183 for (Map.Entry<OnMediaKeyEventDispatchedListener, Executor> e 1184 : mOnMediaKeyEventDispatchedListeners.entrySet()) { 1185 e.getValue().execute( 1186 () -> e.getKey().onMediaKeyEventDispatched(event, packageName, 1187 sessionToken)); 1188 } 1189 } 1190 } 1191 } 1192 1193 private final class OnMediaKeyEventSessionChangedListenerStub 1194 extends IOnMediaKeyEventSessionChangedListener.Stub { 1195 @Override onMediaKeyEventSessionChanged(String packageName, MediaSession.Token sessionToken)1196 public void onMediaKeyEventSessionChanged(String packageName, 1197 MediaSession.Token sessionToken) { 1198 synchronized (mLock) { 1199 mCurMediaKeyEventSessionPackage = packageName; 1200 mCurMediaKeyEventSession = sessionToken; 1201 for (Map.Entry<OnMediaKeyEventSessionChangedListener, Executor> e 1202 : mMediaKeyEventSessionChangedCallbacks.entrySet()) { 1203 e.getValue().execute(() -> e.getKey().onMediaKeyEventSessionChanged(packageName, 1204 sessionToken)); 1205 } 1206 } 1207 } 1208 } 1209 } 1210