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