MediaSession.java revision 01a500ed1c6ae3fff66678144ae637aa8cad0ecc
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media.session; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.app.PendingIntent; 22import android.content.ComponentName; 23import android.content.Intent; 24import android.media.AudioManager; 25import android.media.MediaMetadata; 26import android.media.Rating; 27import android.media.VolumeProvider; 28import android.media.session.ISessionController; 29import android.media.session.ISession; 30import android.media.session.ISessionCallback; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.Looper; 34import android.os.Message; 35import android.os.Parcel; 36import android.os.Parcelable; 37import android.os.RemoteException; 38import android.os.ResultReceiver; 39import android.text.TextUtils; 40import android.util.ArrayMap; 41import android.util.Log; 42 43import java.lang.ref.WeakReference; 44import java.util.ArrayList; 45import java.util.List; 46 47/** 48 * Allows interaction with media controllers, volume keys, media buttons, and 49 * transport controls. 50 * <p> 51 * A MediaSession should be created when an app wants to publish media playback 52 * information or handle media keys. In general an app only needs one session 53 * for all playback, though multiple sessions can be created to provide finer 54 * grain controls of media. 55 * <p> 56 * Once a session is created the owner of the session may pass its 57 * {@link #getSessionToken() session token} to other processes to allow them to 58 * create a {@link MediaController} to interact with the session. 59 * <p> 60 * To receive commands, media keys, and other events a {@link Callback} must be 61 * set with {@link #addCallback(Callback)}. To receive transport control 62 * commands a {@link TransportControlsCallback} must be set with 63 * {@link #addTransportControlsCallback}. 64 * <p> 65 * When an app is finished performing playback it must call {@link #release()} 66 * to clean up the session and notify any controllers. 67 * <p> 68 * MediaSession objects are thread safe. 69 */ 70public final class MediaSession { 71 private static final String TAG = "MediaSession"; 72 73 /** 74 * Set this flag on the session to indicate that it can handle media button 75 * events. 76 */ 77 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 78 79 /** 80 * Set this flag on the session to indicate that it handles transport 81 * control commands through a {@link TransportControlsCallback}. 82 * The callback can be retrieved by calling {@link #addTransportControlsCallback}. 83 */ 84 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 85 86 /** 87 * System only flag for a session that needs to have priority over all other 88 * sessions. This flag ensures this session will receive media button events 89 * regardless of the current ordering in the system. 90 * 91 * @hide 92 */ 93 public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; 94 95 /** 96 * The session uses local playback. 97 */ 98 public static final int PLAYBACK_TYPE_LOCAL = 1; 99 100 /** 101 * The session uses remote playback. 102 */ 103 public static final int PLAYBACK_TYPE_REMOTE = 2; 104 105 private final Object mLock = new Object(); 106 107 private final MediaSession.Token mSessionToken; 108 private final ISession mBinder; 109 private final CallbackStub mCbStub; 110 111 private final ArrayList<CallbackMessageHandler> mCallbacks 112 = new ArrayList<CallbackMessageHandler>(); 113 private final ArrayList<TransportMessageHandler> mTransportCallbacks 114 = new ArrayList<TransportMessageHandler>(); 115 116 private VolumeProvider mVolumeProvider; 117 118 private boolean mActive = false; 119 120 /** 121 * @hide 122 */ 123 public MediaSession(ISession binder, CallbackStub cbStub) { 124 mBinder = binder; 125 mCbStub = cbStub; 126 ISessionController controllerBinder = null; 127 try { 128 controllerBinder = mBinder.getController(); 129 } catch (RemoteException e) { 130 throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); 131 } 132 mSessionToken = new Token(controllerBinder); 133 } 134 135 /** 136 * Add a callback to receive updates on for the MediaSession. This includes 137 * media button and volume events. The caller's thread will be used to post 138 * events. 139 * 140 * @param callback The callback object 141 */ 142 public void addCallback(@NonNull Callback callback) { 143 addCallback(callback, null); 144 } 145 146 /** 147 * Add a callback to receive updates for the MediaSession. This includes 148 * media button and volume events. 149 * 150 * @param callback The callback to receive updates on. 151 * @param handler The handler that events should be posted on. 152 */ 153 public void addCallback(@NonNull Callback callback, @Nullable Handler handler) { 154 if (callback == null) { 155 throw new IllegalArgumentException("Callback cannot be null"); 156 } 157 synchronized (mLock) { 158 if (getHandlerForCallbackLocked(callback) != null) { 159 Log.w(TAG, "Callback is already added, ignoring"); 160 return; 161 } 162 if (handler == null) { 163 handler = new Handler(); 164 } 165 CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), 166 callback); 167 mCallbacks.add(msgHandler); 168 } 169 } 170 171 /** 172 * Remove a callback. It will no longer receive updates. 173 * 174 * @param callback The callback to remove. 175 */ 176 public void removeCallback(@NonNull Callback callback) { 177 synchronized (mLock) { 178 removeCallbackLocked(callback); 179 } 180 } 181 182 /** 183 * Set an intent for launching UI for this Session. This can be used as a 184 * quick link to an ongoing media screen. 185 * 186 * @param pi The intent to launch to show UI for this Session. 187 */ 188 public void setLaunchPendingIntent(@Nullable PendingIntent pi) { 189 // TODO 190 } 191 192 /** 193 * Set a media button event receiver component to use to restart playback 194 * after an app has been stopped. 195 * 196 * @param mbr The receiver component to send the media button event to. 197 * @hide 198 */ 199 public void setMediaButtonReceiver(@Nullable ComponentName mbr) { 200 try { 201 mBinder.setMediaButtonReceiver(mbr); 202 } catch (RemoteException e) { 203 Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); 204 } 205 } 206 207 /** 208 * Set any flags for the session. 209 * 210 * @param flags The flags to set for this session. 211 */ 212 public void setFlags(int flags) { 213 try { 214 mBinder.setFlags(flags); 215 } catch (RemoteException e) { 216 Log.wtf(TAG, "Failure in setFlags.", e); 217 } 218 } 219 220 /** 221 * Set the stream this session is playing on. This will affect the system's 222 * volume handling for this session. If {@link #setPlaybackToRemote} was 223 * previously called it will stop receiving volume commands and the system 224 * will begin sending volume changes to the appropriate stream. 225 * <p> 226 * By default sessions are on {@link AudioManager#STREAM_MUSIC}. 227 * 228 * @param stream The {@link AudioManager} stream this session is playing on. 229 */ 230 public void setPlaybackToLocal(int stream) { 231 try { 232 mBinder.configureVolumeHandling(PLAYBACK_TYPE_LOCAL, stream, 0); 233 } catch (RemoteException e) { 234 Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); 235 } 236 } 237 238 /** 239 * Configure this session to use remote volume handling. This must be called 240 * to receive volume button events, otherwise the system will adjust the 241 * current stream volume for this session. If {@link #setPlaybackToLocal} 242 * was previously called that stream will stop receiving volume changes for 243 * this session. 244 * 245 * @param volumeProvider The provider that will handle volume changes. May 246 * not be null. 247 */ 248 public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) { 249 if (volumeProvider == null) { 250 throw new IllegalArgumentException("volumeProvider may not be null!"); 251 } 252 mVolumeProvider = volumeProvider; 253 volumeProvider.setCallback(new VolumeProvider.Callback() { 254 @Override 255 public void onVolumeChanged(VolumeProvider volumeProvider) { 256 notifyRemoteVolumeChanged(volumeProvider); 257 } 258 }); 259 260 try { 261 mBinder.configureVolumeHandling(PLAYBACK_TYPE_REMOTE, volumeProvider.getVolumeControl(), 262 volumeProvider.getMaxVolume()); 263 } catch (RemoteException e) { 264 Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); 265 } 266 } 267 268 /** 269 * Set if this session is currently active and ready to receive commands. If 270 * set to false your session's controller may not be discoverable. You must 271 * set the session to active before it can start receiving media button 272 * events or transport commands. 273 * 274 * @param active Whether this session is active or not. 275 */ 276 public void setActive(boolean active) { 277 if (mActive == active) { 278 return; 279 } 280 try { 281 mBinder.setActive(active); 282 mActive = active; 283 } catch (RemoteException e) { 284 Log.wtf(TAG, "Failure in setActive.", e); 285 } 286 } 287 288 /** 289 * Get the current active state of this session. 290 * 291 * @return True if the session is active, false otherwise. 292 */ 293 public boolean isActive() { 294 return mActive; 295 } 296 297 /** 298 * Send a proprietary event to all MediaControllers listening to this 299 * Session. It's up to the Controller/Session owner to determine the meaning 300 * of any events. 301 * 302 * @param event The name of the event to send 303 * @param extras Any extras included with the event 304 */ 305 public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) { 306 if (TextUtils.isEmpty(event)) { 307 throw new IllegalArgumentException("event cannot be null or empty"); 308 } 309 try { 310 mBinder.sendEvent(event, extras); 311 } catch (RemoteException e) { 312 Log.wtf(TAG, "Error sending event", e); 313 } 314 } 315 316 /** 317 * This must be called when an app has finished performing playback. If 318 * playback is expected to start again shortly the session can be left open, 319 * but it must be released if your activity or service is being destroyed. 320 */ 321 public void release() { 322 try { 323 mBinder.destroy(); 324 } catch (RemoteException e) { 325 Log.wtf(TAG, "Error releasing session: ", e); 326 } 327 } 328 329 /** 330 * Retrieve a token object that can be used by apps to create a 331 * {@link MediaController} for interacting with this session. The owner of 332 * the session is responsible for deciding how to distribute these tokens. 333 * 334 * @return A token that can be used to create a MediaController for this 335 * session 336 */ 337 public @NonNull Token getSessionToken() { 338 return mSessionToken; 339 } 340 341 /** 342 * Add a callback to receive transport controls on, such as play, rewind, or 343 * fast forward. 344 * 345 * @param callback The callback object 346 */ 347 public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) { 348 addTransportControlsCallback(callback, null); 349 } 350 351 /** 352 * Add a callback to receive transport controls on, such as play, rewind, or 353 * fast forward. The updates will be posted to the specified handler. If no 354 * handler is provided they will be posted to the caller's thread. 355 * 356 * @param callback The callback to receive updates on 357 * @param handler The handler to post the updates on 358 */ 359 public void addTransportControlsCallback(@NonNull TransportControlsCallback callback, 360 @Nullable Handler handler) { 361 if (callback == null) { 362 throw new IllegalArgumentException("Callback cannot be null"); 363 } 364 synchronized (mLock) { 365 if (getTransportControlsHandlerForCallbackLocked(callback) != null) { 366 Log.w(TAG, "Callback is already added, ignoring"); 367 return; 368 } 369 if (handler == null) { 370 handler = new Handler(); 371 } 372 TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(), 373 callback); 374 mTransportCallbacks.add(msgHandler); 375 } 376 } 377 378 /** 379 * Stop receiving transport controls on the specified callback. If an update 380 * has already been posted you may still receive it after this call returns. 381 * 382 * @param callback The callback to stop receiving updates on 383 */ 384 public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) { 385 if (callback == null) { 386 throw new IllegalArgumentException("Callback cannot be null"); 387 } 388 synchronized (mLock) { 389 removeTransportControlsCallbackLocked(callback); 390 } 391 } 392 393 /** 394 * Update the current playback state. 395 * 396 * @param state The current state of playback 397 */ 398 public void setPlaybackState(@Nullable PlaybackState state) { 399 try { 400 mBinder.setPlaybackState(state); 401 } catch (RemoteException e) { 402 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 403 } 404 } 405 406 /** 407 * Update the current metadata. New metadata can be created using 408 * {@link android.media.MediaMetadata.Builder}. 409 * 410 * @param metadata The new metadata 411 */ 412 public void setMetadata(@Nullable MediaMetadata metadata) { 413 try { 414 mBinder.setMetadata(metadata); 415 } catch (RemoteException e) { 416 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 417 } 418 } 419 420 /** 421 * Notify the system that the remote volume changed. 422 * 423 * @param provider The provider that is handling volume changes. 424 * @hide 425 */ 426 public void notifyRemoteVolumeChanged(VolumeProvider provider) { 427 if (provider == null || provider != mVolumeProvider) { 428 Log.w(TAG, "Received update from stale volume provider"); 429 return; 430 } 431 try { 432 mBinder.setCurrentVolume(provider.onGetCurrentVolume()); 433 } catch (RemoteException e) { 434 Log.e(TAG, "Error in notifyVolumeChanged", e); 435 } 436 } 437 438 private void dispatchPlay() { 439 postToTransportCallbacks(TransportMessageHandler.MSG_PLAY); 440 } 441 442 private void dispatchPause() { 443 postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE); 444 } 445 446 private void dispatchStop() { 447 postToTransportCallbacks(TransportMessageHandler.MSG_STOP); 448 } 449 450 private void dispatchNext() { 451 postToTransportCallbacks(TransportMessageHandler.MSG_NEXT); 452 } 453 454 private void dispatchPrevious() { 455 postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS); 456 } 457 458 private void dispatchFastForward() { 459 postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD); 460 } 461 462 private void dispatchRewind() { 463 postToTransportCallbacks(TransportMessageHandler.MSG_REWIND); 464 } 465 466 private void dispatchSeekTo(long pos) { 467 postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos); 468 } 469 470 private void dispatchRate(Rating rating) { 471 postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating); 472 } 473 474 private TransportMessageHandler getTransportControlsHandlerForCallbackLocked( 475 TransportControlsCallback callback) { 476 for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { 477 TransportMessageHandler handler = mTransportCallbacks.get(i); 478 if (callback == handler.mCallback) { 479 return handler; 480 } 481 } 482 return null; 483 } 484 485 private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) { 486 for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { 487 if (callback == mTransportCallbacks.get(i).mCallback) { 488 mTransportCallbacks.remove(i); 489 return true; 490 } 491 } 492 return false; 493 } 494 495 private void postToTransportCallbacks(int what, Object obj) { 496 synchronized (mLock) { 497 for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) { 498 mTransportCallbacks.get(i).post(what, obj); 499 } 500 } 501 } 502 503 private void postToTransportCallbacks(int what) { 504 postToTransportCallbacks(what, null); 505 } 506 507 private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) { 508 if (cb == null) { 509 throw new IllegalArgumentException("Callback cannot be null"); 510 } 511 for (int i = mCallbacks.size() - 1; i >= 0; i--) { 512 CallbackMessageHandler handler = mCallbacks.get(i); 513 if (cb == handler.mCallback) { 514 return handler; 515 } 516 } 517 return null; 518 } 519 520 private boolean removeCallbackLocked(Callback cb) { 521 if (cb == null) { 522 throw new IllegalArgumentException("Callback cannot be null"); 523 } 524 for (int i = mCallbacks.size() - 1; i >= 0; i--) { 525 CallbackMessageHandler handler = mCallbacks.get(i); 526 if (cb == handler.mCallback) { 527 mCallbacks.remove(i); 528 return true; 529 } 530 } 531 return false; 532 } 533 534 private void postCommand(String command, Bundle extras, ResultReceiver resultCb) { 535 Command cmd = new Command(command, extras, resultCb); 536 synchronized (mLock) { 537 for (int i = mCallbacks.size() - 1; i >= 0; i--) { 538 mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd); 539 } 540 } 541 } 542 543 private void postMediaButton(Intent mediaButtonIntent) { 544 synchronized (mLock) { 545 for (int i = mCallbacks.size() - 1; i >= 0; i--) { 546 mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent); 547 } 548 } 549 } 550 551 /** 552 * Return true if this is considered an active playback state. 553 * 554 * @hide 555 */ 556 public static boolean isActiveState(int state) { 557 switch (state) { 558 case PlaybackState.STATE_FAST_FORWARDING: 559 case PlaybackState.STATE_REWINDING: 560 case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: 561 case PlaybackState.STATE_SKIPPING_TO_NEXT: 562 case PlaybackState.STATE_BUFFERING: 563 case PlaybackState.STATE_CONNECTING: 564 case PlaybackState.STATE_PLAYING: 565 return true; 566 } 567 return false; 568 } 569 570 /** 571 * Represents an ongoing session. This may be passed to apps by the session 572 * owner to allow them to create a {@link MediaController} to communicate with 573 * the session. 574 */ 575 public static final class Token implements Parcelable { 576 private ISessionController mBinder; 577 578 /** 579 * @hide 580 */ 581 public Token(ISessionController binder) { 582 mBinder = binder; 583 } 584 585 @Override 586 public int describeContents() { 587 return 0; 588 } 589 590 @Override 591 public void writeToParcel(Parcel dest, int flags) { 592 dest.writeStrongBinder(mBinder.asBinder()); 593 } 594 595 ISessionController getBinder() { 596 return mBinder; 597 } 598 599 public static final Parcelable.Creator<Token> CREATOR 600 = new Parcelable.Creator<Token>() { 601 @Override 602 public Token createFromParcel(Parcel in) { 603 return new Token(ISessionController.Stub.asInterface(in.readStrongBinder())); 604 } 605 606 @Override 607 public Token[] newArray(int size) { 608 return new Token[size]; 609 } 610 }; 611 } 612 613 /** 614 * Receives generic commands or updates from controllers and the system. 615 * Callbacks may be registered using {@link #addCallback}. 616 */ 617 public abstract static class Callback { 618 619 public Callback() { 620 } 621 622 /** 623 * Called when a media button is pressed and this session has the 624 * highest priority or a controller sends a media button event to the 625 * session. TODO determine if using Intents identical to the ones 626 * RemoteControlClient receives is useful 627 * <p> 628 * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a 629 * KeyEvent in {@link Intent#EXTRA_KEY_EVENT} 630 * 631 * @param mediaButtonIntent an intent containing the KeyEvent as an 632 * extra 633 */ 634 public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { 635 } 636 637 /** 638 * Called when a controller has sent a custom command to this session. 639 * The owner of the session may handle custom commands but is not 640 * required to. 641 * 642 * @param command The command name. 643 * @param extras Optional parameters for the command, may be null. 644 * @param cb A result receiver to which a result may be sent by the command, may be null. 645 */ 646 public void onControlCommand(@NonNull String command, @Nullable Bundle extras, 647 @Nullable ResultReceiver cb) { 648 } 649 } 650 651 /** 652 * Receives transport control commands. Callbacks may be registered using 653 * {@link #addTransportControlsCallback}. 654 */ 655 public static abstract class TransportControlsCallback { 656 657 /** 658 * Override to handle requests to begin playback. 659 */ 660 public void onPlay() { 661 } 662 663 /** 664 * Override to handle requests to pause playback. 665 */ 666 public void onPause() { 667 } 668 669 /** 670 * Override to handle requests to skip to the next media item. 671 */ 672 public void onSkipToNext() { 673 } 674 675 /** 676 * Override to handle requests to skip to the previous media item. 677 */ 678 public void onSkipToPrevious() { 679 } 680 681 /** 682 * Override to handle requests to fast forward. 683 */ 684 public void onFastForward() { 685 } 686 687 /** 688 * Override to handle requests to rewind. 689 */ 690 public void onRewind() { 691 } 692 693 /** 694 * Override to handle requests to stop playback. 695 */ 696 public void onStop() { 697 } 698 699 /** 700 * Override to handle requests to seek to a specific position in ms. 701 * 702 * @param pos New position to move to, in milliseconds. 703 */ 704 public void onSeekTo(long pos) { 705 } 706 707 /** 708 * Override to handle the item being rated. 709 * 710 * @param rating 711 */ 712 public void onSetRating(@NonNull Rating rating) { 713 } 714 } 715 716 /** 717 * @hide 718 */ 719 public static class CallbackStub extends ISessionCallback.Stub { 720 private WeakReference<MediaSession> mMediaSession; 721 722 public void setMediaSession(MediaSession session) { 723 mMediaSession = new WeakReference<MediaSession>(session); 724 } 725 726 @Override 727 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 728 MediaSession session = mMediaSession.get(); 729 if (session != null) { 730 session.postCommand(command, extras, cb); 731 } 732 } 733 734 @Override 735 public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, 736 ResultReceiver cb) { 737 MediaSession session = mMediaSession.get(); 738 try { 739 if (session != null) { 740 session.postMediaButton(mediaButtonIntent); 741 } 742 } finally { 743 if (cb != null) { 744 cb.send(sequenceNumber, null); 745 } 746 } 747 } 748 749 @Override 750 public void onPlay() { 751 MediaSession session = mMediaSession.get(); 752 if (session != null) { 753 session.dispatchPlay(); 754 } 755 } 756 757 @Override 758 public void onPause() { 759 MediaSession session = mMediaSession.get(); 760 if (session != null) { 761 session.dispatchPause(); 762 } 763 } 764 765 @Override 766 public void onStop() { 767 MediaSession session = mMediaSession.get(); 768 if (session != null) { 769 session.dispatchStop(); 770 } 771 } 772 773 @Override 774 public void onNext() { 775 MediaSession session = mMediaSession.get(); 776 if (session != null) { 777 session.dispatchNext(); 778 } 779 } 780 781 @Override 782 public void onPrevious() { 783 MediaSession session = mMediaSession.get(); 784 if (session != null) { 785 session.dispatchPrevious(); 786 } 787 } 788 789 @Override 790 public void onFastForward() { 791 MediaSession session = mMediaSession.get(); 792 if (session != null) { 793 session.dispatchFastForward(); 794 } 795 } 796 797 @Override 798 public void onRewind() { 799 MediaSession session = mMediaSession.get(); 800 if (session != null) { 801 session.dispatchRewind(); 802 } 803 } 804 805 @Override 806 public void onSeekTo(long pos) { 807 MediaSession session = mMediaSession.get(); 808 if (session != null) { 809 session.dispatchSeekTo(pos); 810 } 811 } 812 813 @Override 814 public void onRate(Rating rating) { 815 MediaSession session = mMediaSession.get(); 816 if (session != null) { 817 session.dispatchRate(rating); 818 } 819 } 820 821 @Override 822 public void onAdjustVolumeBy(int delta) { 823 MediaSession session = mMediaSession.get(); 824 if (session != null) { 825 if (session.mVolumeProvider != null) { 826 session.mVolumeProvider.onAdjustVolumeBy(delta); 827 } 828 } 829 } 830 831 @Override 832 public void onSetVolumeTo(int value) { 833 MediaSession session = mMediaSession.get(); 834 if (session != null) { 835 if (session.mVolumeProvider != null) { 836 session.mVolumeProvider.onSetVolumeTo(value); 837 } 838 } 839 } 840 841 } 842 843 private class CallbackMessageHandler extends Handler { 844 private static final int MSG_MEDIA_BUTTON = 1; 845 private static final int MSG_COMMAND = 2; 846 847 private MediaSession.Callback mCallback; 848 849 public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { 850 super(looper, null, true); 851 mCallback = callback; 852 } 853 854 @Override 855 public void handleMessage(Message msg) { 856 synchronized (mLock) { 857 if (mCallback == null) { 858 return; 859 } 860 switch (msg.what) { 861 case MSG_MEDIA_BUTTON: 862 mCallback.onMediaButtonEvent((Intent) msg.obj); 863 break; 864 case MSG_COMMAND: 865 Command cmd = (Command) msg.obj; 866 mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub); 867 break; 868 } 869 } 870 } 871 872 public void post(int what, Object obj) { 873 obtainMessage(what, obj).sendToTarget(); 874 } 875 876 public void post(int what, Object obj, int arg1) { 877 obtainMessage(what, arg1, 0, obj).sendToTarget(); 878 } 879 } 880 881 private static final class Command { 882 public final String command; 883 public final Bundle extras; 884 public final ResultReceiver stub; 885 886 public Command(String command, Bundle extras, ResultReceiver stub) { 887 this.command = command; 888 this.extras = extras; 889 this.stub = stub; 890 } 891 } 892 893 private class TransportMessageHandler extends Handler { 894 private static final int MSG_PLAY = 1; 895 private static final int MSG_PAUSE = 2; 896 private static final int MSG_STOP = 3; 897 private static final int MSG_NEXT = 4; 898 private static final int MSG_PREVIOUS = 5; 899 private static final int MSG_FAST_FORWARD = 6; 900 private static final int MSG_REWIND = 7; 901 private static final int MSG_SEEK_TO = 8; 902 private static final int MSG_RATE = 9; 903 904 private TransportControlsCallback mCallback; 905 906 public TransportMessageHandler(Looper looper, TransportControlsCallback cb) { 907 super(looper); 908 mCallback = cb; 909 } 910 911 public void post(int what, Object obj) { 912 obtainMessage(what, obj).sendToTarget(); 913 } 914 915 public void post(int what) { 916 post(what, null); 917 } 918 919 @Override 920 public void handleMessage(Message msg) { 921 switch (msg.what) { 922 case MSG_PLAY: 923 mCallback.onPlay(); 924 break; 925 case MSG_PAUSE: 926 mCallback.onPause(); 927 break; 928 case MSG_STOP: 929 mCallback.onStop(); 930 break; 931 case MSG_NEXT: 932 mCallback.onSkipToNext(); 933 break; 934 case MSG_PREVIOUS: 935 mCallback.onSkipToPrevious(); 936 break; 937 case MSG_FAST_FORWARD: 938 mCallback.onFastForward(); 939 break; 940 case MSG_REWIND: 941 mCallback.onRewind(); 942 break; 943 case MSG_SEEK_TO: 944 mCallback.onSeekTo((Long) msg.obj); 945 break; 946 case MSG_RATE: 947 mCallback.onSetRating((Rating) msg.obj); 948 break; 949 } 950 } 951 } 952} 953