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