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