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