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