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