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