1 2/* 3 * Copyright (C) 2014 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package android.support.v4.media.session; 19 20import android.app.Activity; 21import android.app.PendingIntent; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.media.AudioManager; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.Looper; 30import android.os.Message; 31import android.os.Parcel; 32import android.os.Parcelable; 33import android.os.RemoteCallbackList; 34import android.os.RemoteException; 35import android.os.ResultReceiver; 36import android.os.SystemClock; 37import android.support.v4.media.MediaDescriptionCompat; 38import android.support.v4.media.MediaMetadataCompat; 39import android.support.v4.media.RatingCompat; 40import android.support.v4.media.VolumeProviderCompat; 41import android.text.TextUtils; 42import android.util.Log; 43import android.view.KeyEvent; 44 45import java.lang.ref.WeakReference; 46import java.util.ArrayList; 47import java.util.List; 48 49/** 50 * Allows interaction with media controllers, volume keys, media buttons, and 51 * transport controls. 52 * <p> 53 * A MediaSession should be created when an app wants to publish media playback 54 * information or handle media keys. In general an app only needs one session 55 * for all playback, though multiple sessions can be created to provide finer 56 * grain controls of media. 57 * <p> 58 * Once a session is created the owner of the session may pass its 59 * {@link #getSessionToken() session token} to other processes to allow them to 60 * create a {@link MediaControllerCompat} to interact with the session. 61 * <p> 62 * To receive commands, media keys, and other events a {@link Callback} must be 63 * set with {@link #setCallback(Callback)}. 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 * MediaSessionCompat objects are not thread safe and all calls should be made 69 * from the same thread. 70 * <p> 71 * This is a helper for accessing features in 72 * {@link android.media.session.MediaSession} introduced after API level 4 in a 73 * backwards compatible fashion. 74 */ 75public class MediaSessionCompat { 76 private final MediaSessionImpl mImpl; 77 private final MediaControllerCompat mController; 78 private final ArrayList<OnActiveChangeListener> 79 mActiveListeners = new ArrayList<OnActiveChangeListener>(); 80 81 /** 82 * Set this flag on the session to indicate that it can handle media button 83 * events. 84 */ 85 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 86 87 /** 88 * Set this flag on the session to indicate that it handles transport 89 * control commands through its {@link Callback}. 90 */ 91 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 92 93 /** 94 * Creates a new session. 95 * 96 * @param context The context. 97 * @param tag A short name for debugging purposes. 98 * @param mediaButtonEventReceiver The component name for your receiver. 99 * This must be non-null to support platform versions earlier 100 * than {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 101 * @param mbrIntent The PendingIntent for your receiver component that 102 * handles media button events. This is optional and will be used 103 * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and 104 * later instead of the component name. 105 */ 106 public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver, 107 PendingIntent mbrIntent) { 108 if (context == null) { 109 throw new IllegalArgumentException("context must not be null"); 110 } 111 if (TextUtils.isEmpty(tag)) { 112 throw new IllegalArgumentException("tag must not be null or empty"); 113 } 114 115 if (android.os.Build.VERSION.SDK_INT >= 21) { 116 mImpl = new MediaSessionImplApi21(context, tag); 117 mImpl.setMediaButtonReceiver(mbrIntent); 118 } else { 119 mImpl = new MediaSessionImplBase(context, tag, mediaButtonEventReceiver, mbrIntent); 120 } 121 mController = new MediaControllerCompat(context, this); 122 } 123 124 private MediaSessionCompat(Context context, MediaSessionImpl impl) { 125 mImpl = impl; 126 mController = new MediaControllerCompat(context, this); 127 } 128 129 /** 130 * Add a callback to receive updates on for the MediaSession. This includes 131 * media button and volume events. The caller's thread will be used to post 132 * events. 133 * 134 * @param callback The callback object 135 */ 136 public void setCallback(Callback callback) { 137 setCallback(callback, null); 138 } 139 140 /** 141 * Set the callback to receive updates for the MediaSession. This includes 142 * media button and volume events. Set the callback to null to stop 143 * receiving events. 144 * 145 * @param callback The callback to receive updates on. 146 * @param handler The handler that events should be posted on. 147 */ 148 public void setCallback(Callback callback, Handler handler) { 149 mImpl.setCallback(callback, handler != null ? handler : new Handler()); 150 } 151 152 /** 153 * Set an intent for launching UI for this Session. This can be used as a 154 * quick link to an ongoing media screen. The intent should be for an 155 * activity that may be started using 156 * {@link Activity#startActivity(Intent)}. 157 * 158 * @param pi The intent to launch to show UI for this Session. 159 */ 160 public void setSessionActivity(PendingIntent pi) { 161 mImpl.setSessionActivity(pi); 162 } 163 164 /** 165 * Set a pending intent for your media button receiver to allow restarting 166 * playback after the session has been stopped. If your app is started in 167 * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via 168 * the pending intent. 169 * <p> 170 * This method will only work on 171 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier 172 * platform versions must include the media button receiver in the 173 * constructor. 174 * 175 * @param mbr The {@link PendingIntent} to send the media button event to. 176 */ 177 public void setMediaButtonReceiver(PendingIntent mbr) { 178 mImpl.setMediaButtonReceiver(mbr); 179 } 180 181 /** 182 * Set any flags for the session. 183 * 184 * @param flags The flags to set for this session. 185 */ 186 public void setFlags(int flags) { 187 mImpl.setFlags(flags); 188 } 189 190 /** 191 * Set the stream this session is playing on. This will affect the system's 192 * volume handling for this session. If {@link #setPlaybackToRemote} was 193 * previously called it will stop receiving volume commands and the system 194 * will begin sending volume changes to the appropriate stream. 195 * <p> 196 * By default sessions are on {@link AudioManager#STREAM_MUSIC}. 197 * 198 * @param stream The {@link AudioManager} stream this session is playing on. 199 */ 200 public void setPlaybackToLocal(int stream) { 201 mImpl.setPlaybackToLocal(stream); 202 } 203 204 /** 205 * Configure this session to use remote volume handling. This must be called 206 * to receive volume button events, otherwise the system will adjust the 207 * current stream volume for this session. If {@link #setPlaybackToLocal} 208 * was previously called that stream will stop receiving volume changes for 209 * this session. 210 * <p> 211 * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP} 212 * this will only allow an app to handle volume commands sent directly to 213 * the session by a {@link MediaControllerCompat}. System routing of volume 214 * keys will not use the volume provider. 215 * 216 * @param volumeProvider The provider that will handle volume changes. May 217 * not be null. 218 */ 219 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 220 if (volumeProvider == null) { 221 throw new IllegalArgumentException("volumeProvider may not be null!"); 222 } 223 mImpl.setPlaybackToRemote(volumeProvider); 224 } 225 226 /** 227 * Set if this session is currently active and ready to receive commands. If 228 * set to false your session's controller may not be discoverable. You must 229 * set the session to active before it can start receiving media button 230 * events or transport commands. 231 * <p> 232 * On platforms earlier than 233 * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, 234 * {@link #setMediaButtonReceiver(PendingIntent)} must be called before 235 * setting this to true. 236 * 237 * @param active Whether this session is active or not. 238 */ 239 public void setActive(boolean active) { 240 mImpl.setActive(active); 241 for (OnActiveChangeListener listener : mActiveListeners) { 242 listener.onActiveChanged(); 243 } 244 } 245 246 /** 247 * Get the current active state of this session. 248 * 249 * @return True if the session is active, false otherwise. 250 */ 251 public boolean isActive() { 252 return mImpl.isActive(); 253 } 254 255 /** 256 * Send a proprietary event to all MediaControllers listening to this 257 * Session. It's up to the Controller/Session owner to determine the meaning 258 * of any events. 259 * 260 * @param event The name of the event to send 261 * @param extras Any extras included with the event 262 */ 263 public void sendSessionEvent(String event, Bundle extras) { 264 if (TextUtils.isEmpty(event)) { 265 throw new IllegalArgumentException("event cannot be null or empty"); 266 } 267 mImpl.sendSessionEvent(event, extras); 268 } 269 270 /** 271 * This must be called when an app has finished performing playback. If 272 * playback is expected to start again shortly the session can be left open, 273 * but it must be released if your activity or service is being destroyed. 274 */ 275 public void release() { 276 mImpl.release(); 277 } 278 279 /** 280 * Retrieve a token object that can be used by apps to create a 281 * {@link MediaControllerCompat} for interacting with this session. The 282 * owner of the session is responsible for deciding how to distribute these 283 * tokens. 284 * <p> 285 * On platform versions before 286 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be 287 * used within your app as there is no way to guarantee other apps are using 288 * the same version of the support library. 289 * 290 * @return A token that can be used to create a media controller for this 291 * session. 292 */ 293 public Token getSessionToken() { 294 return mImpl.getSessionToken(); 295 } 296 297 /** 298 * Get a controller for this session. This is a convenience method to avoid 299 * having to cache your own controller in process. 300 * 301 * @return A controller for this session. 302 */ 303 public MediaControllerCompat getController() { 304 return mController; 305 } 306 307 /** 308 * Update the current playback state. 309 * 310 * @param state The current state of playback 311 */ 312 public void setPlaybackState(PlaybackStateCompat state) { 313 mImpl.setPlaybackState(state); 314 } 315 316 /** 317 * Update the current metadata. New metadata can be created using 318 * {@link android.media.MediaMetadata.Builder}. 319 * 320 * @param metadata The new metadata 321 */ 322 public void setMetadata(MediaMetadataCompat metadata) { 323 mImpl.setMetadata(metadata); 324 } 325 326 /** 327 * Update the list of items in the play queue. It is an ordered list and 328 * should contain the current item, and previous or upcoming items if they 329 * exist. Specify null if there is no current play queue. 330 * <p> 331 * The queue should be of reasonable size. If the play queue is unbounded 332 * within your app, it is better to send a reasonable amount in a sliding 333 * window instead. 334 * 335 * @param queue A list of items in the play queue. 336 */ 337 public void setQueue(List<QueueItem> queue) { 338 mImpl.setQueue(queue); 339 } 340 341 /** 342 * Set the title of the play queue. The UI should display this title along 343 * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album 344 * name. 345 * 346 * @param title The title of the play queue. 347 */ 348 public void setQueueTitle(CharSequence title) { 349 mImpl.setQueueTitle(title); 350 } 351 352 /** 353 * Set the style of rating used by this session. Apps trying to set the 354 * rating should use this style. Must be one of the following: 355 * <ul> 356 * <li>{@link RatingCompat#RATING_NONE}</li> 357 * <li>{@link RatingCompat#RATING_3_STARS}</li> 358 * <li>{@link RatingCompat#RATING_4_STARS}</li> 359 * <li>{@link RatingCompat#RATING_5_STARS}</li> 360 * <li>{@link RatingCompat#RATING_HEART}</li> 361 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 362 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 363 * </ul> 364 */ 365 public void setRatingType(int type) { 366 mImpl.setRatingType(type); 367 } 368 369 /** 370 * Set some extras that can be associated with the 371 * {@link MediaSessionCompat}. No assumptions should be made as to how a 372 * {@link MediaControllerCompat} will handle these extras. Keys should be 373 * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. 374 * 375 * @param extras The extras associated with the session. 376 */ 377 public void setExtras(Bundle extras) { 378 mImpl.setExtras(extras); 379 } 380 381 /** 382 * Gets the underlying framework {@link android.media.session.MediaSession} 383 * object. 384 * <p> 385 * This method is only supported on API 21+. 386 * </p> 387 * 388 * @return The underlying {@link android.media.session.MediaSession} object, 389 * or null if none. 390 */ 391 public Object getMediaSession() { 392 return mImpl.getMediaSession(); 393 } 394 395 /** 396 * Gets the underlying framework {@link android.media.RemoteControlClient} 397 * object. 398 * <p> 399 * This method is only supported on APIs 14-20. On API 21+ 400 * {@link #getMediaSession()} should be used instead. 401 * 402 * @return The underlying {@link android.media.RemoteControlClient} object, 403 * or null if none. 404 */ 405 public Object getRemoteControlClient() { 406 return mImpl.getRemoteControlClient(); 407 } 408 409 /** 410 * Adds a listener to be notified when the active status of this session 411 * changes. This is primarily used by the support library and should not be 412 * needed by apps. 413 * 414 * @param listener The listener to add. 415 */ 416 public void addOnActiveChangeListener(OnActiveChangeListener listener) { 417 if (listener == null) { 418 throw new IllegalArgumentException("Listener may not be null"); 419 } 420 mActiveListeners.add(listener); 421 } 422 423 /** 424 * Stops the listener from being notified when the active status of this 425 * session changes. 426 * 427 * @param listener The listener to remove. 428 */ 429 public void removeOnActiveChangeListener(OnActiveChangeListener listener) { 430 if (listener == null) { 431 throw new IllegalArgumentException("Listener may not be null"); 432 } 433 mActiveListeners.remove(listener); 434 } 435 436 /** 437 * Obtain a compat wrapper for an existing MediaSession. 438 * 439 * @param mediaSession The {@link android.media.session.MediaSession} to 440 * wrap. 441 * @return A compat wrapper for the provided session. 442 */ 443 public static MediaSessionCompat obtain(Context context, Object mediaSession) { 444 return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession)); 445 } 446 447 /** 448 * Receives transport controls, media buttons, and commands from controllers 449 * and the system. The callback may be set using {@link #setCallback}. 450 */ 451 public abstract static class Callback { 452 final Object mCallbackObj; 453 454 public Callback() { 455 if (android.os.Build.VERSION.SDK_INT >= 21) { 456 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21()); 457 } else { 458 mCallbackObj = null; 459 } 460 } 461 462 /** 463 * Called when a controller has sent a custom command to this session. 464 * The owner of the session may handle custom commands but is not 465 * required to. 466 * 467 * @param command The command name. 468 * @param extras Optional parameters for the command, may be null. 469 * @param cb A result receiver to which a result may be sent by the command, may be null. 470 */ 471 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 472 } 473 474 /** 475 * Override to handle media button events. 476 * 477 * @param mediaButtonEvent The media button event intent. 478 * @return True if the event was handled, false otherwise. 479 */ 480 public boolean onMediaButtonEvent(Intent mediaButtonEvent) { 481 return false; 482 } 483 484 /** 485 * Override to handle requests to begin playback. 486 */ 487 public void onPlay() { 488 } 489 490 /** 491 * Override to handle requests to play a specific mediaId that was 492 * provided by your app. 493 */ 494 public void onPlayFromMediaId(String mediaId, Bundle extras) { 495 } 496 497 /** 498 * Override to handle requests to begin playback from a search query. An 499 * empty query indicates that the app may play any music. The 500 * implementation should attempt to make a smart choice about what to 501 * play. 502 */ 503 public void onPlayFromSearch(String query, Bundle extras) { 504 } 505 506 /** 507 * Override to handle requests to play an item with a given id from the 508 * play queue. 509 */ 510 public void onSkipToQueueItem(long id) { 511 } 512 513 /** 514 * Override to handle requests to pause playback. 515 */ 516 public void onPause() { 517 } 518 519 /** 520 * Override to handle requests to skip to the next media item. 521 */ 522 public void onSkipToNext() { 523 } 524 525 /** 526 * Override to handle requests to skip to the previous media item. 527 */ 528 public void onSkipToPrevious() { 529 } 530 531 /** 532 * Override to handle requests to fast forward. 533 */ 534 public void onFastForward() { 535 } 536 537 /** 538 * Override to handle requests to rewind. 539 */ 540 public void onRewind() { 541 } 542 543 /** 544 * Override to handle requests to stop playback. 545 */ 546 public void onStop() { 547 } 548 549 /** 550 * Override to handle requests to seek to a specific position in ms. 551 * 552 * @param pos New position to move to, in milliseconds. 553 */ 554 public void onSeekTo(long pos) { 555 } 556 557 /** 558 * Override to handle the item being rated. 559 * 560 * @param rating 561 */ 562 public void onSetRating(RatingCompat rating) { 563 } 564 565 /** 566 * Called when a {@link MediaControllerCompat} wants a 567 * {@link PlaybackStateCompat.CustomAction} to be performed. 568 * 569 * @param action The action that was originally sent in the 570 * {@link PlaybackStateCompat.CustomAction}. 571 * @param extras Optional extras specified by the 572 * {@link MediaControllerCompat}. 573 */ 574 public void onCustomAction(String action, Bundle extras) { 575 } 576 577 private class StubApi21 implements MediaSessionCompatApi21.Callback { 578 579 @Override 580 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 581 Callback.this.onCommand(command, extras, cb); 582 } 583 584 @Override 585 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 586 return Callback.this.onMediaButtonEvent(mediaButtonIntent); 587 } 588 589 @Override 590 public void onPlay() { 591 Callback.this.onPlay(); 592 } 593 594 @Override 595 public void onPlayFromMediaId(String mediaId, Bundle extras) { 596 Callback.this.onPlayFromMediaId(mediaId, extras); 597 } 598 599 @Override 600 public void onPlayFromSearch(String search, Bundle extras) { 601 Callback.this.onPlayFromSearch(search, extras); 602 } 603 604 @Override 605 public void onSkipToQueueItem(long id) { 606 Callback.this.onSkipToQueueItem(id); 607 } 608 609 @Override 610 public void onPause() { 611 Callback.this.onPause(); 612 } 613 614 @Override 615 public void onSkipToNext() { 616 Callback.this.onSkipToNext(); 617 } 618 619 @Override 620 public void onSkipToPrevious() { 621 Callback.this.onSkipToPrevious(); 622 } 623 624 @Override 625 public void onFastForward() { 626 Callback.this.onFastForward(); 627 } 628 629 @Override 630 public void onRewind() { 631 Callback.this.onRewind(); 632 } 633 634 @Override 635 public void onStop() { 636 Callback.this.onStop(); 637 } 638 639 @Override 640 public void onSeekTo(long pos) { 641 Callback.this.onSeekTo(pos); 642 } 643 644 @Override 645 public void onSetRating(Object ratingObj) { 646 Callback.this.onSetRating(RatingCompat.fromRating(ratingObj)); 647 } 648 649 @Override 650 public void onCustomAction(String action, Bundle extras) { 651 Callback.this.onCustomAction(action, extras); 652 } 653 } 654 } 655 656 /** 657 * Represents an ongoing session. This may be passed to apps by the session 658 * owner to allow them to create a {@link MediaControllerCompat} to communicate with 659 * the session. 660 */ 661 public static final class Token implements Parcelable { 662 private final Object mInner; 663 664 Token(Object inner) { 665 mInner = inner; 666 } 667 668 /** 669 * Creates a compat Token from a framework 670 * {@link android.media.session.MediaSession.Token} object. 671 * <p> 672 * This method is only supported on 673 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 674 * </p> 675 * 676 * @param token The framework token object. 677 * @return A compat Token for use with {@link MediaControllerCompat}. 678 */ 679 public static Token fromToken(Object token) { 680 if (token == null || android.os.Build.VERSION.SDK_INT < 21) { 681 return null; 682 } 683 return new Token(MediaSessionCompatApi21.verifyToken(token)); 684 } 685 686 @Override 687 public int describeContents() { 688 return 0; 689 } 690 691 @Override 692 public void writeToParcel(Parcel dest, int flags) { 693 if (android.os.Build.VERSION.SDK_INT >= 21) { 694 dest.writeParcelable((Parcelable) mInner, flags); 695 } else { 696 dest.writeStrongBinder((IBinder) mInner); 697 } 698 } 699 700 /** 701 * Gets the underlying framework {@link android.media.session.MediaSession.Token} object. 702 * <p> 703 * This method is only supported on API 21+. 704 * </p> 705 * 706 * @return The underlying {@link android.media.session.MediaSession.Token} object, 707 * or null if none. 708 */ 709 public Object getToken() { 710 return mInner; 711 } 712 713 public static final Parcelable.Creator<Token> CREATOR 714 = new Parcelable.Creator<Token>() { 715 @Override 716 public Token createFromParcel(Parcel in) { 717 Object inner; 718 if (android.os.Build.VERSION.SDK_INT >= 21) { 719 inner = in.readParcelable(null); 720 } else { 721 inner = in.readStrongBinder(); 722 } 723 return new Token(inner); 724 } 725 726 @Override 727 public Token[] newArray(int size) { 728 return new Token[size]; 729 } 730 }; 731 } 732 733 /** 734 * A single item that is part of the play queue. It contains a description 735 * of the item and its id in the queue. 736 */ 737 public static final class QueueItem implements Parcelable { 738 /** 739 * This id is reserved. No items can be explicitly asigned this id. 740 */ 741 public static final int UNKNOWN_ID = -1; 742 743 private final MediaDescriptionCompat mDescription; 744 private final long mId; 745 746 private Object mItem; 747 748 /** 749 * Create a new {@link MediaSessionCompat.QueueItem}. 750 * 751 * @param description The {@link MediaDescriptionCompat} for this item. 752 * @param id An identifier for this item. It must be unique within the 753 * play queue and cannot be {@link #UNKNOWN_ID}. 754 */ 755 public QueueItem(MediaDescriptionCompat description, long id) { 756 this(null, description, id); 757 } 758 759 private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) { 760 if (description == null) { 761 throw new IllegalArgumentException("Description cannot be null."); 762 } 763 if (id == UNKNOWN_ID) { 764 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); 765 } 766 mDescription = description; 767 mId = id; 768 mItem = queueItem; 769 } 770 771 private QueueItem(Parcel in) { 772 mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in); 773 mId = in.readLong(); 774 } 775 776 /** 777 * Get the description for this item. 778 */ 779 public MediaDescriptionCompat getDescription() { 780 return mDescription; 781 } 782 783 /** 784 * Get the queue id for this item. 785 */ 786 public long getQueueId() { 787 return mId; 788 } 789 790 @Override 791 public void writeToParcel(Parcel dest, int flags) { 792 mDescription.writeToParcel(dest, flags); 793 dest.writeLong(mId); 794 } 795 796 @Override 797 public int describeContents() { 798 return 0; 799 } 800 801 /** 802 * Get the underlying 803 * {@link android.media.session.MediaSession.QueueItem}. 804 * <p> 805 * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null 806 * is returned. 807 * 808 * @return The underlying 809 * {@link android.media.session.MediaSession.QueueItem} or null. 810 */ 811 public Object getQueueItem() { 812 if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) { 813 return mItem; 814 } 815 mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(), 816 mId); 817 return mItem; 818 } 819 820 /** 821 * Obtain a compat wrapper for an existing QueueItem. 822 * 823 * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to 824 * wrap. 825 * @return A compat wrapper for the provided item. 826 */ 827 public static QueueItem obtain(Object queueItem) { 828 Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem); 829 MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription( 830 descriptionObj); 831 long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem); 832 return new QueueItem(queueItem, description, id); 833 } 834 835 public static final Creator<MediaSessionCompat.QueueItem> 836 CREATOR = new Creator<MediaSessionCompat.QueueItem>() { 837 838 @Override 839 public MediaSessionCompat.QueueItem createFromParcel(Parcel p) { 840 return new MediaSessionCompat.QueueItem(p); 841 } 842 843 @Override 844 public MediaSessionCompat.QueueItem[] newArray(int size) { 845 return new MediaSessionCompat.QueueItem[size]; 846 } 847 }; 848 849 @Override 850 public String toString() { 851 return "MediaSession.QueueItem {" + 852 "Description=" + mDescription + 853 ", Id=" + mId + " }"; 854 } 855 } 856 857 /** 858 * This is a wrapper for {@link ResultReceiver} for sending over aidl 859 * interfaces. The framework version was not exposed to aidls until 860 * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 861 */ 862 static final class ResultReceiverWrapper implements Parcelable { 863 private ResultReceiver mResultReceiver; 864 865 public ResultReceiverWrapper(ResultReceiver resultReceiver) { 866 mResultReceiver = resultReceiver; 867 } 868 869 ResultReceiverWrapper(Parcel in) { 870 mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in); 871 } 872 873 public static final Creator<ResultReceiverWrapper> 874 CREATOR = new Creator<ResultReceiverWrapper>() { 875 @Override 876 public ResultReceiverWrapper createFromParcel(Parcel p) { 877 return new ResultReceiverWrapper(p); 878 } 879 880 @Override 881 public ResultReceiverWrapper[] newArray(int size) { 882 return new ResultReceiverWrapper[size]; 883 } 884 }; 885 886 @Override 887 public int describeContents() { 888 return 0; 889 } 890 891 @Override 892 public void writeToParcel(Parcel dest, int flags) { 893 mResultReceiver.writeToParcel(dest, flags); 894 } 895 } 896 897 public interface OnActiveChangeListener { 898 void onActiveChanged(); 899 } 900 901 interface MediaSessionImpl { 902 void setCallback(Callback callback, Handler handler); 903 void setFlags(int flags); 904 void setPlaybackToLocal(int stream); 905 void setPlaybackToRemote(VolumeProviderCompat volumeProvider); 906 void setActive(boolean active); 907 boolean isActive(); 908 void sendSessionEvent(String event, Bundle extras); 909 void release(); 910 Token getSessionToken(); 911 void setPlaybackState(PlaybackStateCompat state); 912 void setMetadata(MediaMetadataCompat metadata); 913 914 void setSessionActivity(PendingIntent pi); 915 916 void setMediaButtonReceiver(PendingIntent mbr); 917 void setQueue(List<QueueItem> queue); 918 void setQueueTitle(CharSequence title); 919 920 void setRatingType(int type); 921 void setExtras(Bundle extras); 922 923 Object getMediaSession(); 924 925 Object getRemoteControlClient(); 926 } 927 928 // TODO: compatibility implementation 929 static class MediaSessionImplBase implements MediaSessionImpl { 930 private final Context mContext; 931 private final ComponentName mComponentName; 932 private final PendingIntent mMediaButtonEventReceiver; 933 private final Object mRccObj; 934 private final MediaSessionStub mStub; 935 private final Token mToken; 936 private final MessageHandler mHandler; 937 private final String mPackageName; 938 private final String mTag; 939 private final AudioManager mAudioManager; 940 941 private final Object mLock = new Object(); 942 private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks 943 = new RemoteCallbackList<IMediaControllerCallback>(); 944 945 private boolean mDestroyed = false; 946 private boolean mIsActive = false; 947 private boolean mIsRccRegistered = false; 948 private boolean mIsMbrRegistered = false; 949 private Callback mCallback; 950 951 private int mFlags; 952 953 private MediaMetadataCompat mMetadata; 954 private PlaybackStateCompat mState; 955 private PendingIntent mSessionActivity; 956 private List<QueueItem> mQueue; 957 private CharSequence mQueueTitle; 958 private int mRatingType; 959 private Bundle mExtras; 960 961 private int mVolumeType; 962 private int mLocalStream; 963 private VolumeProviderCompat mVolumeProvider; 964 965 private VolumeProviderCompat.Callback mVolumeCallback 966 = new VolumeProviderCompat.Callback() { 967 @Override 968 public void onVolumeChanged(VolumeProviderCompat volumeProvider) { 969 if (mVolumeProvider != volumeProvider) { 970 return; 971 } 972 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 973 volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(), 974 volumeProvider.getCurrentVolume()); 975 sendVolumeInfoChanged(info); 976 } 977 }; 978 979 public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent, 980 PendingIntent mbr) { 981 if (mbrComponent == null) { 982 throw new IllegalArgumentException( 983 "MediaButtonReceiver component may not be null."); 984 } 985 if (mbr == null) { 986 // construct a PendingIntent for the media button 987 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 988 // the associated intent will be handled by the component being 989 // registered 990 mediaButtonIntent.setComponent(mbrComponent); 991 mbr = PendingIntent.getBroadcast(context, 992 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */); 993 } 994 mContext = context; 995 mPackageName = context.getPackageName(); 996 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 997 mTag = tag; 998 mComponentName = mbrComponent; 999 mMediaButtonEventReceiver = mbr; 1000 mStub = new MediaSessionStub(); 1001 mToken = new Token(mStub); 1002 mHandler = new MessageHandler(Looper.myLooper()); 1003 1004 mRatingType = RatingCompat.RATING_NONE; 1005 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL; 1006 mLocalStream = AudioManager.STREAM_MUSIC; 1007 if (android.os.Build.VERSION.SDK_INT >= 14) { 1008 mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr); 1009 } else { 1010 mRccObj = null; 1011 } 1012 } 1013 1014 @Override 1015 public void setCallback(final Callback callback, Handler handler) { 1016 if (callback == mCallback) { 1017 return; 1018 } 1019 if (callback == null || android.os.Build.VERSION.SDK_INT < 18) { 1020 // There's nothing to register on API < 18 since media buttons 1021 // all go through the media button receiver 1022 if (android.os.Build.VERSION.SDK_INT >= 18) { 1023 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null); 1024 } 1025 if (android.os.Build.VERSION.SDK_INT >= 19) { 1026 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null); 1027 } 1028 } else { 1029 if (handler == null) { 1030 handler = new Handler(); 1031 } 1032 MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() { 1033 @Override 1034 public void onStop() { 1035 callback.onStop(); 1036 } 1037 1038 @Override 1039 public void onSkipToPrevious() { 1040 callback.onSkipToPrevious(); 1041 } 1042 1043 @Override 1044 public void onSkipToNext() { 1045 callback.onSkipToNext(); 1046 } 1047 1048 @Override 1049 public void onSetRating(Object ratingObj) { 1050 callback.onSetRating(RatingCompat.fromRating(ratingObj)); 1051 } 1052 1053 @Override 1054 public void onSeekTo(long pos) { 1055 callback.onSeekTo(pos); 1056 } 1057 1058 @Override 1059 public void onRewind() { 1060 callback.onRewind(); 1061 } 1062 1063 @Override 1064 public void onPlay() { 1065 callback.onPlay(); 1066 } 1067 1068 @Override 1069 public void onPause() { 1070 callback.onPause(); 1071 } 1072 1073 @Override 1074 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 1075 return callback.onMediaButtonEvent(mediaButtonIntent); 1076 } 1077 1078 @Override 1079 public void onFastForward() { 1080 callback.onFastForward(); 1081 } 1082 1083 @Override 1084 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 1085 callback.onCommand(command, extras, cb); 1086 } 1087 }; 1088 if (android.os.Build.VERSION.SDK_INT >= 18) { 1089 Object onPositionUpdateObj = MediaSessionCompatApi18 1090 .createPlaybackPositionUpdateListener(cb14); 1091 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, 1092 onPositionUpdateObj); 1093 } 1094 if (android.os.Build.VERSION.SDK_INT >= 19) { 1095 Object onMetadataUpdateObj = MediaSessionCompatApi19 1096 .createMetadataUpdateListener(cb14); 1097 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, 1098 onMetadataUpdateObj); 1099 } 1100 } 1101 mCallback = callback; 1102 } 1103 1104 @Override 1105 public void setFlags(int flags) { 1106 synchronized (mLock) { 1107 mFlags = flags; 1108 } 1109 update(); 1110 } 1111 1112 @Override 1113 public void setPlaybackToLocal(int stream) { 1114 if (mVolumeProvider != null) { 1115 mVolumeProvider.setCallback(null); 1116 } 1117 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL; 1118 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 1119 VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 1120 mAudioManager.getStreamMaxVolume(mLocalStream), 1121 mAudioManager.getStreamVolume(mLocalStream)); 1122 sendVolumeInfoChanged(info); 1123 } 1124 1125 @Override 1126 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 1127 if (volumeProvider == null) { 1128 throw new IllegalArgumentException("volumeProvider may not be null"); 1129 } 1130 if (mVolumeProvider != null) { 1131 mVolumeProvider.setCallback(null); 1132 } 1133 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE; 1134 mVolumeProvider = volumeProvider; 1135 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 1136 mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(), 1137 mVolumeProvider.getCurrentVolume()); 1138 sendVolumeInfoChanged(info); 1139 1140 volumeProvider.setCallback(mVolumeCallback); 1141 } 1142 1143 @Override 1144 public void setActive(boolean active) { 1145 if (active == mIsActive) { 1146 return; 1147 } 1148 mIsActive = active; 1149 if (update()) { 1150 setMetadata(mMetadata); 1151 setPlaybackState(mState); 1152 } 1153 } 1154 1155 @Override 1156 public boolean isActive() { 1157 return mIsActive; 1158 } 1159 1160 @Override 1161 public void sendSessionEvent(String event, Bundle extras) { 1162 sendEvent(event, extras); 1163 } 1164 1165 @Override 1166 public void release() { 1167 mIsActive = false; 1168 mDestroyed = true; 1169 update(); 1170 sendSessionDestroyed(); 1171 } 1172 1173 @Override 1174 public Token getSessionToken() { 1175 return mToken; 1176 } 1177 1178 @Override 1179 public void setPlaybackState(PlaybackStateCompat state) { 1180 synchronized (mLock) { 1181 mState = state; 1182 } 1183 sendState(state); 1184 if (!mIsActive) { 1185 // Don't set the state until after the RCC is registered 1186 return; 1187 } 1188 if (state == null) { 1189 if (android.os.Build.VERSION.SDK_INT >= 14) { 1190 MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE); 1191 } 1192 } else { 1193 if (android.os.Build.VERSION.SDK_INT >= 18) { 1194 MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(), 1195 state.getPlaybackSpeed(), state.getLastPositionUpdateTime()); 1196 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1197 MediaSessionCompatApi14.setState(mRccObj, state.getState()); 1198 } 1199 } 1200 } 1201 1202 @Override 1203 public void setMetadata(MediaMetadataCompat metadata) { 1204 synchronized (mLock) { 1205 mMetadata = metadata; 1206 } 1207 sendMetadata(metadata); 1208 if (!mIsActive) { 1209 // Don't set metadata until after the rcc has been registered 1210 return; 1211 } 1212 if (android.os.Build.VERSION.SDK_INT >= 19) { 1213 boolean canRate = mState != null 1214 && (mState.getActions() & PlaybackStateCompat.ACTION_SET_RATING) != 0; 1215 MediaSessionCompatApi19.setMetadata(mRccObj, 1216 metadata == null ? null : metadata.getBundle(), canRate); 1217 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1218 MediaSessionCompatApi14.setMetadata(mRccObj, 1219 metadata == null ? null : metadata.getBundle()); 1220 } 1221 } 1222 1223 @Override 1224 public void setSessionActivity(PendingIntent pi) { 1225 synchronized (mLock) { 1226 mSessionActivity = pi; 1227 } 1228 } 1229 1230 @Override 1231 public void setMediaButtonReceiver(PendingIntent mbr) { 1232 // Do nothing, changing this is not supported before API 21. 1233 } 1234 1235 @Override 1236 public void setQueue(List<QueueItem> queue) { 1237 mQueue = queue; 1238 sendQueue(queue); 1239 } 1240 1241 @Override 1242 public void setQueueTitle(CharSequence title) { 1243 mQueueTitle = title; 1244 sendQueueTitle(title); 1245 } 1246 1247 @Override 1248 public Object getMediaSession() { 1249 return null; 1250 } 1251 1252 @Override 1253 public Object getRemoteControlClient() { 1254 return mRccObj; 1255 } 1256 1257 @Override 1258 public void setRatingType(int type) { 1259 mRatingType = type; 1260 } 1261 1262 @Override 1263 public void setExtras(Bundle extras) { 1264 mExtras = extras; 1265 } 1266 1267 // Registers/unregisters the RCC and MediaButtonEventReceiver as needed. 1268 private boolean update() { 1269 boolean registeredRcc = false; 1270 if (mIsActive) { 1271 // On API 8+ register a MBR if it's supported, unregister it 1272 // if support was removed. 1273 if (android.os.Build.VERSION.SDK_INT >= 8) { 1274 if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) { 1275 if (android.os.Build.VERSION.SDK_INT >= 18) { 1276 MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext, 1277 mMediaButtonEventReceiver); 1278 } else { 1279 MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext, 1280 mComponentName); 1281 } 1282 mIsMbrRegistered = true; 1283 } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) { 1284 if (android.os.Build.VERSION.SDK_INT >= 18) { 1285 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1286 mMediaButtonEventReceiver); 1287 } else { 1288 MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext, 1289 mComponentName); 1290 } 1291 mIsMbrRegistered = false; 1292 } 1293 } 1294 // On API 14+ register a RCC if it's supported, unregister it if 1295 // not. 1296 if (android.os.Build.VERSION.SDK_INT >= 14) { 1297 if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) { 1298 MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj); 1299 mIsRccRegistered = true; 1300 registeredRcc = true; 1301 } else if (mIsRccRegistered 1302 && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) { 1303 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1304 mIsRccRegistered = false; 1305 } 1306 } 1307 } else { 1308 // When inactive remove any registered components. 1309 if (mIsMbrRegistered) { 1310 if (android.os.Build.VERSION.SDK_INT >= 18) { 1311 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1312 mMediaButtonEventReceiver); 1313 } else { 1314 MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext, 1315 mComponentName); 1316 } 1317 mIsMbrRegistered = false; 1318 } 1319 if (mIsRccRegistered) { 1320 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1321 mIsRccRegistered = false; 1322 } 1323 } 1324 return registeredRcc; 1325 } 1326 1327 private void adjustVolume(int direction, int flags) { 1328 if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1329 if (mVolumeProvider != null) { 1330 mVolumeProvider.onAdjustVolume(direction); 1331 } 1332 } else { 1333 mAudioManager.adjustStreamVolume(direction, mLocalStream, flags); 1334 } 1335 } 1336 1337 private void setVolumeTo(int value, int flags) { 1338 if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1339 if (mVolumeProvider != null) { 1340 mVolumeProvider.onSetVolumeTo(value); 1341 } 1342 } else { 1343 mAudioManager.setStreamVolume(mLocalStream, value, flags); 1344 } 1345 } 1346 1347 private PlaybackStateCompat getStateWithUpdatedPosition() { 1348 PlaybackStateCompat state; 1349 long duration = -1; 1350 synchronized (mLock) { 1351 state = mState; 1352 if (mMetadata != null 1353 && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) { 1354 duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION); 1355 } 1356 } 1357 1358 PlaybackStateCompat result = null; 1359 if (state != null) { 1360 if (state.getState() == PlaybackStateCompat.STATE_PLAYING 1361 || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING 1362 || state.getState() == PlaybackStateCompat.STATE_REWINDING) { 1363 long updateTime = state.getLastPositionUpdateTime(); 1364 long currentTime = SystemClock.elapsedRealtime(); 1365 if (updateTime > 0) { 1366 long position = (long) (state.getPlaybackSpeed() 1367 * (currentTime - updateTime)) + state.getPosition(); 1368 if (duration >= 0 && position > duration) { 1369 position = duration; 1370 } else if (position < 0) { 1371 position = 0; 1372 } 1373 PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder( 1374 state); 1375 builder.setState(state.getState(), position, state.getPlaybackSpeed(), 1376 currentTime); 1377 result = builder.build(); 1378 } 1379 } 1380 } 1381 return result == null ? state : result; 1382 } 1383 1384 private void sendVolumeInfoChanged(ParcelableVolumeInfo info) { 1385 int size = mControllerCallbacks.beginBroadcast(); 1386 for (int i = size - 1; i >= 0; i--) { 1387 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1388 try { 1389 cb.onVolumeInfoChanged(info); 1390 } catch (RemoteException e) { 1391 } 1392 } 1393 mControllerCallbacks.finishBroadcast(); 1394 } 1395 1396 private void sendSessionDestroyed() { 1397 int size = mControllerCallbacks.beginBroadcast(); 1398 for (int i = size - 1; i >= 0; i--) { 1399 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1400 try { 1401 cb.onSessionDestroyed();; 1402 } catch (RemoteException e) { 1403 } 1404 } 1405 mControllerCallbacks.finishBroadcast(); 1406 mControllerCallbacks.kill(); 1407 } 1408 1409 private void sendEvent(String event, Bundle extras) { 1410 int size = mControllerCallbacks.beginBroadcast(); 1411 for (int i = size - 1; i >= 0; i--) { 1412 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1413 try { 1414 cb.onEvent(event, extras); 1415 } catch (RemoteException e) { 1416 } 1417 } 1418 mControllerCallbacks.finishBroadcast(); 1419 } 1420 1421 private void sendState(PlaybackStateCompat state) { 1422 int size = mControllerCallbacks.beginBroadcast(); 1423 for (int i = size - 1; i >= 0; i--) { 1424 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1425 try { 1426 cb.onPlaybackStateChanged(state); 1427 } catch (RemoteException e) { 1428 } 1429 } 1430 mControllerCallbacks.finishBroadcast(); 1431 } 1432 1433 private void sendMetadata(MediaMetadataCompat metadata) { 1434 int size = mControllerCallbacks.beginBroadcast(); 1435 for (int i = size - 1; i >= 0; i--) { 1436 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1437 try { 1438 cb.onMetadataChanged(metadata); 1439 } catch (RemoteException e) { 1440 } 1441 } 1442 mControllerCallbacks.finishBroadcast(); 1443 } 1444 1445 private void sendQueue(List<QueueItem> queue) { 1446 int size = mControllerCallbacks.beginBroadcast(); 1447 for (int i = size - 1; i >= 0; i--) { 1448 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1449 try { 1450 cb.onQueueChanged(queue); 1451 } catch (RemoteException e) { 1452 } 1453 } 1454 mControllerCallbacks.finishBroadcast(); 1455 } 1456 1457 private void sendQueueTitle(CharSequence queueTitle) { 1458 int size = mControllerCallbacks.beginBroadcast(); 1459 for (int i = size - 1; i >= 0; i--) { 1460 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1461 try { 1462 cb.onQueueTitleChanged(queueTitle); 1463 } catch (RemoteException e) { 1464 } 1465 } 1466 mControllerCallbacks.finishBroadcast(); 1467 } 1468 1469 class MediaSessionStub extends IMediaSession.Stub { 1470 @Override 1471 public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) { 1472 mHandler.post(MessageHandler.MSG_COMMAND, 1473 new Command(command, args, cb.mResultReceiver)); 1474 } 1475 1476 @Override 1477 public boolean sendMediaButton(KeyEvent mediaButton) { 1478 boolean handlesMediaButtons = 1479 (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0; 1480 if (handlesMediaButtons) { 1481 mHandler.post(MessageHandler.MSG_MEDIA_BUTTON, mediaButton); 1482 } 1483 return handlesMediaButtons; 1484 } 1485 1486 @Override 1487 public void registerCallbackListener(IMediaControllerCallback cb) { 1488 // If this session is already destroyed tell the caller and 1489 // don't add them. 1490 if (mDestroyed) { 1491 try { 1492 cb.onSessionDestroyed(); 1493 } catch (Exception e) { 1494 // ignored 1495 } 1496 return; 1497 } 1498 mControllerCallbacks.register(cb); 1499 } 1500 1501 @Override 1502 public void unregisterCallbackListener(IMediaControllerCallback cb) { 1503 mControllerCallbacks.unregister(cb); 1504 } 1505 1506 @Override 1507 public String getPackageName() { 1508 // mPackageName is final so doesn't need synchronize block 1509 return mPackageName; 1510 } 1511 1512 @Override 1513 public String getTag() { 1514 // mTag is final so doesn't need synchronize block 1515 return mTag; 1516 } 1517 1518 @Override 1519 public PendingIntent getLaunchPendingIntent() { 1520 synchronized (mLock) { 1521 return mSessionActivity; 1522 } 1523 } 1524 1525 @Override 1526 public long getFlags() { 1527 synchronized (mLock) { 1528 return mFlags; 1529 } 1530 } 1531 1532 @Override 1533 public ParcelableVolumeInfo getVolumeAttributes() { 1534 int controlType; 1535 int max; 1536 int current; 1537 int stream; 1538 int volumeType; 1539 synchronized (mLock) { 1540 volumeType = mVolumeType; 1541 stream = mLocalStream; 1542 VolumeProviderCompat vp = mVolumeProvider; 1543 if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1544 controlType = vp.getVolumeControl(); 1545 max = vp.getMaxVolume(); 1546 current = vp.getCurrentVolume(); 1547 } else { 1548 controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 1549 max = mAudioManager.getStreamMaxVolume(stream); 1550 current = mAudioManager.getStreamVolume(stream); 1551 } 1552 } 1553 return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current); 1554 } 1555 1556 @Override 1557 public void adjustVolume(int direction, int flags, String packageName) { 1558 MediaSessionImplBase.this.adjustVolume(direction, flags); 1559 } 1560 1561 @Override 1562 public void setVolumeTo(int value, int flags, String packageName) { 1563 MediaSessionImplBase.this.setVolumeTo(value, flags); 1564 } 1565 1566 @Override 1567 public void play() throws RemoteException { 1568 mHandler.post(MessageHandler.MSG_PLAY); 1569 } 1570 1571 @Override 1572 public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { 1573 mHandler.post(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); 1574 } 1575 1576 @Override 1577 public void playFromSearch(String query, Bundle extras) throws RemoteException { 1578 mHandler.post(MessageHandler.MSG_PLAY_SEARCH, query, extras); 1579 } 1580 1581 @Override 1582 public void skipToQueueItem(long id) { 1583 mHandler.post(MessageHandler.MSG_SKIP_TO_ITEM, id); 1584 } 1585 1586 @Override 1587 public void pause() throws RemoteException { 1588 mHandler.post(MessageHandler.MSG_PAUSE); 1589 } 1590 1591 @Override 1592 public void stop() throws RemoteException { 1593 mHandler.post(MessageHandler.MSG_STOP); 1594 } 1595 1596 @Override 1597 public void next() throws RemoteException { 1598 mHandler.post(MessageHandler.MSG_NEXT); 1599 } 1600 1601 @Override 1602 public void previous() throws RemoteException { 1603 mHandler.post(MessageHandler.MSG_PREVIOUS); 1604 } 1605 1606 @Override 1607 public void fastForward() throws RemoteException { 1608 mHandler.post(MessageHandler.MSG_FAST_FORWARD); 1609 } 1610 1611 @Override 1612 public void rewind() throws RemoteException { 1613 mHandler.post(MessageHandler.MSG_REWIND); 1614 } 1615 1616 @Override 1617 public void seekTo(long pos) throws RemoteException { 1618 mHandler.post(MessageHandler.MSG_SEEK_TO, pos); 1619 } 1620 1621 @Override 1622 public void rate(RatingCompat rating) throws RemoteException { 1623 mHandler.post(MessageHandler.MSG_RATE, rating); 1624 } 1625 1626 @Override 1627 public void sendCustomAction(String action, Bundle args) 1628 throws RemoteException { 1629 mHandler.post(MessageHandler.MSG_CUSTOM_ACTION, action, args); 1630 } 1631 1632 @Override 1633 public MediaMetadataCompat getMetadata() { 1634 return mMetadata; 1635 } 1636 1637 @Override 1638 public PlaybackStateCompat getPlaybackState() { 1639 return getStateWithUpdatedPosition(); 1640 } 1641 1642 @Override 1643 public List<QueueItem> getQueue() { 1644 synchronized (mLock) { 1645 return mQueue; 1646 } 1647 } 1648 1649 @Override 1650 public CharSequence getQueueTitle() { 1651 return mQueueTitle; 1652 } 1653 1654 @Override 1655 public Bundle getExtras() { 1656 synchronized (mLock) { 1657 return mExtras; 1658 } 1659 } 1660 1661 @Override 1662 public int getRatingType() { 1663 return mRatingType; 1664 } 1665 1666 @Override 1667 public boolean isTransportControlEnabled() { 1668 return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0; 1669 } 1670 } 1671 1672 private static final class Command { 1673 public final String command; 1674 public final Bundle extras; 1675 public final ResultReceiver stub; 1676 1677 public Command(String command, Bundle extras, ResultReceiver stub) { 1678 this.command = command; 1679 this.extras = extras; 1680 this.stub = stub; 1681 } 1682 } 1683 1684 private class MessageHandler extends Handler { 1685 1686 private static final int MSG_PLAY = 1; 1687 private static final int MSG_PLAY_MEDIA_ID = 2; 1688 private static final int MSG_PLAY_SEARCH = 3; 1689 private static final int MSG_SKIP_TO_ITEM = 4; 1690 private static final int MSG_PAUSE = 5; 1691 private static final int MSG_STOP = 6; 1692 private static final int MSG_NEXT = 7; 1693 private static final int MSG_PREVIOUS = 8; 1694 private static final int MSG_FAST_FORWARD = 9; 1695 private static final int MSG_REWIND = 10; 1696 private static final int MSG_SEEK_TO = 11; 1697 private static final int MSG_RATE = 12; 1698 private static final int MSG_CUSTOM_ACTION = 13; 1699 private static final int MSG_MEDIA_BUTTON = 14; 1700 private static final int MSG_COMMAND = 15; 1701 private static final int MSG_ADJUST_VOLUME = 16; 1702 private static final int MSG_SET_VOLUME = 17; 1703 1704 public MessageHandler(Looper looper) { 1705 super(looper); 1706 } 1707 1708 public void post(int what, Object obj, Bundle bundle) { 1709 Message msg = obtainMessage(what, obj); 1710 msg.setData(bundle); 1711 msg.sendToTarget(); 1712 } 1713 1714 public void post(int what, Object obj) { 1715 obtainMessage(what, obj).sendToTarget(); 1716 } 1717 1718 public void post(int what) { 1719 post(what, null); 1720 } 1721 1722 public void post(int what, Object obj, int arg1) { 1723 obtainMessage(what, arg1, 0, obj).sendToTarget(); 1724 } 1725 1726 @Override 1727 public void handleMessage(Message msg) { 1728 if (mCallback == null) { 1729 return; 1730 } 1731 switch (msg.what) { 1732 case MSG_PLAY: 1733 mCallback.onPlay(); 1734 break; 1735 case MSG_PLAY_MEDIA_ID: 1736 mCallback.onPlayFromMediaId((String) msg.obj, msg.getData()); 1737 break; 1738 case MSG_PLAY_SEARCH: 1739 mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); 1740 break; 1741 case MSG_SKIP_TO_ITEM: 1742 mCallback.onSkipToQueueItem((Long) msg.obj); 1743 break; 1744 case MSG_PAUSE: 1745 mCallback.onPause(); 1746 break; 1747 case MSG_STOP: 1748 mCallback.onStop(); 1749 break; 1750 case MSG_NEXT: 1751 mCallback.onSkipToNext(); 1752 break; 1753 case MSG_PREVIOUS: 1754 mCallback.onSkipToPrevious(); 1755 break; 1756 case MSG_FAST_FORWARD: 1757 mCallback.onFastForward(); 1758 break; 1759 case MSG_REWIND: 1760 mCallback.onRewind(); 1761 break; 1762 case MSG_SEEK_TO: 1763 mCallback.onSeekTo((Long) msg.obj); 1764 break; 1765 case MSG_RATE: 1766 mCallback.onSetRating((RatingCompat) msg.obj); 1767 break; 1768 case MSG_CUSTOM_ACTION: 1769 mCallback.onCustomAction((String) msg.obj, msg.getData()); 1770 break; 1771 case MSG_MEDIA_BUTTON: 1772 mCallback.onMediaButtonEvent((Intent) msg.obj); 1773 break; 1774 case MSG_COMMAND: 1775 Command cmd = (Command) msg.obj; 1776 mCallback.onCommand(cmd.command, cmd.extras, cmd.stub); 1777 break; 1778 case MSG_ADJUST_VOLUME: 1779 adjustVolume((int) msg.obj, 0); 1780 break; 1781 case MSG_SET_VOLUME: 1782 setVolumeTo((int) msg.obj, 0); 1783 break; 1784 } 1785 } 1786 } 1787 } 1788 1789 static class MediaSessionImplApi21 implements MediaSessionImpl { 1790 private final Object mSessionObj; 1791 private final Token mToken; 1792 1793 private PendingIntent mMediaButtonIntent; 1794 1795 public MediaSessionImplApi21(Context context, String tag) { 1796 mSessionObj = MediaSessionCompatApi21.createSession(context, tag); 1797 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj)); 1798 } 1799 1800 public MediaSessionImplApi21(Object mediaSession) { 1801 mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession); 1802 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj)); 1803 } 1804 1805 @Override 1806 public void setCallback(Callback callback, Handler handler) { 1807 MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler); 1808 } 1809 1810 @Override 1811 public void setFlags(int flags) { 1812 MediaSessionCompatApi21.setFlags(mSessionObj, flags); 1813 } 1814 1815 @Override 1816 public void setPlaybackToLocal(int stream) { 1817 MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream); 1818 } 1819 1820 @Override 1821 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 1822 MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj, 1823 volumeProvider.getVolumeProvider()); 1824 } 1825 1826 @Override 1827 public void setActive(boolean active) { 1828 MediaSessionCompatApi21.setActive(mSessionObj, active); 1829 } 1830 1831 @Override 1832 public boolean isActive() { 1833 return MediaSessionCompatApi21.isActive(mSessionObj); 1834 } 1835 1836 @Override 1837 public void sendSessionEvent(String event, Bundle extras) { 1838 MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras); 1839 } 1840 1841 @Override 1842 public void release() { 1843 MediaSessionCompatApi21.release(mSessionObj); 1844 } 1845 1846 @Override 1847 public Token getSessionToken() { 1848 return mToken; 1849 } 1850 1851 @Override 1852 public void setPlaybackState(PlaybackStateCompat state) { 1853 MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState()); 1854 } 1855 1856 @Override 1857 public void setMetadata(MediaMetadataCompat metadata) { 1858 MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata()); 1859 } 1860 1861 @Override 1862 public void setSessionActivity(PendingIntent pi) { 1863 MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi); 1864 } 1865 1866 @Override 1867 public void setMediaButtonReceiver(PendingIntent mbr) { 1868 mMediaButtonIntent = mbr; 1869 MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr); 1870 } 1871 1872 @Override 1873 public void setQueue(List<QueueItem> queue) { 1874 List<Object> queueObjs = null; 1875 if (queue != null) { 1876 queueObjs = new ArrayList<Object>(); 1877 for (QueueItem item : queue) { 1878 queueObjs.add(item.getQueueItem()); 1879 } 1880 } 1881 MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs); 1882 } 1883 1884 @Override 1885 public void setQueueTitle(CharSequence title) { 1886 MediaSessionCompatApi21.setQueueTitle(mSessionObj, title); 1887 } 1888 1889 @Override 1890 public void setRatingType(int type) { 1891 if (android.os.Build.VERSION.SDK_INT < 22) { 1892 // TODO figure out 21 implementation 1893 } else { 1894 MediaSessionCompatApi22.setRatingType(mSessionObj, type); 1895 } 1896 } 1897 1898 @Override 1899 public void setExtras(Bundle extras) { 1900 MediaSessionCompatApi21.setExtras(mSessionObj, extras); 1901 } 1902 1903 @Override 1904 public Object getMediaSession() { 1905 return mSessionObj; 1906 } 1907 1908 @Override 1909 public Object getRemoteControlClient() { 1910 return null; 1911 } 1912 } 1913} 1914