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