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