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 * This API is not generally intended for third party application developers. 159 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 160 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 161 * Library</a> for consistent behavior across all devices. 162 * <p> 163 * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is 164 * created. 165 * <p> 166 * Do not use this API directly, but create a new instance through the 167 * {@link MediaSession2.Builder} instead. 168 * 169 * @param token newly created session2 token 170 * @deprecated Don't use this method. A new media session is notified automatically. 171 */ 172 @Deprecated notifySession2Created(@onNull Session2Token token)173 public void notifySession2Created(@NonNull Session2Token token) { 174 // Does nothing 175 } 176 177 /** 178 * Get a list of controllers for all ongoing sessions. The controllers will 179 * be provided in priority order with the most important controller at index 180 * 0. 181 * <p> 182 * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} 183 * permission be held by the calling app. You may also retrieve this list if 184 * your app is an enabled notification listener using the 185 * {@link NotificationListenerService} APIs, in which case you must pass the 186 * {@link ComponentName} of your enabled listener. 187 * 188 * @param notificationListener The enabled notification listener component. 189 * May be null. 190 * @return A list of controllers for ongoing sessions. 191 */ getActiveSessions( @ullable ComponentName notificationListener)192 public @NonNull List<MediaController> getActiveSessions( 193 @Nullable ComponentName notificationListener) { 194 return getActiveSessionsForUser(notificationListener, UserHandle.myUserId()); 195 } 196 197 /** 198 * Gets the media key event session, which would receive a media key event unless specified. 199 * @return The media key event session, which would receive key events by default, unless 200 * the caller has specified the target. Can be {@code null}. 201 * @hide 202 */ 203 @SystemApi 204 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) 205 @Nullable getMediaKeyEventSession()206 public MediaSession.Token getMediaKeyEventSession() { 207 try { 208 return mService.getMediaKeyEventSession(); 209 } catch (RemoteException ex) { 210 Log.e(TAG, "Failed to get media key event session", ex); 211 } 212 return null; 213 } 214 215 /** 216 * Gets the package name of the media key event session. 217 * @return The package name of the media key event session or the last session's media button 218 * receiver if the media key event session is {@code null}. 219 * @see #getMediaKeyEventSession() 220 * @hide 221 */ 222 @SystemApi 223 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) 224 @NonNull getMediaKeyEventSessionPackageName()225 public String getMediaKeyEventSessionPackageName() { 226 try { 227 String packageName = mService.getMediaKeyEventSessionPackageName(); 228 return (packageName != null) ? packageName : ""; 229 } catch (RemoteException ex) { 230 Log.e(TAG, "Failed to get media key event session", ex); 231 } 232 return ""; 233 } 234 235 /** 236 * Get active sessions for the given user. 237 * <p> 238 * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be 239 * held by the calling app. You may also retrieve this list if your app is an enabled 240 * notification listener using the {@link NotificationListenerService} APIs, in which case you 241 * must pass the {@link ComponentName} of your enabled listener. 242 * <p> 243 * The calling application needs to hold the 244 * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission in order to 245 * retrieve sessions for user ids that do not belong to current process. 246 * 247 * @param notificationListener The enabled notification listener component. May be null. 248 * @param userHandle The user handle to fetch sessions for. 249 * @return A list of controllers for ongoing sessions. 250 * @hide 251 */ 252 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 253 @SuppressLint("UserHandle") getActiveSessionsForUser( @ullable ComponentName notificationListener, @NonNull UserHandle userHandle)254 public @NonNull List<MediaController> getActiveSessionsForUser( 255 @Nullable ComponentName notificationListener, @NonNull UserHandle userHandle) { 256 Objects.requireNonNull(userHandle, "userHandle shouldn't be null"); 257 return getActiveSessionsForUser(notificationListener, userHandle.getIdentifier()); 258 } 259 getActiveSessionsForUser(ComponentName notificationListener, int userId)260 private List<MediaController> getActiveSessionsForUser(ComponentName notificationListener, 261 int userId) { 262 ArrayList<MediaController> controllers = new ArrayList<MediaController>(); 263 try { 264 List<MediaSession.Token> tokens = mService.getSessions(notificationListener, 265 userId); 266 int size = tokens.size(); 267 for (int i = 0; i < size; i++) { 268 MediaController controller = new MediaController(mContext, tokens.get(i)); 269 controllers.add(controller); 270 } 271 } catch (RemoteException e) { 272 Log.e(TAG, "Failed to get active sessions: ", e); 273 } 274 return controllers; 275 } 276 277 /** 278 * This API is not generally intended for third party application developers. 279 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 280 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 281 * Library</a> for consistent behavior across all devices. 282 * <p> 283 * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the 284 * current user. 285 * <p> 286 * Although this API can be used without any restriction, each session owners can accept or 287 * reject your uses of {@link MediaSession2}. 288 * 289 * @return A list of {@link Session2Token}. 290 */ 291 @NonNull getSession2Tokens()292 public List<Session2Token> getSession2Tokens() { 293 return mCommunicationManager.getSession2Tokens(); 294 } 295 296 /** 297 * Add a listener to be notified when the list of active sessions changes. 298 * <p> 299 * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be 300 * held by the calling app. You may also retrieve this list if your app is an enabled 301 * notificationlistener using the {@link NotificationListenerService} APIs, in which case you 302 * must pass the {@link ComponentName} of your enabled listener. 303 * 304 * @param sessionListener The listener to add. 305 * @param notificationListener The enabled notification listener component. May be null. 306 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener)307 public void addOnActiveSessionsChangedListener( 308 @NonNull OnActiveSessionsChangedListener sessionListener, 309 @Nullable ComponentName notificationListener) { 310 addOnActiveSessionsChangedListener(sessionListener, notificationListener, null); 311 } 312 313 /** 314 * Add a listener to be notified when the list of active sessions changes. 315 * <p> 316 * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be 317 * held by the calling app. You may also retrieve this list if your app is an enabled 318 * notification listener using the {@link NotificationListenerService} APIs, in which case you 319 * must pass the {@link ComponentName} of your enabled listener. Updates will be posted to the 320 * handler specified or to the caller's thread if the handler is null. 321 * 322 * @param sessionListener The listener to add. 323 * @param notificationListener The enabled notification listener component. May be null. 324 * @param handler The handler to post events to. 325 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler)326 public void addOnActiveSessionsChangedListener( 327 @NonNull OnActiveSessionsChangedListener sessionListener, 328 @Nullable ComponentName notificationListener, @Nullable Handler handler) { 329 addOnActiveSessionsChangedListener(sessionListener, notificationListener, 330 UserHandle.myUserId(), handler == null ? null : new HandlerExecutor(handler)); 331 } 332 333 /** 334 * Add a listener to be notified when the list of active sessions changes. 335 * <p> 336 * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be 337 * held by the calling app. You may also retrieve this list if your app is an enabled 338 * notification listener using the {@link NotificationListenerService} APIs, in which case you 339 * must pass the {@link ComponentName} of your enabled listener. Updates will be posted to the 340 * handler specified or to the caller's thread if the handler is null. 341 * <p> 342 * The calling application needs to hold the 343 * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission in order to 344 * add listeners for user ids that do not belong to current process. 345 * 346 * @param notificationListener The enabled notification listener component. May be null. 347 * @param userHandle The user handle to listen for changes on. 348 * @param executor The executor on which the listener should be invoked 349 * @param sessionListener The listener to add. 350 * @hide 351 */ 352 @SuppressLint("UserHandle") 353 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) addOnActiveSessionsChangedListener( @ullable ComponentName notificationListener, @NonNull UserHandle userHandle, @NonNull Executor executor, @NonNull OnActiveSessionsChangedListener sessionListener)354 public void addOnActiveSessionsChangedListener( 355 @Nullable ComponentName notificationListener, 356 @NonNull UserHandle userHandle, @NonNull Executor executor, 357 @NonNull OnActiveSessionsChangedListener sessionListener) { 358 Objects.requireNonNull(userHandle, "userHandle shouldn't be null"); 359 Objects.requireNonNull(executor, "executor shouldn't be null"); 360 addOnActiveSessionsChangedListener(sessionListener, notificationListener, 361 userHandle.getIdentifier(), executor); 362 } 363 addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, @Nullable Executor executor)364 private void addOnActiveSessionsChangedListener( 365 @NonNull OnActiveSessionsChangedListener sessionListener, 366 @Nullable ComponentName notificationListener, int userId, 367 @Nullable Executor executor) { 368 Objects.requireNonNull(sessionListener, "sessionListener shouldn't be null"); 369 if (executor == null) { 370 executor = new HandlerExecutor(new Handler()); 371 } 372 373 synchronized (mLock) { 374 if (mListeners.get(sessionListener) != null) { 375 Log.w(TAG, "Attempted to add session listener twice, ignoring."); 376 return; 377 } 378 SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener, 379 executor); 380 try { 381 mService.addSessionsListener(wrapper.mStub, notificationListener, userId); 382 mListeners.put(sessionListener, wrapper); 383 } catch (RemoteException e) { 384 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e); 385 } 386 } 387 } 388 389 /** 390 * Stop receiving active sessions updates on the specified listener. 391 * 392 * @param sessionListener The listener to remove. 393 */ removeOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener)394 public void removeOnActiveSessionsChangedListener( 395 @NonNull OnActiveSessionsChangedListener sessionListener) { 396 Objects.requireNonNull(sessionListener, "sessionListener shouldn't be null"); 397 synchronized (mLock) { 398 SessionsChangedWrapper wrapper = mListeners.remove(sessionListener); 399 if (wrapper != null) { 400 try { 401 mService.removeSessionsListener(wrapper.mStub); 402 } catch (RemoteException e) { 403 Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e); 404 } finally { 405 wrapper.release(); 406 } 407 } 408 } 409 } 410 411 /** 412 * This API is not generally intended for third party application developers. 413 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 414 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 415 * Library</a> for consistent behavior across all devices. 416 * <p> 417 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 418 * 419 * @param listener The listener to add 420 */ addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)421 public void addOnSession2TokensChangedListener( 422 @NonNull OnSession2TokensChangedListener listener) { 423 addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, 424 new HandlerExecutor(new Handler())); 425 } 426 427 /** 428 * This API is not generally intended for third party application developers. 429 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 430 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 431 * Library</a> for consistent behavior across all devices. 432 * <p> 433 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 434 * 435 * @param listener The listener to add 436 * @param handler The handler to call listener on. 437 */ addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener, @NonNull Handler handler)438 public void addOnSession2TokensChangedListener( 439 @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) { 440 Objects.requireNonNull(handler, "handler shouldn't be null"); 441 addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, 442 new HandlerExecutor(handler)); 443 } 444 445 /** 446 * This API is not generally intended for third party application developers. 447 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 448 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 449 * Library</a> for consistent behavior across all devices. 450 * <p> 451 * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. 452 * <p> 453 * The calling application needs to hold the 454 * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission in order to 455 * add listeners for user ids that do not belong to current process. 456 * 457 * @param userHandle The userHandle to listen for changes on 458 * @param listener The listener to add 459 * @param executor The executor on which the listener should be invoked 460 * @hide 461 */ 462 @SuppressLint("UserHandle") addOnSession2TokensChangedListener(@onNull UserHandle userHandle, @NonNull OnSession2TokensChangedListener listener, @NonNull Executor executor)463 public void addOnSession2TokensChangedListener(@NonNull UserHandle userHandle, 464 @NonNull OnSession2TokensChangedListener listener, @NonNull Executor executor) { 465 Objects.requireNonNull(userHandle, "userHandle shouldn't be null"); 466 Objects.requireNonNull(executor, "executor shouldn't be null"); 467 addOnSession2TokensChangedListener(userHandle.getIdentifier(), listener, executor); 468 } 469 addOnSession2TokensChangedListener(int userId, OnSession2TokensChangedListener listener, Executor executor)470 private void addOnSession2TokensChangedListener(int userId, 471 OnSession2TokensChangedListener listener, Executor executor) { 472 Objects.requireNonNull(listener, "listener shouldn't be null"); 473 synchronized (mLock) { 474 if (mSession2TokensListeners.get(listener) != null) { 475 Log.w(TAG, "Attempted to add session listener twice, ignoring."); 476 return; 477 } 478 Session2TokensChangedWrapper wrapper = 479 new Session2TokensChangedWrapper(listener, executor); 480 try { 481 mService.addSession2TokensListener(wrapper.getStub(), userId); 482 mSession2TokensListeners.put(listener, wrapper); 483 } catch (RemoteException e) { 484 Log.e(TAG, "Error in addSessionTokensListener.", e); 485 e.rethrowFromSystemServer(); 486 } 487 } 488 } 489 490 /** 491 * This API is not generally intended for third party application developers. 492 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 493 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 494 * Library</a> for consistent behavior across all devices. 495 * <p> 496 * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates. 497 * 498 * @param listener The listener to remove. 499 */ removeOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)500 public void removeOnSession2TokensChangedListener( 501 @NonNull OnSession2TokensChangedListener listener) { 502 Objects.requireNonNull(listener, "listener shouldn't be null"); 503 final Session2TokensChangedWrapper wrapper; 504 synchronized (mLock) { 505 wrapper = mSession2TokensListeners.remove(listener); 506 } 507 if (wrapper != null) { 508 try { 509 mService.removeSession2TokensListener(wrapper.getStub()); 510 } catch (RemoteException e) { 511 Log.e(TAG, "Error in removeSessionTokensListener.", e); 512 e.rethrowFromSystemServer(); 513 } 514 } 515 } 516 517 /** 518 * Set the remote volume controller callback to receive volume updates on. 519 * Only for use by System UI and Settings application. 520 * 521 * @param executor The executor on which the callback should be invoked 522 * @param callback The volume controller callback to receive updates on. 523 * 524 * @hide 525 */ 526 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) registerRemoteSessionCallback( @onNull @allbackExecutor Executor executor, @NonNull RemoteSessionCallback callback)527 public void registerRemoteSessionCallback( 528 @NonNull @CallbackExecutor Executor executor, 529 @NonNull RemoteSessionCallback callback) { 530 Objects.requireNonNull(executor, "executor shouldn't be null"); 531 Objects.requireNonNull(callback, "callback shouldn't be null"); 532 boolean shouldRegisterCallback = false; 533 synchronized (mLock) { 534 int prevCallbackCount = mRemoteSessionCallbacks.size(); 535 mRemoteSessionCallbacks.put(callback, executor); 536 if (prevCallbackCount == 0 && mRemoteSessionCallbacks.size() == 1) { 537 shouldRegisterCallback = true; 538 } 539 } 540 if (shouldRegisterCallback) { 541 try { 542 mService.registerRemoteSessionCallback(mRemoteSessionCallbackStub); 543 } catch (RemoteException e) { 544 Log.e(TAG, "Failed to register remote volume controller callback", e); 545 } 546 } 547 } 548 549 /** 550 * Unregisters the remote volume controller callback which was previously registered with 551 * {@link #registerRemoteSessionCallback(Executor, RemoteSessionCallback)}. 552 * Only for use by System UI and Settings application. 553 * 554 * @param callback The volume controller callback to receive updates on. 555 * @hide 556 */ 557 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) unregisterRemoteSessionCallback( @onNull RemoteSessionCallback callback)558 public void unregisterRemoteSessionCallback( 559 @NonNull RemoteSessionCallback callback) { 560 Objects.requireNonNull(callback, "callback shouldn't be null"); 561 boolean shouldUnregisterCallback = false; 562 synchronized (mLock) { 563 if (mRemoteSessionCallbacks.remove(callback) != null 564 && mRemoteSessionCallbacks.size() == 0) { 565 shouldUnregisterCallback = true; 566 } 567 } 568 try { 569 if (shouldUnregisterCallback) { 570 mService.unregisterRemoteSessionCallback( 571 mRemoteSessionCallbackStub); 572 } 573 } catch (RemoteException e) { 574 Log.e(TAG, "Failed to unregister remote volume controller callback", e); 575 } 576 } 577 578 /** 579 * Sends a media key event. The receiver will be selected automatically. 580 * 581 * @param keyEvent the key event to send 582 * @param needWakeLock true if a wake lock should be held while sending the key 583 * @hide 584 */ 585 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchMediaKeyEvent(@onNull KeyEvent keyEvent, boolean needWakeLock)586 public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) { 587 dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, needWakeLock); 588 } 589 590 /** 591 * Sends a media key event as system service. The receiver will be selected automatically. 592 * <p> 593 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or 594 * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key 595 * from the hardware devices. 596 * 597 * @param keyEvent the key event to send 598 * @hide 599 */ 600 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchMediaKeyEventAsSystemService(@onNull KeyEvent keyEvent)601 public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) { 602 dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/true, /*needWakeLock=*/false); 603 } 604 dispatchMediaKeyEventInternal(KeyEvent keyEvent, boolean asSystemService, boolean needWakeLock)605 private void dispatchMediaKeyEventInternal(KeyEvent keyEvent, boolean asSystemService, 606 boolean needWakeLock) { 607 Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); 608 try { 609 mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent, 610 needWakeLock); 611 } catch (RemoteException e) { 612 Log.e(TAG, "Failed to send key event.", e); 613 } 614 } 615 616 /** 617 * Sends a media key event as system service to the given session. 618 * <p> 619 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the 620 * foreground activity didn't consume the key from the hardware devices. 621 * 622 * @param keyEvent the key event to send 623 * @param sessionToken the session token to which the key event should be dispatched 624 * @return {@code true} if the event was sent to the session, {@code false} otherwise 625 * @hide 626 */ 627 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchMediaKeyEventToSessionAsSystemService(@onNull KeyEvent keyEvent, @NonNull MediaSession.Token sessionToken)628 public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull KeyEvent keyEvent, 629 @NonNull MediaSession.Token sessionToken) { 630 Objects.requireNonNull(sessionToken, "sessionToken shouldn't be null"); 631 Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); 632 if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { 633 return false; 634 } 635 try { 636 return mService.dispatchMediaKeyEventToSessionAsSystemService( 637 mContext.getPackageName(), keyEvent, sessionToken); 638 } catch (RemoteException e) { 639 Log.e(TAG, "Failed to send key event.", e); 640 } 641 return false; 642 } 643 644 /** 645 * Sends a volume key event. The receiver will be selected automatically. 646 * 647 * @param keyEvent the volume key event to send 648 * @param streamType type of stream 649 * @param musicOnly true if key event should only be sent to music stream 650 * @hide 651 */ 652 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchVolumeKeyEvent(@onNull KeyEvent keyEvent, int streamType, boolean musicOnly)653 public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int streamType, 654 boolean musicOnly) { 655 dispatchVolumeKeyEventInternal(keyEvent, streamType, musicOnly, /*asSystemService=*/false); 656 } 657 658 /** 659 * Dispatches the volume button event as system service to the session. This only effects the 660 * {@link MediaSession.Callback#getCurrentControllerInfo()} and doesn't bypass any permission 661 * check done by the system service. 662 * <p> 663 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or 664 * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key 665 * from the hardware devices. 666 * <p> 667 * Valid stream types include {@link AudioManager.PublicStreamTypes} and 668 * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}. 669 * 670 * @param keyEvent the volume key event to send 671 * @param streamType type of stream 672 * @hide 673 */ 674 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchVolumeKeyEventAsSystemService(@onNull KeyEvent keyEvent, int streamType)675 public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) { 676 dispatchVolumeKeyEventInternal(keyEvent, streamType, /*musicOnly=*/false, 677 /*asSystemService=*/true); 678 } 679 dispatchVolumeKeyEventInternal(@onNull KeyEvent keyEvent, int stream, boolean musicOnly, boolean asSystemService)680 private void dispatchVolumeKeyEventInternal(@NonNull KeyEvent keyEvent, int stream, 681 boolean musicOnly, boolean asSystemService) { 682 Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); 683 try { 684 mService.dispatchVolumeKeyEvent(mContext.getPackageName(), mContext.getOpPackageName(), 685 asSystemService, keyEvent, stream, musicOnly); 686 } catch (RemoteException e) { 687 Log.e(TAG, "Failed to send volume key event.", e); 688 } 689 } 690 691 /** 692 * Dispatches the volume key event as system service to the session. 693 * <p> 694 * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the 695 * foreground activity didn't consume the key from the hardware devices. 696 * 697 * @param keyEvent the volume key event to send 698 * @param sessionToken the session token to which the key event should be dispatched 699 * @hide 700 */ 701 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) dispatchVolumeKeyEventToSessionAsSystemService(@onNull KeyEvent keyEvent, @NonNull MediaSession.Token sessionToken)702 public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull KeyEvent keyEvent, 703 @NonNull MediaSession.Token sessionToken) { 704 Objects.requireNonNull(sessionToken, "sessionToken shouldn't be null"); 705 Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); 706 try { 707 mService.dispatchVolumeKeyEventToSessionAsSystemService(mContext.getPackageName(), 708 mContext.getOpPackageName(), keyEvent, sessionToken); 709 } catch (RemoteException e) { 710 Log.wtf(TAG, "Error calling dispatchVolumeKeyEventAsSystemService", e); 711 } 712 } 713 714 /** 715 * Dispatch an adjust volume request to the system. It will be sent to the 716 * most relevant audio stream or media session. The direction must be one of 717 * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, 718 * {@link AudioManager#ADJUST_SAME}. 719 * 720 * @param suggestedStream The stream to fall back to if there isn't a 721 * relevant stream 722 * @param direction The direction to adjust volume in. 723 * @param flags Any flags to include with the volume change. 724 * @hide 725 */ dispatchAdjustVolume(int suggestedStream, int direction, int flags)726 public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) { 727 try { 728 mService.dispatchAdjustVolume(mContext.getPackageName(), mContext.getOpPackageName(), 729 suggestedStream, direction, flags); 730 } catch (RemoteException e) { 731 Log.e(TAG, "Failed to send adjust volume.", e); 732 } 733 } 734 735 /** 736 * Checks whether the remote user is a trusted app. 737 * <p> 738 * An app is trusted if the app holds the 739 * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission or has an enabled 740 * notification listener. 741 * 742 * @param userInfo The remote user info from either 743 * {@link MediaSession#getCurrentControllerInfo()} or 744 * {@link MediaBrowserService#getCurrentBrowserInfo()}. 745 * @return {@code true} if the remote user is trusted and its package name matches with the UID. 746 * {@code false} otherwise. 747 */ isTrustedForMediaControl(@onNull RemoteUserInfo userInfo)748 public boolean isTrustedForMediaControl(@NonNull RemoteUserInfo userInfo) { 749 Objects.requireNonNull(userInfo, "userInfo shouldn't be null"); 750 if (userInfo.getPackageName() == null) { 751 return false; 752 } 753 try { 754 return mService.isTrusted( 755 userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid()); 756 } catch (RemoteException e) { 757 Log.wtf(TAG, "Cannot communicate with the service.", e); 758 } 759 return false; 760 } 761 762 /** 763 * Check if the global priority session is currently active. This can be 764 * used to decide if media keys should be sent to the session or to the app. 765 * 766 * @hide 767 */ isGlobalPriorityActive()768 public boolean isGlobalPriorityActive() { 769 try { 770 return mService.isGlobalPriorityActive(); 771 } catch (RemoteException e) { 772 Log.e(TAG, "Failed to check if the global priority is active.", e); 773 } 774 return false; 775 } 776 777 /** 778 * Set the volume key long-press listener. While the listener is set, the listener 779 * gets the volume key long-presses instead of changing volume. 780 * 781 * <p>System can only have a single volume key long-press listener. 782 * 783 * @param listener The volume key long-press listener. {@code null} to reset. 784 * @param handler The handler on which the listener should be invoked, or {@code null} 785 * if the listener should be invoked on the calling thread's looper. 786 * @hide 787 */ 788 @SystemApi 789 @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) setOnVolumeKeyLongPressListener( OnVolumeKeyLongPressListener listener, @Nullable Handler handler)790 public void setOnVolumeKeyLongPressListener( 791 OnVolumeKeyLongPressListener listener, @Nullable Handler handler) { 792 synchronized (mLock) { 793 try { 794 if (listener == null) { 795 mOnVolumeKeyLongPressListener = null; 796 mService.setOnVolumeKeyLongPressListener(null); 797 } else { 798 if (handler == null) { 799 handler = new Handler(); 800 } 801 mOnVolumeKeyLongPressListener = 802 new OnVolumeKeyLongPressListenerImpl(listener, handler); 803 mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener); 804 } 805 } catch (RemoteException e) { 806 Log.e(TAG, "Failed to set volume key long press listener", e); 807 } 808 } 809 } 810 811 /** 812 * Set the media key listener. While the listener is set, the listener 813 * gets the media key before any other media sessions but after the global priority session. 814 * If the listener handles the key (i.e. returns {@code true}), 815 * other sessions will not get the event. 816 * 817 * <p>System can only have a single media key listener. 818 * 819 * @param listener The media key listener. {@code null} to reset. 820 * @param handler The handler on which the listener should be invoked, or {@code null} 821 * if the listener should be invoked on the calling thread's looper. 822 * @hide 823 */ 824 @SystemApi 825 @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler)826 public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) { 827 synchronized (mLock) { 828 try { 829 if (listener == null) { 830 mOnMediaKeyListener = null; 831 mService.setOnMediaKeyListener(null); 832 } else { 833 if (handler == null) { 834 handler = new Handler(); 835 } 836 mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler); 837 mService.setOnMediaKeyListener(mOnMediaKeyListener); 838 } 839 } catch (RemoteException e) { 840 Log.e(TAG, "Failed to set media key listener", e); 841 } 842 } 843 } 844 845 /** 846 * Add a {@link OnMediaKeyEventDispatchedListener}. 847 * 848 * @param executor The executor on which the listener should be invoked 849 * @param listener A {@link OnMediaKeyEventDispatchedListener}. 850 * @hide 851 */ 852 @SystemApi 853 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) addOnMediaKeyEventDispatchedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventDispatchedListener listener)854 public void addOnMediaKeyEventDispatchedListener( 855 @NonNull @CallbackExecutor Executor executor, 856 @NonNull OnMediaKeyEventDispatchedListener listener) { 857 Objects.requireNonNull(executor, "executor shouldn't be null"); 858 Objects.requireNonNull(listener, "listener shouldn't be null"); 859 synchronized (mLock) { 860 try { 861 mOnMediaKeyEventDispatchedListeners.put(listener, executor); 862 if (mOnMediaKeyEventDispatchedListeners.size() == 1) { 863 mService.addOnMediaKeyEventDispatchedListener( 864 mOnMediaKeyEventDispatchedListenerStub); 865 } 866 } catch (RemoteException e) { 867 Log.e(TAG, "Failed to set media key listener", e); 868 } 869 } 870 } 871 872 /** 873 * Remove a {@link OnMediaKeyEventDispatchedListener}. 874 * 875 * @param listener A {@link OnMediaKeyEventDispatchedListener}. 876 * @hide 877 */ 878 @SystemApi 879 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) removeOnMediaKeyEventDispatchedListener( @onNull OnMediaKeyEventDispatchedListener listener)880 public void removeOnMediaKeyEventDispatchedListener( 881 @NonNull OnMediaKeyEventDispatchedListener listener) { 882 Objects.requireNonNull(listener, "listener shouldn't be null"); 883 synchronized (mLock) { 884 try { 885 mOnMediaKeyEventDispatchedListeners.remove(listener); 886 if (mOnMediaKeyEventDispatchedListeners.size() == 0) { 887 mService.removeOnMediaKeyEventDispatchedListener( 888 mOnMediaKeyEventDispatchedListenerStub); 889 } 890 } catch (RemoteException e) { 891 Log.e(TAG, "Failed to set media key event dispatched listener", e); 892 } 893 } 894 } 895 896 /** 897 * Add a {@link OnMediaKeyEventSessionChangedListener}. 898 * 899 * @param executor The executor on which the listener should be invoked 900 * @param listener A {@link OnMediaKeyEventSessionChangedListener}. 901 * @hide 902 */ 903 @SystemApi 904 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) addOnMediaKeyEventSessionChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventSessionChangedListener listener)905 public void addOnMediaKeyEventSessionChangedListener( 906 @NonNull @CallbackExecutor Executor executor, 907 @NonNull OnMediaKeyEventSessionChangedListener listener) { 908 Objects.requireNonNull(executor, "executor shouldn't be null"); 909 Objects.requireNonNull(listener, "listener shouldn't be null"); 910 synchronized (mLock) { 911 try { 912 mMediaKeyEventSessionChangedCallbacks.put(listener, executor); 913 executor.execute( 914 () -> listener.onMediaKeyEventSessionChanged( 915 mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession)); 916 if (mMediaKeyEventSessionChangedCallbacks.size() == 1) { 917 mService.addOnMediaKeyEventSessionChangedListener( 918 mOnMediaKeyEventSessionChangedListenerStub); 919 } 920 } catch (RemoteException e) { 921 Log.e(TAG, "Failed to set media key listener", e); 922 } 923 } 924 } 925 926 /** 927 * Remove a {@link OnMediaKeyEventSessionChangedListener}. 928 * 929 * @param listener A {@link OnMediaKeyEventSessionChangedListener}. 930 * @hide 931 */ 932 @SystemApi 933 @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) removeOnMediaKeyEventSessionChangedListener( @onNull OnMediaKeyEventSessionChangedListener listener)934 public void removeOnMediaKeyEventSessionChangedListener( 935 @NonNull OnMediaKeyEventSessionChangedListener listener) { 936 Objects.requireNonNull(listener, "listener shouldn't be null"); 937 synchronized (mLock) { 938 try { 939 mMediaKeyEventSessionChangedCallbacks.remove(listener); 940 if (mMediaKeyEventSessionChangedCallbacks.size() == 0) { 941 mService.removeOnMediaKeyEventSessionChangedListener( 942 mOnMediaKeyEventSessionChangedListenerStub); 943 } 944 } catch (RemoteException e) { 945 Log.e(TAG, "Failed to set media key listener", e); 946 } 947 } 948 } 949 950 /** 951 * Set the component name for the custom 952 * {@link com.android.server.media.MediaKeyDispatcher} class. Set to null to restore to the 953 * custom {@link com.android.server.media.MediaKeyDispatcher} class name retrieved from the 954 * config value. 955 * 956 * @hide 957 */ 958 @VisibleForTesting setCustomMediaKeyDispatcher(@ullable String name)959 public void setCustomMediaKeyDispatcher(@Nullable String name) { 960 try { 961 mService.setCustomMediaKeyDispatcher(name); 962 } catch (RemoteException e) { 963 Log.e(TAG, "Failed to set custom media key dispatcher name", e); 964 } 965 } 966 967 /** 968 * Set the component name for the custom 969 * {@link com.android.server.media.MediaSessionPolicyProvider} class. Set to null to restore to 970 * the custom {@link com.android.server.media.MediaSessionPolicyProvider} class name retrieved 971 * from the config value. 972 * 973 * @hide 974 */ 975 @VisibleForTesting setCustomMediaSessionPolicyProvider(@ullable String name)976 public void setCustomMediaSessionPolicyProvider(@Nullable String name) { 977 try { 978 mService.setCustomMediaSessionPolicyProvider(name); 979 } catch (RemoteException e) { 980 Log.e(TAG, "Failed to set custom session policy provider name", e); 981 } 982 } 983 984 /** 985 * Get the component name for the custom {@link com.android.server.media.MediaKeyDispatcher} 986 * class. 987 * 988 * @hide 989 */ 990 @VisibleForTesting hasCustomMediaKeyDispatcher(@onNull String componentName)991 public boolean hasCustomMediaKeyDispatcher(@NonNull String componentName) { 992 Objects.requireNonNull(componentName, "componentName shouldn't be null"); 993 try { 994 return mService.hasCustomMediaKeyDispatcher(componentName); 995 } catch (RemoteException e) { 996 Log.e(TAG, "Failed to check if custom media key dispatcher with given component" 997 + " name exists", e); 998 } 999 return false; 1000 } 1001 1002 /** 1003 * Get the component name for the custom 1004 * {@link com.android.server.media.MediaSessionPolicyProvider} class. 1005 * 1006 * @hide 1007 */ 1008 @VisibleForTesting hasCustomMediaSessionPolicyProvider(@onNull String componentName)1009 public boolean hasCustomMediaSessionPolicyProvider(@NonNull String componentName) { 1010 Objects.requireNonNull(componentName, "componentName shouldn't be null"); 1011 try { 1012 return mService.hasCustomMediaSessionPolicyProvider(componentName); 1013 } catch (RemoteException e) { 1014 Log.e(TAG, "Failed to check if custom media session policy provider with given" 1015 + " component name exists", e); 1016 } 1017 return false; 1018 } 1019 1020 /** 1021 * Get session policies of the specified {@link MediaSession.Token}. 1022 * 1023 * @hide 1024 */ 1025 @Nullable getSessionPolicies(@onNull MediaSession.Token token)1026 public int getSessionPolicies(@NonNull MediaSession.Token token) { 1027 try { 1028 return mService.getSessionPolicies(token); 1029 } catch (RemoteException e) { 1030 Log.e(TAG, "Failed to get session policies", e); 1031 } 1032 return 0; 1033 } 1034 1035 /** 1036 * Set new session policies to the specified {@link MediaSession.Token}. 1037 * 1038 * @hide 1039 */ setSessionPolicies(@onNull MediaSession.Token token, @Nullable int policies)1040 public void setSessionPolicies(@NonNull MediaSession.Token token, @Nullable int policies) { 1041 try { 1042 mService.setSessionPolicies(token, policies); 1043 } catch (RemoteException e) { 1044 Log.e(TAG, "Failed to set session policies", e); 1045 } 1046 } 1047 1048 /** 1049 * Listens for changes to the list of active sessions. This can be added 1050 * using {@link #addOnActiveSessionsChangedListener}. 1051 */ 1052 public interface OnActiveSessionsChangedListener { onActiveSessionsChanged(@ullable List<MediaController> controllers)1053 public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); 1054 } 1055 1056 /** 1057 * This API is not generally intended for third party application developers. 1058 * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 1059 * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session 1060 * Library</a> for consistent behavior across all devices. 1061 * <p> 1062 * Listens for changes to the {@link #getSession2Tokens()}. This can be added 1063 * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}. 1064 */ 1065 public interface OnSession2TokensChangedListener { 1066 /** 1067 * Called when the {@link #getSession2Tokens()} is changed. 1068 * 1069 * @param tokens list of {@link Session2Token} 1070 */ onSession2TokensChanged(@onNull List<Session2Token> tokens)1071 void onSession2TokensChanged(@NonNull List<Session2Token> tokens); 1072 } 1073 1074 /** 1075 * Listens the volume key long-presses. 1076 * @hide 1077 */ 1078 @SystemApi 1079 public interface OnVolumeKeyLongPressListener { 1080 /** 1081 * Called when the volume key is long-pressed. 1082 * <p>This will be called for both down and up events. 1083 */ onVolumeKeyLongPress(KeyEvent event)1084 void onVolumeKeyLongPress(KeyEvent event); 1085 } 1086 1087 /** 1088 * Listens the media key. 1089 * @hide 1090 */ 1091 @SystemApi 1092 public interface OnMediaKeyListener { 1093 /** 1094 * Called when the media key is pressed. 1095 * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with 1096 * repeat count zero), it must also comsume all following key events. 1097 * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP). 1098 * <p>If it takes more than 1s to return, the key event will be sent to 1099 * other media sessions. 1100 */ onMediaKey(KeyEvent event)1101 boolean onMediaKey(KeyEvent event); 1102 } 1103 1104 /** 1105 * Listener to be called when the media session service dispatches a media key event. 1106 * @hide 1107 */ 1108 @SystemApi 1109 public interface OnMediaKeyEventDispatchedListener { 1110 /** 1111 * Called when a media key event is dispatched through the media session service. The 1112 * session token can be {@link null} if the framework has sent the media key event to the 1113 * media button receiver to revive the media app's playback after the corresponding session 1114 * is released. 1115 * 1116 * @param event Dispatched media key event. 1117 * @param packageName The package name 1118 * @param sessionToken The media session's token. Can be {@code null}. 1119 */ onMediaKeyEventDispatched(@onNull KeyEvent event, @NonNull String packageName, @Nullable MediaSession.Token sessionToken)1120 void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName, 1121 @Nullable MediaSession.Token sessionToken); 1122 } 1123 1124 /** 1125 * Listener to receive changes in the media key event session, which would receive a media key 1126 * event unless specified. 1127 * @hide 1128 */ 1129 @SystemApi 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 {@link 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.Flags int flags)1166 void onVolumeChanged(@NonNull MediaSession.Token sessionToken, 1167 @AudioManager.Flags 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