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