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