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