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