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