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.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SystemApi; 23 import android.app.Activity; 24 import android.app.PendingIntent; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.media.AudioAttributes; 30 import android.media.MediaDescription; 31 import android.media.MediaMetadata; 32 import android.media.Rating; 33 import android.media.VolumeProvider; 34 import android.media.session.MediaSessionManager.RemoteUserInfo; 35 import android.net.Uri; 36 import android.os.BadParcelableException; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.os.Parcel; 44 import android.os.Parcelable; 45 import android.os.Process; 46 import android.os.RemoteException; 47 import android.os.ResultReceiver; 48 import android.service.media.MediaBrowserService; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.util.Pair; 52 import android.view.KeyEvent; 53 import android.view.ViewConfiguration; 54 55 import java.lang.annotation.Retention; 56 import java.lang.annotation.RetentionPolicy; 57 import java.lang.ref.WeakReference; 58 import java.util.List; 59 import java.util.Objects; 60 61 /** 62 * Allows interaction with media controllers, volume keys, media buttons, and 63 * transport controls. 64 * <p> 65 * A MediaSession should be created when an app wants to publish media playback 66 * information or handle media keys. In general an app only needs one session 67 * for all playback, though multiple sessions can be created to provide finer 68 * grain controls of media. 69 * <p> 70 * Once a session is created the owner of the session may pass its 71 * {@link #getSessionToken() session token} to other processes to allow them to 72 * create a {@link MediaController} to interact with the session. 73 * <p> 74 * To receive commands, media keys, and other events a {@link Callback} must be 75 * set with {@link #setCallback(Callback)} and {@link #setActive(boolean) 76 * setActive(true)} must be called. 77 * <p> 78 * When an app is finished performing playback it must call {@link #release()} 79 * to clean up the session and notify any controllers. 80 * <p> 81 * MediaSession objects are thread safe. 82 */ 83 public final class MediaSession { 84 static final String TAG = "MediaSession"; 85 86 /** 87 * Set this flag on the session to indicate that it can handle media button 88 * events. 89 * @deprecated This flag is no longer used. All media sessions are expected to handle media 90 * button events now. 91 */ 92 @Deprecated 93 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 94 95 /** 96 * Set this flag on the session to indicate that it handles transport 97 * control commands through its {@link Callback}. 98 * @deprecated This flag is no longer used. All media sessions are expected to handle transport 99 * controls now. 100 */ 101 @Deprecated 102 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 103 104 /** 105 * System only flag for a session that needs to have priority over all other 106 * sessions. This flag ensures this session will receive media button events 107 * regardless of the current ordering in the system. 108 * If there are two or more sessions with this flag, the last session that sets this flag 109 * will be the global priority session. 110 * 111 * @hide 112 */ 113 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 114 public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; 115 116 /** 117 * @hide 118 */ 119 public static final int INVALID_UID = -1; 120 121 /** 122 * @hide 123 */ 124 public static final int INVALID_PID = -1; 125 126 /** @hide */ 127 @Retention(RetentionPolicy.SOURCE) 128 @IntDef(flag = true, value = { 129 FLAG_HANDLES_MEDIA_BUTTONS, 130 FLAG_HANDLES_TRANSPORT_CONTROLS, 131 FLAG_EXCLUSIVE_GLOBAL_PRIORITY }) 132 public @interface SessionFlags { } 133 134 private final Object mLock = new Object(); 135 private Context mContext; 136 private final int mMaxBitmapSize; 137 138 private final Token mSessionToken; 139 private final MediaController mController; 140 private final ISession mBinder; 141 private final CallbackStub mCbStub; 142 143 // Do not change the name of mCallback. Support lib accesses this by using reflection. 144 @UnsupportedAppUsage 145 private CallbackMessageHandler mCallback; 146 private VolumeProvider mVolumeProvider; 147 private PlaybackState mPlaybackState; 148 149 private boolean mActive = false; 150 151 /** 152 * Creates a new session. The session will automatically be registered with 153 * the system but will not be published until {@link #setActive(boolean) 154 * setActive(true)} is called. You must call {@link #release()} when 155 * finished with the session. 156 * <p> 157 * Note that {@link RuntimeException} will be thrown if an app creates too many sessions. 158 * 159 * @param context The context to use to create the session. 160 * @param tag A short name for debugging purposes. 161 */ MediaSession(@onNull Context context, @NonNull String tag)162 public MediaSession(@NonNull Context context, @NonNull String tag) { 163 this(context, tag, null); 164 } 165 166 /** 167 * Creates a new session. The session will automatically be registered with 168 * the system but will not be published until {@link #setActive(boolean) 169 * setActive(true)} is called. You must call {@link #release()} when 170 * finished with the session. 171 * <p> 172 * The {@code sessionInfo} can include additional unchanging information about this session. 173 * For example, it can include the version of the application, or the list of the custom 174 * commands that this session supports. 175 * <p> 176 * Note that {@link RuntimeException} will be thrown if an app creates too many sessions. 177 * 178 * @param context The context to use to create the session. 179 * @param tag A short name for debugging purposes. 180 * @param sessionInfo A bundle for additional information about this session. 181 * Controllers can get this information by calling 182 * {@link MediaController#getSessionInfo()}. 183 * An {@link IllegalArgumentException} will be thrown if this contains 184 * any non-framework Parcelable objects. 185 */ MediaSession(@onNull Context context, @NonNull String tag, @Nullable Bundle sessionInfo)186 public MediaSession(@NonNull Context context, @NonNull String tag, 187 @Nullable Bundle sessionInfo) { 188 if (context == null) { 189 throw new IllegalArgumentException("context cannot be null."); 190 } 191 if (TextUtils.isEmpty(tag)) { 192 throw new IllegalArgumentException("tag cannot be null or empty"); 193 } 194 if (hasCustomParcelable(sessionInfo)) { 195 throw new IllegalArgumentException("sessionInfo shouldn't contain any custom " 196 + "parcelables"); 197 } 198 199 mContext = context; 200 mMaxBitmapSize = context.getResources().getDimensionPixelSize( 201 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize); 202 mCbStub = new CallbackStub(this); 203 MediaSessionManager manager = (MediaSessionManager) context 204 .getSystemService(Context.MEDIA_SESSION_SERVICE); 205 try { 206 mBinder = manager.createSession(mCbStub, tag, sessionInfo); 207 mSessionToken = new Token(Process.myUid(), mBinder.getController()); 208 mController = new MediaController(context, mSessionToken); 209 } catch (RemoteException e) { 210 throw new RuntimeException("Remote error creating session.", e); 211 } 212 } 213 214 /** 215 * Set the callback to receive updates for the MediaSession. This includes 216 * media button events and transport controls. The caller's thread will be 217 * used to post updates. 218 * <p> 219 * Set the callback to null to stop receiving updates. 220 * 221 * @param callback The callback object 222 */ setCallback(@ullable Callback callback)223 public void setCallback(@Nullable Callback callback) { 224 setCallback(callback, null); 225 } 226 227 /** 228 * Set the callback to receive updates for the MediaSession. This includes 229 * media button events and transport controls. 230 * <p> 231 * Set the callback to null to stop receiving updates. 232 * 233 * @param callback The callback to receive updates on. 234 * @param handler The handler that events should be posted on. 235 */ setCallback(@ullable Callback callback, @Nullable Handler handler)236 public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { 237 synchronized (mLock) { 238 if (mCallback != null) { 239 // We're updating the callback, clear the session from the old one. 240 mCallback.mCallback.mSession = null; 241 mCallback.removeCallbacksAndMessages(null); 242 } 243 if (callback == null) { 244 mCallback = null; 245 return; 246 } 247 if (handler == null) { 248 handler = new Handler(); 249 } 250 callback.mSession = this; 251 CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), 252 callback); 253 mCallback = msgHandler; 254 } 255 } 256 257 /** 258 * Set an intent for launching UI for this Session. This can be used as a 259 * quick link to an ongoing media screen. The intent should be for an 260 * activity that may be started using {@link Activity#startActivity(Intent)}. 261 * 262 * @param pi The intent to launch to show UI for this Session. 263 */ setSessionActivity(@ullable PendingIntent pi)264 public void setSessionActivity(@Nullable PendingIntent pi) { 265 try { 266 mBinder.setLaunchPendingIntent(pi); 267 } catch (RemoteException e) { 268 Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e); 269 } 270 } 271 272 /** 273 * Set a pending intent for your media button receiver to allow restarting 274 * playback after the session has been stopped. If your app is started in 275 * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via 276 * the pending intent. 277 * <p> 278 * The pending intent is recommended to be explicit to follow the security recommendation of 279 * {@link PendingIntent#getActivity}. 280 * 281 * @param mbr The {@link PendingIntent} to send the media button event to. 282 * @see PendingIntent#getActivity 283 * 284 * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead. 285 */ 286 @Deprecated setMediaButtonReceiver(@ullable PendingIntent mbr)287 public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { 288 try { 289 mBinder.setMediaButtonReceiver(mbr, mContext.getPackageName()); 290 } catch (RemoteException e) { 291 Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); 292 } 293 } 294 295 /** 296 * Set the component name of the manifest-declared {@link android.content.BroadcastReceiver} 297 * class that should receive media buttons. This allows restarting playback after the session 298 * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} 299 * intent will be sent to the broadcast receiver. 300 * <p> 301 * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package 302 * as the context that was given when creating {@link MediaSession}. 303 * 304 * @param broadcastReceiver the component name of the BroadcastReceiver class 305 */ setMediaButtonBroadcastReceiver(@ullable ComponentName broadcastReceiver)306 public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) { 307 try { 308 if (broadcastReceiver != null) { 309 if (!TextUtils.equals(broadcastReceiver.getPackageName(), 310 mContext.getPackageName())) { 311 throw new IllegalArgumentException("broadcastReceiver should belong to the same" 312 + " package as the context given when creating MediaSession."); 313 } 314 } 315 mBinder.setMediaButtonBroadcastReceiver(broadcastReceiver); 316 } catch (RemoteException e) { 317 Log.wtf(TAG, "Failure in setMediaButtonBroadcastReceiver.", e); 318 } 319 } 320 321 /** 322 * Set any flags for the session. 323 * 324 * @param flags The flags to set for this session. 325 */ setFlags(@essionFlags int flags)326 public void setFlags(@SessionFlags int flags) { 327 try { 328 mBinder.setFlags(flags); 329 } catch (RemoteException e) { 330 Log.wtf(TAG, "Failure in setFlags.", e); 331 } 332 } 333 334 /** 335 * Set the attributes for this session's audio. This will affect the 336 * system's volume handling for this session. If 337 * {@link #setPlaybackToRemote} was previously called it will stop receiving 338 * volume commands and the system will begin sending volume changes to the 339 * appropriate stream. 340 * <p> 341 * By default sessions use attributes for media. 342 * 343 * @param attributes The {@link AudioAttributes} for this session's audio. 344 */ setPlaybackToLocal(AudioAttributes attributes)345 public void setPlaybackToLocal(AudioAttributes attributes) { 346 if (attributes == null) { 347 throw new IllegalArgumentException("Attributes cannot be null for local playback."); 348 } 349 try { 350 mBinder.setPlaybackToLocal(attributes); 351 } catch (RemoteException e) { 352 Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); 353 } 354 } 355 356 /** 357 * Configure this session to use remote volume handling. This must be called 358 * to receive volume button events, otherwise the system will adjust the 359 * appropriate stream volume for this session. If 360 * {@link #setPlaybackToLocal} was previously called the system will stop 361 * handling volume changes for this session and pass them to the volume 362 * provider instead. 363 * 364 * @param volumeProvider The provider that will handle volume changes. May 365 * not be null. 366 */ setPlaybackToRemote(@onNull VolumeProvider volumeProvider)367 public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) { 368 if (volumeProvider == null) { 369 throw new IllegalArgumentException("volumeProvider may not be null!"); 370 } 371 synchronized (mLock) { 372 mVolumeProvider = volumeProvider; 373 } 374 volumeProvider.setCallback(new VolumeProvider.Callback() { 375 @Override 376 public void onVolumeChanged(VolumeProvider volumeProvider) { 377 notifyRemoteVolumeChanged(volumeProvider); 378 } 379 }); 380 381 try { 382 mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(), 383 volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId()); 384 mBinder.setCurrentVolume(volumeProvider.getCurrentVolume()); 385 } catch (RemoteException e) { 386 Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); 387 } 388 } 389 390 /** 391 * Set if this session is currently active and ready to receive commands. If 392 * set to false your session's controller may not be discoverable. You must 393 * set the session to active before it can start receiving media button 394 * events or transport commands. 395 * 396 * @param active Whether this session is active or not. 397 */ setActive(boolean active)398 public void setActive(boolean active) { 399 if (mActive == active) { 400 return; 401 } 402 try { 403 mBinder.setActive(active); 404 mActive = active; 405 } catch (RemoteException e) { 406 Log.wtf(TAG, "Failure in setActive.", e); 407 } 408 } 409 410 /** 411 * Get the current active state of this session. 412 * 413 * @return True if the session is active, false otherwise. 414 */ isActive()415 public boolean isActive() { 416 return mActive; 417 } 418 419 /** 420 * Send a proprietary event to all MediaControllers listening to this 421 * Session. It's up to the Controller/Session owner to determine the meaning 422 * of any events. 423 * 424 * @param event The name of the event to send 425 * @param extras Any extras included with the event 426 */ sendSessionEvent(@onNull String event, @Nullable Bundle extras)427 public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) { 428 if (TextUtils.isEmpty(event)) { 429 throw new IllegalArgumentException("event cannot be null or empty"); 430 } 431 try { 432 mBinder.sendEvent(event, extras); 433 } catch (RemoteException e) { 434 Log.wtf(TAG, "Error sending event", e); 435 } 436 } 437 438 /** 439 * This must be called when an app has finished performing playback. If 440 * playback is expected to start again shortly the session can be left open, 441 * but it must be released if your activity or service is being destroyed. 442 */ release()443 public void release() { 444 try { 445 mBinder.destroySession(); 446 } catch (RemoteException e) { 447 Log.wtf(TAG, "Error releasing session: ", e); 448 } 449 } 450 451 /** 452 * Retrieve a token object that can be used by apps to create a 453 * {@link MediaController} for interacting with this session. The owner of 454 * the session is responsible for deciding how to distribute these tokens. 455 * 456 * @return A token that can be used to create a MediaController for this 457 * session 458 */ getSessionToken()459 public @NonNull Token getSessionToken() { 460 return mSessionToken; 461 } 462 463 /** 464 * Get a controller for this session. This is a convenience method to avoid 465 * having to cache your own controller in process. 466 * 467 * @return A controller for this session. 468 */ getController()469 public @NonNull MediaController getController() { 470 return mController; 471 } 472 473 /** 474 * Update the current playback state. 475 * 476 * @param state The current state of playback 477 */ setPlaybackState(@ullable PlaybackState state)478 public void setPlaybackState(@Nullable PlaybackState state) { 479 mPlaybackState = state; 480 try { 481 mBinder.setPlaybackState(state); 482 } catch (RemoteException e) { 483 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 484 } 485 } 486 487 /** 488 * Update the current metadata. New metadata can be created using 489 * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to 490 * the size of the bitmap to replace large bitmaps with a scaled down copy. 491 * 492 * @param metadata The new metadata 493 * @see android.media.MediaMetadata.Builder#putBitmap 494 */ setMetadata(@ullable MediaMetadata metadata)495 public void setMetadata(@Nullable MediaMetadata metadata) { 496 long duration = -1; 497 int fields = 0; 498 MediaDescription description = null; 499 if (metadata != null) { 500 metadata = new MediaMetadata.Builder(metadata) 501 .setBitmapDimensionLimit(mMaxBitmapSize) 502 .build(); 503 if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 504 duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 505 } 506 fields = metadata.size(); 507 description = metadata.getDescription(); 508 } 509 String metadataDescription = "size=" + fields + ", description=" + description; 510 511 try { 512 mBinder.setMetadata(metadata, duration, metadataDescription); 513 } catch (RemoteException e) { 514 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 515 } 516 } 517 518 /** 519 * Update the list of items in the play queue. It is an ordered list and 520 * should contain the current item, and previous or upcoming items if they 521 * exist. Specify null if there is no current play queue. 522 * <p> 523 * The queue should be of reasonable size. If the play queue is unbounded 524 * within your app, it is better to send a reasonable amount in a sliding 525 * window instead. 526 * 527 * @param queue A list of items in the play queue. 528 */ setQueue(@ullable List<QueueItem> queue)529 public void setQueue(@Nullable List<QueueItem> queue) { 530 try { 531 if (queue == null) { 532 mBinder.resetQueue(); 533 } else { 534 IBinder binder = mBinder.getBinderForSetQueue(); 535 ParcelableListBinder.send(binder, queue); 536 } 537 } catch (RemoteException e) { 538 Log.wtf("Dead object in setQueue.", e); 539 } 540 } 541 542 /** 543 * Set the title of the play queue. The UI should display this title along 544 * with the play queue itself. 545 * e.g. "Play Queue", "Now Playing", or an album name. 546 * 547 * @param title The title of the play queue. 548 */ setQueueTitle(@ullable CharSequence title)549 public void setQueueTitle(@Nullable CharSequence title) { 550 try { 551 mBinder.setQueueTitle(title); 552 } catch (RemoteException e) { 553 Log.wtf("Dead object in setQueueTitle.", e); 554 } 555 } 556 557 /** 558 * Set the style of rating used by this session. Apps trying to set the 559 * rating should use this style. Must be one of the following: 560 * <ul> 561 * <li>{@link Rating#RATING_NONE}</li> 562 * <li>{@link Rating#RATING_3_STARS}</li> 563 * <li>{@link Rating#RATING_4_STARS}</li> 564 * <li>{@link Rating#RATING_5_STARS}</li> 565 * <li>{@link Rating#RATING_HEART}</li> 566 * <li>{@link Rating#RATING_PERCENTAGE}</li> 567 * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li> 568 * </ul> 569 */ setRatingType(@ating.Style int type)570 public void setRatingType(@Rating.Style int type) { 571 try { 572 mBinder.setRatingType(type); 573 } catch (RemoteException e) { 574 Log.e(TAG, "Error in setRatingType.", e); 575 } 576 } 577 578 /** 579 * Set some extras that can be associated with the {@link MediaSession}. No assumptions should 580 * be made as to how a {@link MediaController} will handle these extras. 581 * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. 582 * 583 * @param extras The extras associated with the {@link MediaSession}. 584 */ setExtras(@ullable Bundle extras)585 public void setExtras(@Nullable Bundle extras) { 586 try { 587 mBinder.setExtras(extras); 588 } catch (RemoteException e) { 589 Log.wtf("Dead object in setExtras.", e); 590 } 591 } 592 593 /** 594 * Gets the controller information who sent the current request. 595 * <p> 596 * Note: This is only valid while in a request callback, such as {@link Callback#onPlay}. 597 * 598 * @throws IllegalStateException If this method is called outside of {@link Callback} methods. 599 * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) 600 */ getCurrentControllerInfo()601 public final @NonNull RemoteUserInfo getCurrentControllerInfo() { 602 if (mCallback == null || mCallback.mCurrentControllerInfo == null) { 603 throw new IllegalStateException( 604 "This should be called inside of MediaSession.Callback methods"); 605 } 606 return mCallback.mCurrentControllerInfo; 607 } 608 609 /** 610 * Notify the system that the remote volume changed. 611 * 612 * @param provider The provider that is handling volume changes. 613 * @hide 614 */ notifyRemoteVolumeChanged(VolumeProvider provider)615 public void notifyRemoteVolumeChanged(VolumeProvider provider) { 616 synchronized (mLock) { 617 if (provider == null || provider != mVolumeProvider) { 618 Log.w(TAG, "Received update from stale volume provider"); 619 return; 620 } 621 } 622 try { 623 mBinder.setCurrentVolume(provider.getCurrentVolume()); 624 } catch (RemoteException e) { 625 Log.e(TAG, "Error in notifyVolumeChanged", e); 626 } 627 } 628 629 /** 630 * Returns the name of the package that sent the last media button, transport control, or 631 * command from controllers and the system. This is only valid while in a request callback, such 632 * as {@link Callback#onPlay}. 633 * 634 * @hide 635 */ 636 @UnsupportedAppUsage getCallingPackage()637 public String getCallingPackage() { 638 if (mCallback != null && mCallback.mCurrentControllerInfo != null) { 639 return mCallback.mCurrentControllerInfo.getPackageName(); 640 } 641 return null; 642 } 643 644 /** 645 * Returns whether the given bundle includes non-framework Parcelables. 646 */ hasCustomParcelable(@ullable Bundle bundle)647 static boolean hasCustomParcelable(@Nullable Bundle bundle) { 648 if (bundle == null) { 649 return false; 650 } 651 652 // Try writing the bundle to parcel, and read it with framework classloader. 653 Parcel parcel = null; 654 try { 655 parcel = Parcel.obtain(); 656 parcel.writeBundle(bundle); 657 parcel.setDataPosition(0); 658 Bundle out = parcel.readBundle(null); 659 660 // Calling Bundle#size() will trigger Bundle#unparcel(). 661 out.size(); 662 } catch (BadParcelableException e) { 663 Log.d(TAG, "Custom parcelable in bundle.", e); 664 return true; 665 } finally { 666 if (parcel != null) { 667 parcel.recycle(); 668 } 669 } 670 return false; 671 } 672 dispatchPrepare(RemoteUserInfo caller)673 void dispatchPrepare(RemoteUserInfo caller) { 674 postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null); 675 } 676 dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras)677 void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { 678 postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras); 679 } 680 dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras)681 void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) { 682 postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras); 683 } 684 dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras)685 void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { 686 postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras); 687 } 688 dispatchPlay(RemoteUserInfo caller)689 void dispatchPlay(RemoteUserInfo caller) { 690 postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null); 691 } 692 dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras)693 void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { 694 postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); 695 } 696 dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras)697 void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) { 698 postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras); 699 } 700 dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras)701 void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { 702 postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras); 703 } 704 dispatchSkipToItem(RemoteUserInfo caller, long id)705 void dispatchSkipToItem(RemoteUserInfo caller, long id) { 706 postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null); 707 } 708 dispatchPause(RemoteUserInfo caller)709 void dispatchPause(RemoteUserInfo caller) { 710 postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null); 711 } 712 dispatchStop(RemoteUserInfo caller)713 void dispatchStop(RemoteUserInfo caller) { 714 postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null); 715 } 716 dispatchNext(RemoteUserInfo caller)717 void dispatchNext(RemoteUserInfo caller) { 718 postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null); 719 } 720 dispatchPrevious(RemoteUserInfo caller)721 void dispatchPrevious(RemoteUserInfo caller) { 722 postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null); 723 } 724 dispatchFastForward(RemoteUserInfo caller)725 void dispatchFastForward(RemoteUserInfo caller) { 726 postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null); 727 } 728 dispatchRewind(RemoteUserInfo caller)729 void dispatchRewind(RemoteUserInfo caller) { 730 postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null); 731 } 732 dispatchSeekTo(RemoteUserInfo caller, long pos)733 void dispatchSeekTo(RemoteUserInfo caller, long pos) { 734 postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null); 735 } 736 dispatchRate(RemoteUserInfo caller, Rating rating)737 void dispatchRate(RemoteUserInfo caller, Rating rating) { 738 postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null); 739 } 740 dispatchSetPlaybackSpeed(RemoteUserInfo caller, float speed)741 void dispatchSetPlaybackSpeed(RemoteUserInfo caller, float speed) { 742 postToCallback(caller, CallbackMessageHandler.MSG_SET_PLAYBACK_SPEED, speed, null); 743 } 744 dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args)745 void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) { 746 postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); 747 } 748 dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent)749 void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) { 750 postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null); 751 } 752 dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent, long delay)753 void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent, 754 long delay) { 755 postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT, 756 mediaButtonIntent, null, delay); 757 } 758 dispatchAdjustVolume(RemoteUserInfo caller, int direction)759 void dispatchAdjustVolume(RemoteUserInfo caller, int direction) { 760 postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null); 761 } 762 dispatchSetVolumeTo(RemoteUserInfo caller, int volume)763 void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) { 764 postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null); 765 } 766 dispatchCommand(RemoteUserInfo caller, String command, Bundle args, ResultReceiver resultCb)767 void dispatchCommand(RemoteUserInfo caller, String command, Bundle args, 768 ResultReceiver resultCb) { 769 Command cmd = new Command(command, args, resultCb); 770 postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null); 771 } 772 postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data)773 void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) { 774 postToCallbackDelayed(caller, what, obj, data, 0); 775 } 776 postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, long delay)777 void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, 778 long delay) { 779 synchronized (mLock) { 780 if (mCallback != null) { 781 mCallback.post(caller, what, obj, data, delay); 782 } 783 } 784 } 785 786 /** 787 * Represents an ongoing session. This may be passed to apps by the session 788 * owner to allow them to create a {@link MediaController} to communicate with 789 * the session. 790 */ 791 public static final class Token implements Parcelable { 792 793 private final int mUid; 794 private final ISessionController mBinder; 795 796 /** 797 * @hide 798 */ Token(int uid, ISessionController binder)799 public Token(int uid, ISessionController binder) { 800 mUid = uid; 801 mBinder = binder; 802 } 803 Token(Parcel in)804 Token(Parcel in) { 805 mUid = in.readInt(); 806 mBinder = ISessionController.Stub.asInterface(in.readStrongBinder()); 807 } 808 809 @Override describeContents()810 public int describeContents() { 811 return 0; 812 } 813 814 @Override writeToParcel(Parcel dest, int flags)815 public void writeToParcel(Parcel dest, int flags) { 816 dest.writeInt(mUid); 817 dest.writeStrongBinder(mBinder.asBinder()); 818 } 819 820 @Override hashCode()821 public int hashCode() { 822 final int prime = 31; 823 int result = mUid; 824 result = prime * result + (mBinder == null ? 0 : mBinder.asBinder().hashCode()); 825 return result; 826 } 827 828 @Override equals(Object obj)829 public boolean equals(Object obj) { 830 if (this == obj) 831 return true; 832 if (obj == null) 833 return false; 834 if (getClass() != obj.getClass()) 835 return false; 836 Token other = (Token) obj; 837 if (mUid != other.mUid) { 838 return false; 839 } 840 if (mBinder == null || other.mBinder == null) { 841 return mBinder == other.mBinder; 842 } 843 return Objects.equals(mBinder.asBinder(), other.mBinder.asBinder()); 844 } 845 846 /** 847 * Gets the UID of the application that created the media session. 848 * @hide 849 */ 850 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) getUid()851 public int getUid() { 852 return mUid; 853 } 854 855 /** 856 * Gets the controller binder in this token. 857 * @hide 858 */ getBinder()859 public ISessionController getBinder() { 860 return mBinder; 861 } 862 863 public static final @android.annotation.NonNull Parcelable.Creator<Token> CREATOR = 864 new Parcelable.Creator<Token>() { 865 @Override 866 public Token createFromParcel(Parcel in) { 867 return new Token(in); 868 } 869 870 @Override 871 public Token[] newArray(int size) { 872 return new Token[size]; 873 } 874 }; 875 } 876 877 /** 878 * Receives media buttons, transport controls, and commands from controllers 879 * and the system. A callback may be set using {@link #setCallback}. 880 */ 881 public abstract static class Callback { 882 883 private MediaSession mSession; 884 private CallbackMessageHandler mHandler; 885 private boolean mMediaPlayPauseKeyPending; 886 Callback()887 public Callback() { 888 } 889 890 /** 891 * Called when a controller has sent a command to this session. 892 * The owner of the session may handle custom commands but is not 893 * required to. 894 * 895 * @param command The command name. 896 * @param args Optional parameters for the command, may be null. 897 * @param cb A result receiver to which a result may be sent by the command, may be null. 898 */ onCommand(@onNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb)899 public void onCommand(@NonNull String command, @Nullable Bundle args, 900 @Nullable ResultReceiver cb) { 901 } 902 903 /** 904 * Called when a media button is pressed and this session has the 905 * highest priority or a controller sends a media button event to the 906 * session. The default behavior will call the relevant method if the 907 * action for it was set. 908 * <p> 909 * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a 910 * KeyEvent in {@link Intent#EXTRA_KEY_EVENT} 911 * 912 * @param mediaButtonIntent an intent containing the KeyEvent as an 913 * extra 914 * @return True if the event was handled, false otherwise. 915 */ onMediaButtonEvent(@onNull Intent mediaButtonIntent)916 public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { 917 if (mSession != null && mHandler != null 918 && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) { 919 KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 920 if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) { 921 PlaybackState state = mSession.mPlaybackState; 922 long validActions = state == null ? 0 : state.getActions(); 923 switch (ke.getKeyCode()) { 924 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 925 case KeyEvent.KEYCODE_HEADSETHOOK: 926 if (ke.getRepeatCount() > 0) { 927 // Consider long-press as a single tap. 928 handleMediaPlayPauseKeySingleTapIfPending(); 929 } else if (mMediaPlayPauseKeyPending) { 930 // Consider double tap as the next. 931 mHandler.removeMessages(CallbackMessageHandler 932 .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); 933 mMediaPlayPauseKeyPending = false; 934 if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { 935 onSkipToNext(); 936 } 937 } else { 938 mMediaPlayPauseKeyPending = true; 939 mSession.dispatchMediaButtonDelayed( 940 mSession.getCurrentControllerInfo(), 941 mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout()); 942 } 943 return true; 944 default: 945 // If another key is pressed within double tap timeout, consider the 946 // pending play/pause as a single tap to handle media keys in order. 947 handleMediaPlayPauseKeySingleTapIfPending(); 948 break; 949 } 950 951 switch (ke.getKeyCode()) { 952 case KeyEvent.KEYCODE_MEDIA_PLAY: 953 if ((validActions & PlaybackState.ACTION_PLAY) != 0) { 954 onPlay(); 955 return true; 956 } 957 break; 958 case KeyEvent.KEYCODE_MEDIA_PAUSE: 959 if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { 960 onPause(); 961 return true; 962 } 963 break; 964 case KeyEvent.KEYCODE_MEDIA_NEXT: 965 if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { 966 onSkipToNext(); 967 return true; 968 } 969 break; 970 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 971 if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { 972 onSkipToPrevious(); 973 return true; 974 } 975 break; 976 case KeyEvent.KEYCODE_MEDIA_STOP: 977 if ((validActions & PlaybackState.ACTION_STOP) != 0) { 978 onStop(); 979 return true; 980 } 981 break; 982 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 983 if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { 984 onFastForward(); 985 return true; 986 } 987 break; 988 case KeyEvent.KEYCODE_MEDIA_REWIND: 989 if ((validActions & PlaybackState.ACTION_REWIND) != 0) { 990 onRewind(); 991 return true; 992 } 993 break; 994 } 995 } 996 } 997 return false; 998 } 999 handleMediaPlayPauseKeySingleTapIfPending()1000 private void handleMediaPlayPauseKeySingleTapIfPending() { 1001 if (!mMediaPlayPauseKeyPending) { 1002 return; 1003 } 1004 mMediaPlayPauseKeyPending = false; 1005 mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); 1006 PlaybackState state = mSession.mPlaybackState; 1007 long validActions = state == null ? 0 : state.getActions(); 1008 boolean isPlaying = state != null 1009 && state.getState() == PlaybackState.STATE_PLAYING; 1010 boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 1011 | PlaybackState.ACTION_PLAY)) != 0; 1012 boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 1013 | PlaybackState.ACTION_PAUSE)) != 0; 1014 if (isPlaying && canPause) { 1015 onPause(); 1016 } else if (!isPlaying && canPlay) { 1017 onPlay(); 1018 } 1019 } 1020 1021 /** 1022 * Override to handle requests to prepare playback. During the preparation, a session should 1023 * not hold audio focus in order to allow other sessions play seamlessly. The state of 1024 * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is 1025 * done. 1026 */ onPrepare()1027 public void onPrepare() { 1028 } 1029 1030 /** 1031 * Override to handle requests to prepare for playing a specific mediaId that was provided 1032 * by your app's {@link MediaBrowserService}. During the preparation, a session should not 1033 * hold audio focus in order to allow other sessions play seamlessly. The state of playback 1034 * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. 1035 * The playback of the prepared content should start in the implementation of 1036 * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting 1037 * playback without preparation. 1038 */ onPrepareFromMediaId(String mediaId, Bundle extras)1039 public void onPrepareFromMediaId(String mediaId, Bundle extras) { 1040 } 1041 1042 /** 1043 * Override to handle requests to prepare playback from a search query. An empty query 1044 * indicates that the app may prepare any music. The implementation should attempt to make a 1045 * smart choice about what to play. During the preparation, a session should not hold audio 1046 * focus in order to allow other sessions play seamlessly. The state of playback should be 1047 * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback 1048 * of the prepared content should start in the implementation of {@link #onPlay}. Override 1049 * {@link #onPlayFromSearch} to handle requests for starting playback without preparation. 1050 */ onPrepareFromSearch(String query, Bundle extras)1051 public void onPrepareFromSearch(String query, Bundle extras) { 1052 } 1053 1054 /** 1055 * Override to handle requests to prepare a specific media item represented by a URI. 1056 * During the preparation, a session should not hold audio focus in order to allow 1057 * other sessions play seamlessly. The state of playback should be updated to 1058 * {@link PlaybackState#STATE_PAUSED} after the preparation is done. 1059 * The playback of the prepared content should start in the implementation of 1060 * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests 1061 * for starting playback without preparation. 1062 */ onPrepareFromUri(Uri uri, Bundle extras)1063 public void onPrepareFromUri(Uri uri, Bundle extras) { 1064 } 1065 1066 /** 1067 * Override to handle requests to begin playback. 1068 */ onPlay()1069 public void onPlay() { 1070 } 1071 1072 /** 1073 * Override to handle requests to begin playback from a search query. An 1074 * empty query indicates that the app may play any music. The 1075 * implementation should attempt to make a smart choice about what to 1076 * play. 1077 */ onPlayFromSearch(String query, Bundle extras)1078 public void onPlayFromSearch(String query, Bundle extras) { 1079 } 1080 1081 /** 1082 * Override to handle requests to play a specific mediaId that was 1083 * provided by your app's {@link MediaBrowserService}. 1084 */ onPlayFromMediaId(String mediaId, Bundle extras)1085 public void onPlayFromMediaId(String mediaId, Bundle extras) { 1086 } 1087 1088 /** 1089 * Override to handle requests to play a specific media item represented by a URI. 1090 */ onPlayFromUri(Uri uri, Bundle extras)1091 public void onPlayFromUri(Uri uri, Bundle extras) { 1092 } 1093 1094 /** 1095 * Override to handle requests to play an item with a given id from the 1096 * play queue. 1097 */ onSkipToQueueItem(long id)1098 public void onSkipToQueueItem(long id) { 1099 } 1100 1101 /** 1102 * Override to handle requests to pause playback. 1103 */ onPause()1104 public void onPause() { 1105 } 1106 1107 /** 1108 * Override to handle requests to skip to the next media item. 1109 */ onSkipToNext()1110 public void onSkipToNext() { 1111 } 1112 1113 /** 1114 * Override to handle requests to skip to the previous media item. 1115 */ onSkipToPrevious()1116 public void onSkipToPrevious() { 1117 } 1118 1119 /** 1120 * Override to handle requests to fast forward. 1121 */ onFastForward()1122 public void onFastForward() { 1123 } 1124 1125 /** 1126 * Override to handle requests to rewind. 1127 */ onRewind()1128 public void onRewind() { 1129 } 1130 1131 /** 1132 * Override to handle requests to stop playback. 1133 */ onStop()1134 public void onStop() { 1135 } 1136 1137 /** 1138 * Override to handle requests to seek to a specific position in ms. 1139 * 1140 * @param pos New position to move to, in milliseconds. 1141 */ onSeekTo(long pos)1142 public void onSeekTo(long pos) { 1143 } 1144 1145 /** 1146 * Override to handle the item being rated. 1147 * 1148 * @param rating 1149 */ onSetRating(@onNull Rating rating)1150 public void onSetRating(@NonNull Rating rating) { 1151 } 1152 1153 /** 1154 * Override to handle the playback speed change. 1155 * To update the new playback speed, create a new {@link PlaybackState} by using {@link 1156 * PlaybackState.Builder#setState(int, long, float)}, and set it with 1157 * {@link #setPlaybackState(PlaybackState)}. 1158 * <p> 1159 * A value of {@code 1.0f} is the default playback value, and a negative value indicates 1160 * reverse playback. The {@code speed} will not be equal to zero. 1161 * 1162 * @param speed the playback speed 1163 * @see #setPlaybackState(PlaybackState) 1164 * @see PlaybackState.Builder#setState(int, long, float) 1165 */ onSetPlaybackSpeed(float speed)1166 public void onSetPlaybackSpeed(float speed) { 1167 } 1168 1169 /** 1170 * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be 1171 * performed. 1172 * 1173 * @param action The action that was originally sent in the 1174 * {@link PlaybackState.CustomAction}. 1175 * @param extras Optional extras specified by the {@link MediaController}. 1176 */ onCustomAction(@onNull String action, @Nullable Bundle extras)1177 public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { 1178 } 1179 } 1180 1181 /** 1182 * @hide 1183 */ 1184 public static class CallbackStub extends ISessionCallback.Stub { 1185 private WeakReference<MediaSession> mMediaSession; 1186 CallbackStub(MediaSession session)1187 public CallbackStub(MediaSession session) { 1188 mMediaSession = new WeakReference<>(session); 1189 } 1190 createRemoteUserInfo(String packageName, int pid, int uid)1191 private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) { 1192 return new RemoteUserInfo(packageName, pid, uid); 1193 } 1194 1195 @Override onCommand(String packageName, int pid, int uid, String command, Bundle args, ResultReceiver cb)1196 public void onCommand(String packageName, int pid, int uid, String command, Bundle args, 1197 ResultReceiver cb) { 1198 MediaSession session = mMediaSession.get(); 1199 if (session != null) { 1200 session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid), 1201 command, args, cb); 1202 } 1203 } 1204 1205 @Override onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)1206 public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, 1207 int sequenceNumber, ResultReceiver cb) { 1208 MediaSession session = mMediaSession.get(); 1209 try { 1210 if (session != null) { 1211 session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid), 1212 mediaButtonIntent); 1213 } 1214 } finally { 1215 if (cb != null) { 1216 cb.send(sequenceNumber, null); 1217 } 1218 } 1219 } 1220 1221 @Override onMediaButtonFromController(String packageName, int pid, int uid, Intent mediaButtonIntent)1222 public void onMediaButtonFromController(String packageName, int pid, int uid, 1223 Intent mediaButtonIntent) { 1224 MediaSession session = mMediaSession.get(); 1225 if (session != null) { 1226 session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid), 1227 mediaButtonIntent); 1228 } 1229 } 1230 1231 @Override onPrepare(String packageName, int pid, int uid)1232 public void onPrepare(String packageName, int pid, int uid) { 1233 MediaSession session = mMediaSession.get(); 1234 if (session != null) { 1235 session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid)); 1236 } 1237 } 1238 1239 @Override onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras)1240 public void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId, 1241 Bundle extras) { 1242 MediaSession session = mMediaSession.get(); 1243 if (session != null) { 1244 session.dispatchPrepareFromMediaId( 1245 createRemoteUserInfo(packageName, pid, uid), mediaId, extras); 1246 } 1247 } 1248 1249 @Override onPrepareFromSearch(String packageName, int pid, int uid, String query, Bundle extras)1250 public void onPrepareFromSearch(String packageName, int pid, int uid, String query, 1251 Bundle extras) { 1252 MediaSession session = mMediaSession.get(); 1253 if (session != null) { 1254 session.dispatchPrepareFromSearch( 1255 createRemoteUserInfo(packageName, pid, uid), query, extras); 1256 } 1257 } 1258 1259 @Override onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras)1260 public void onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { 1261 MediaSession session = mMediaSession.get(); 1262 if (session != null) { 1263 session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid), 1264 uri, extras); 1265 } 1266 } 1267 1268 @Override onPlay(String packageName, int pid, int uid)1269 public void onPlay(String packageName, int pid, int uid) { 1270 MediaSession session = mMediaSession.get(); 1271 if (session != null) { 1272 session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid)); 1273 } 1274 } 1275 1276 @Override onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras)1277 public void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, 1278 Bundle extras) { 1279 MediaSession session = mMediaSession.get(); 1280 if (session != null) { 1281 session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid), 1282 mediaId, extras); 1283 } 1284 } 1285 1286 @Override onPlayFromSearch(String packageName, int pid, int uid, String query, Bundle extras)1287 public void onPlayFromSearch(String packageName, int pid, int uid, String query, 1288 Bundle extras) { 1289 MediaSession session = mMediaSession.get(); 1290 if (session != null) { 1291 session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid), 1292 query, extras); 1293 } 1294 } 1295 1296 @Override onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras)1297 public void onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { 1298 MediaSession session = mMediaSession.get(); 1299 if (session != null) { 1300 session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid), 1301 uri, extras); 1302 } 1303 } 1304 1305 @Override onSkipToTrack(String packageName, int pid, int uid, long id)1306 public void onSkipToTrack(String packageName, int pid, int uid, long id) { 1307 MediaSession session = mMediaSession.get(); 1308 if (session != null) { 1309 session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid), id); 1310 } 1311 } 1312 1313 @Override onPause(String packageName, int pid, int uid)1314 public void onPause(String packageName, int pid, int uid) { 1315 MediaSession session = mMediaSession.get(); 1316 if (session != null) { 1317 session.dispatchPause(createRemoteUserInfo(packageName, pid, uid)); 1318 } 1319 } 1320 1321 @Override onStop(String packageName, int pid, int uid)1322 public void onStop(String packageName, int pid, int uid) { 1323 MediaSession session = mMediaSession.get(); 1324 if (session != null) { 1325 session.dispatchStop(createRemoteUserInfo(packageName, pid, uid)); 1326 } 1327 } 1328 1329 @Override onNext(String packageName, int pid, int uid)1330 public void onNext(String packageName, int pid, int uid) { 1331 MediaSession session = mMediaSession.get(); 1332 if (session != null) { 1333 session.dispatchNext(createRemoteUserInfo(packageName, pid, uid)); 1334 } 1335 } 1336 1337 @Override onPrevious(String packageName, int pid, int uid)1338 public void onPrevious(String packageName, int pid, int uid) { 1339 MediaSession session = mMediaSession.get(); 1340 if (session != null) { 1341 session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid)); 1342 } 1343 } 1344 1345 @Override onFastForward(String packageName, int pid, int uid)1346 public void onFastForward(String packageName, int pid, int uid) { 1347 MediaSession session = mMediaSession.get(); 1348 if (session != null) { 1349 session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid)); 1350 } 1351 } 1352 1353 @Override onRewind(String packageName, int pid, int uid)1354 public void onRewind(String packageName, int pid, int uid) { 1355 MediaSession session = mMediaSession.get(); 1356 if (session != null) { 1357 session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid)); 1358 } 1359 } 1360 1361 @Override onSeekTo(String packageName, int pid, int uid, long pos)1362 public void onSeekTo(String packageName, int pid, int uid, long pos) { 1363 MediaSession session = mMediaSession.get(); 1364 if (session != null) { 1365 session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid), pos); 1366 } 1367 } 1368 1369 @Override onRate(String packageName, int pid, int uid, Rating rating)1370 public void onRate(String packageName, int pid, int uid, Rating rating) { 1371 MediaSession session = mMediaSession.get(); 1372 if (session != null) { 1373 session.dispatchRate(createRemoteUserInfo(packageName, pid, uid), rating); 1374 } 1375 } 1376 1377 @Override onSetPlaybackSpeed(String packageName, int pid, int uid, float speed)1378 public void onSetPlaybackSpeed(String packageName, int pid, int uid, float speed) { 1379 MediaSession session = mMediaSession.get(); 1380 if (session != null) { 1381 session.dispatchSetPlaybackSpeed( 1382 createRemoteUserInfo(packageName, pid, uid), speed); 1383 } 1384 } 1385 1386 @Override onCustomAction(String packageName, int pid, int uid, String action, Bundle args)1387 public void onCustomAction(String packageName, int pid, int uid, String action, 1388 Bundle args) { 1389 MediaSession session = mMediaSession.get(); 1390 if (session != null) { 1391 session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid), 1392 action, args); 1393 } 1394 } 1395 1396 @Override onAdjustVolume(String packageName, int pid, int uid, int direction)1397 public void onAdjustVolume(String packageName, int pid, int uid, int direction) { 1398 MediaSession session = mMediaSession.get(); 1399 if (session != null) { 1400 session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid), 1401 direction); 1402 } 1403 } 1404 1405 @Override onSetVolumeTo(String packageName, int pid, int uid, int value)1406 public void onSetVolumeTo(String packageName, int pid, int uid, int value) { 1407 MediaSession session = mMediaSession.get(); 1408 if (session != null) { 1409 session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid), 1410 value); 1411 } 1412 } 1413 } 1414 1415 /** 1416 * A single item that is part of the play queue. It contains a description 1417 * of the item and its id in the queue. 1418 */ 1419 public static final class QueueItem implements Parcelable { 1420 /** 1421 * This id is reserved. No items can be explicitly assigned this id. 1422 */ 1423 public static final int UNKNOWN_ID = -1; 1424 1425 private final MediaDescription mDescription; 1426 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1427 private final long mId; 1428 1429 /** 1430 * Create a new {@link MediaSession.QueueItem}. 1431 * 1432 * @param description The {@link MediaDescription} for this item. 1433 * @param id An identifier for this item. It must be unique within the 1434 * play queue and cannot be {@link #UNKNOWN_ID}. 1435 */ QueueItem(MediaDescription description, long id)1436 public QueueItem(MediaDescription description, long id) { 1437 if (description == null) { 1438 throw new IllegalArgumentException("Description cannot be null."); 1439 } 1440 if (id == UNKNOWN_ID) { 1441 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); 1442 } 1443 mDescription = description; 1444 mId = id; 1445 } 1446 QueueItem(Parcel in)1447 private QueueItem(Parcel in) { 1448 mDescription = MediaDescription.CREATOR.createFromParcel(in); 1449 mId = in.readLong(); 1450 } 1451 1452 /** 1453 * Get the description for this item. 1454 */ getDescription()1455 public MediaDescription getDescription() { 1456 return mDescription; 1457 } 1458 1459 /** 1460 * Get the queue id for this item. 1461 */ getQueueId()1462 public long getQueueId() { 1463 return mId; 1464 } 1465 1466 @Override writeToParcel(Parcel dest, int flags)1467 public void writeToParcel(Parcel dest, int flags) { 1468 mDescription.writeToParcel(dest, flags); 1469 dest.writeLong(mId); 1470 } 1471 1472 @Override describeContents()1473 public int describeContents() { 1474 return 0; 1475 } 1476 1477 public static final @android.annotation.NonNull Creator<MediaSession.QueueItem> CREATOR = 1478 new Creator<MediaSession.QueueItem>() { 1479 1480 @Override 1481 public MediaSession.QueueItem createFromParcel(Parcel p) { 1482 return new MediaSession.QueueItem(p); 1483 } 1484 1485 @Override 1486 public MediaSession.QueueItem[] newArray(int size) { 1487 return new MediaSession.QueueItem[size]; 1488 } 1489 }; 1490 1491 @Override toString()1492 public String toString() { 1493 return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId 1494 + " }"; 1495 } 1496 1497 @Override equals(Object o)1498 public boolean equals(Object o) { 1499 if (o == null) { 1500 return false; 1501 } 1502 1503 if (!(o instanceof QueueItem)) { 1504 return false; 1505 } 1506 1507 final QueueItem item = (QueueItem) o; 1508 if (mId != item.mId) { 1509 return false; 1510 } 1511 1512 if (!Objects.equals(mDescription, item.mDescription)) { 1513 return false; 1514 } 1515 1516 return true; 1517 } 1518 } 1519 1520 private static final class Command { 1521 public final String command; 1522 public final Bundle extras; 1523 public final ResultReceiver stub; 1524 Command(String command, Bundle extras, ResultReceiver stub)1525 Command(String command, Bundle extras, ResultReceiver stub) { 1526 this.command = command; 1527 this.extras = extras; 1528 this.stub = stub; 1529 } 1530 } 1531 1532 private class CallbackMessageHandler extends Handler { 1533 private static final int MSG_COMMAND = 1; 1534 private static final int MSG_MEDIA_BUTTON = 2; 1535 private static final int MSG_PREPARE = 3; 1536 private static final int MSG_PREPARE_MEDIA_ID = 4; 1537 private static final int MSG_PREPARE_SEARCH = 5; 1538 private static final int MSG_PREPARE_URI = 6; 1539 private static final int MSG_PLAY = 7; 1540 private static final int MSG_PLAY_MEDIA_ID = 8; 1541 private static final int MSG_PLAY_SEARCH = 9; 1542 private static final int MSG_PLAY_URI = 10; 1543 private static final int MSG_SKIP_TO_ITEM = 11; 1544 private static final int MSG_PAUSE = 12; 1545 private static final int MSG_STOP = 13; 1546 private static final int MSG_NEXT = 14; 1547 private static final int MSG_PREVIOUS = 15; 1548 private static final int MSG_FAST_FORWARD = 16; 1549 private static final int MSG_REWIND = 17; 1550 private static final int MSG_SEEK_TO = 18; 1551 private static final int MSG_RATE = 19; 1552 private static final int MSG_SET_PLAYBACK_SPEED = 20; 1553 private static final int MSG_CUSTOM_ACTION = 21; 1554 private static final int MSG_ADJUST_VOLUME = 22; 1555 private static final int MSG_SET_VOLUME = 23; 1556 private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 24; 1557 1558 private MediaSession.Callback mCallback; 1559 private RemoteUserInfo mCurrentControllerInfo; 1560 CallbackMessageHandler(Looper looper, MediaSession.Callback callback)1561 CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { 1562 super(looper); 1563 mCallback = callback; 1564 mCallback.mHandler = this; 1565 } 1566 post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs)1567 void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) { 1568 Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj); 1569 Message msg = obtainMessage(what, objWithCaller); 1570 msg.setAsynchronous(true); 1571 msg.setData(data); 1572 if (delayMs > 0) { 1573 sendMessageDelayed(msg, delayMs); 1574 } else { 1575 sendMessage(msg); 1576 } 1577 } 1578 1579 @Override handleMessage(Message msg)1580 public void handleMessage(Message msg) { 1581 mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first; 1582 1583 VolumeProvider vp; 1584 Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second; 1585 1586 switch (msg.what) { 1587 case MSG_COMMAND: 1588 Command cmd = (Command) obj; 1589 mCallback.onCommand(cmd.command, cmd.extras, cmd.stub); 1590 break; 1591 case MSG_MEDIA_BUTTON: 1592 mCallback.onMediaButtonEvent((Intent) obj); 1593 break; 1594 case MSG_PREPARE: 1595 mCallback.onPrepare(); 1596 break; 1597 case MSG_PREPARE_MEDIA_ID: 1598 mCallback.onPrepareFromMediaId((String) obj, msg.getData()); 1599 break; 1600 case MSG_PREPARE_SEARCH: 1601 mCallback.onPrepareFromSearch((String) obj, msg.getData()); 1602 break; 1603 case MSG_PREPARE_URI: 1604 mCallback.onPrepareFromUri((Uri) obj, msg.getData()); 1605 break; 1606 case MSG_PLAY: 1607 mCallback.onPlay(); 1608 break; 1609 case MSG_PLAY_MEDIA_ID: 1610 mCallback.onPlayFromMediaId((String) obj, msg.getData()); 1611 break; 1612 case MSG_PLAY_SEARCH: 1613 mCallback.onPlayFromSearch((String) obj, msg.getData()); 1614 break; 1615 case MSG_PLAY_URI: 1616 mCallback.onPlayFromUri((Uri) obj, msg.getData()); 1617 break; 1618 case MSG_SKIP_TO_ITEM: 1619 mCallback.onSkipToQueueItem((Long) obj); 1620 break; 1621 case MSG_PAUSE: 1622 mCallback.onPause(); 1623 break; 1624 case MSG_STOP: 1625 mCallback.onStop(); 1626 break; 1627 case MSG_NEXT: 1628 mCallback.onSkipToNext(); 1629 break; 1630 case MSG_PREVIOUS: 1631 mCallback.onSkipToPrevious(); 1632 break; 1633 case MSG_FAST_FORWARD: 1634 mCallback.onFastForward(); 1635 break; 1636 case MSG_REWIND: 1637 mCallback.onRewind(); 1638 break; 1639 case MSG_SEEK_TO: 1640 mCallback.onSeekTo((Long) obj); 1641 break; 1642 case MSG_RATE: 1643 mCallback.onSetRating((Rating) obj); 1644 break; 1645 case MSG_SET_PLAYBACK_SPEED: 1646 mCallback.onSetPlaybackSpeed((Float) obj); 1647 break; 1648 case MSG_CUSTOM_ACTION: 1649 mCallback.onCustomAction((String) obj, msg.getData()); 1650 break; 1651 case MSG_ADJUST_VOLUME: 1652 synchronized (mLock) { 1653 vp = mVolumeProvider; 1654 } 1655 if (vp != null) { 1656 vp.onAdjustVolume((int) obj); 1657 } 1658 break; 1659 case MSG_SET_VOLUME: 1660 synchronized (mLock) { 1661 vp = mVolumeProvider; 1662 } 1663 if (vp != null) { 1664 vp.onSetVolumeTo((int) obj); 1665 } 1666 break; 1667 case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT: 1668 mCallback.handleMediaPlayPauseKeySingleTapIfPending(); 1669 break; 1670 } 1671 mCurrentControllerInfo = null; 1672 } 1673 } 1674 } 1675