MediaSessionCompat.java revision 5c9469e010106467791b47b0fa83efda84491a21
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.Parcel; 29import android.os.Parcelable; 30import android.os.ResultReceiver; 31import android.support.v4.media.MediaDescriptionCompat; 32import android.support.v4.media.MediaMetadataCompat; 33import android.support.v4.media.RatingCompat; 34import android.support.v4.media.VolumeProviderCompat; 35import android.text.TextUtils; 36 37import java.util.ArrayList; 38import java.util.List; 39 40/** 41 * Allows interaction with media controllers, volume keys, media buttons, and 42 * transport controls. 43 * <p> 44 * A MediaSession should be created when an app wants to publish media playback 45 * information or handle media keys. In general an app only needs one session 46 * for all playback, though multiple sessions can be created to provide finer 47 * grain controls of media. 48 * <p> 49 * Once a session is created the owner of the session may pass its 50 * {@link #getSessionToken() session token} to other processes to allow them to 51 * create a {@link MediaControllerCompat} to interact with the session. 52 * <p> 53 * To receive commands, media keys, and other events a {@link Callback} must be 54 * set with {@link #setCallback(Callback)}. 55 * <p> 56 * When an app is finished performing playback it must call {@link #release()} 57 * to clean up the session and notify any controllers. 58 * <p> 59 * MediaSessionCompat objects are not thread safe and all calls should be made 60 * from the same thread. 61 * <p> 62 * This is a helper for accessing features in 63 * {@link android.media.session.MediaSession} introduced after API level 4 in a 64 * backwards compatible fashion. 65 */ 66public class MediaSessionCompat { 67 private final MediaSessionImpl mImpl; 68 private final MediaControllerCompat mController; 69 private final ArrayList<OnActiveChangeListener> 70 mActiveListeners = new ArrayList<OnActiveChangeListener>(); 71 72 /** 73 * Set this flag on the session to indicate that it can handle media button 74 * events. 75 */ 76 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 77 78 /** 79 * Set this flag on the session to indicate that it handles transport 80 * control commands through its {@link Callback}. 81 */ 82 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 83 84 /** 85 * Creates a new session. 86 * 87 * @param context The context. 88 * @param tag A short name for debugging purposes. 89 * @param mediaButtonEventReceiver The component name for your receiver. 90 * This must be non-null to support platform versions earlier 91 * than {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 92 * @param mbrIntent The PendingIntent for your receiver component that 93 * handles media button events. This is optional and will be used 94 * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and 95 * later instead of the component name. 96 */ 97 public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver, 98 PendingIntent mbrIntent) { 99 if (context == null) { 100 throw new IllegalArgumentException("context must not be null"); 101 } 102 if (TextUtils.isEmpty(tag)) { 103 throw new IllegalArgumentException("tag must not be null or empty"); 104 } 105 106 if (android.os.Build.VERSION.SDK_INT >= 21) { 107 mImpl = new MediaSessionImplApi21(context, tag); 108 mImpl.setMediaButtonReceiver(mbrIntent); 109 } else { 110 mImpl = new MediaSessionImplBase(context, mediaButtonEventReceiver, mbrIntent); 111 } 112 mController = new MediaControllerCompat(context, this); 113 } 114 115 private MediaSessionCompat(Context context, MediaSessionImpl impl) { 116 mImpl = impl; 117 mController = new MediaControllerCompat(context, this); 118 } 119 120 /** 121 * Add a callback to receive updates on for the MediaSession. This includes 122 * media button and volume events. The caller's thread will be used to post 123 * events. 124 * 125 * @param callback The callback object 126 */ 127 public void setCallback(Callback callback) { 128 setCallback(callback, null); 129 } 130 131 /** 132 * Set the callback to receive updates for the MediaSession. This includes 133 * media button and volume events. Set the callback to null to stop 134 * receiving events. 135 * 136 * @param callback The callback to receive updates on. 137 * @param handler The handler that events should be posted on. 138 */ 139 public void setCallback(Callback callback, Handler handler) { 140 mImpl.setCallback(callback, handler != null ? handler : new Handler()); 141 } 142 143 /** 144 * Set an intent for launching UI for this Session. This can be used as a 145 * quick link to an ongoing media screen. The intent should be for an 146 * activity that may be started using 147 * {@link Activity#startActivity(Intent)}. 148 * 149 * @param pi The intent to launch to show UI for this Session. 150 */ 151 public void setSessionActivity(PendingIntent pi) { 152 mImpl.setSessionActivity(pi); 153 } 154 155 /** 156 * Set a pending intent for your media button receiver to allow restarting 157 * playback after the session has been stopped. If your app is started in 158 * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via 159 * the pending intent. 160 * <p> 161 * This method will only work on 162 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier 163 * platform versions must include the media button receiver in the 164 * constructor. 165 * 166 * @param mbr The {@link PendingIntent} to send the media button event to. 167 */ 168 public void setMediaButtonReceiver(PendingIntent mbr) { 169 mImpl.setMediaButtonReceiver(mbr); 170 } 171 172 /** 173 * Set any flags for the session. 174 * 175 * @param flags The flags to set for this session. 176 */ 177 public void setFlags(int flags) { 178 mImpl.setFlags(flags); 179 } 180 181 /** 182 * Set the stream this session is playing on. This will affect the system's 183 * volume handling for this session. If {@link #setPlaybackToRemote} was 184 * previously called it will stop receiving volume commands and the system 185 * will begin sending volume changes to the appropriate stream. 186 * <p> 187 * By default sessions are on {@link AudioManager#STREAM_MUSIC}. 188 * 189 * @param stream The {@link AudioManager} stream this session is playing on. 190 */ 191 public void setPlaybackToLocal(int stream) { 192 mImpl.setPlaybackToLocal(stream); 193 } 194 195 /** 196 * Configure this session to use remote volume handling. This must be called 197 * to receive volume button events, otherwise the system will adjust the 198 * current stream volume for this session. If {@link #setPlaybackToLocal} 199 * was previously called that stream will stop receiving volume changes for 200 * this session. 201 * 202 * @param volumeProvider The provider that will handle volume changes. May 203 * not be null. 204 */ 205 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 206 if (volumeProvider == null) { 207 throw new IllegalArgumentException("volumeProvider may not be null!"); 208 } 209 mImpl.setPlaybackToRemote(volumeProvider); 210 } 211 212 /** 213 * Set if this session is currently active and ready to receive commands. If 214 * set to false your session's controller may not be discoverable. You must 215 * set the session to active before it can start receiving media button 216 * events or transport commands. 217 * <p> 218 * On platforms earlier than 219 * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, 220 * {@link #setMediaButtonReceiver(PendingIntent)} must be called before 221 * setting this to true. 222 * 223 * @param active Whether this session is active or not. 224 */ 225 public void setActive(boolean active) { 226 mImpl.setActive(active); 227 for (OnActiveChangeListener listener : mActiveListeners) { 228 listener.onActiveChanged(); 229 } 230 } 231 232 /** 233 * Get the current active state of this session. 234 * 235 * @return True if the session is active, false otherwise. 236 */ 237 public boolean isActive() { 238 return mImpl.isActive(); 239 } 240 241 /** 242 * Send a proprietary event to all MediaControllers listening to this 243 * Session. It's up to the Controller/Session owner to determine the meaning 244 * of any events. 245 * 246 * @param event The name of the event to send 247 * @param extras Any extras included with the event 248 */ 249 public void sendSessionEvent(String event, Bundle extras) { 250 if (TextUtils.isEmpty(event)) { 251 throw new IllegalArgumentException("event cannot be null or empty"); 252 } 253 mImpl.sendSessionEvent(event, extras); 254 } 255 256 /** 257 * This must be called when an app has finished performing playback. If 258 * playback is expected to start again shortly the session can be left open, 259 * but it must be released if your activity or service is being destroyed. 260 */ 261 public void release() { 262 mImpl.release(); 263 } 264 265 /** 266 * Retrieve a token object that can be used by apps to create a 267 * {@link MediaControllerCompat} for interacting with this session. The owner of 268 * the session is responsible for deciding how to distribute these tokens. 269 * 270 * @return A token that can be used to create a MediaController for this 271 * session. 272 */ 273 public Token getSessionToken() { 274 return mImpl.getSessionToken(); 275 } 276 277 /** 278 * Get a controller for this session. This is a convenience method to avoid 279 * having to cache your own controller in process. 280 * 281 * @return A controller for this session. 282 */ 283 public MediaControllerCompat getController() { 284 return mController; 285 } 286 287 /** 288 * Update the current playback state. 289 * 290 * @param state The current state of playback 291 */ 292 public void setPlaybackState(PlaybackStateCompat state) { 293 mImpl.setPlaybackState(state); 294 } 295 296 /** 297 * Update the current metadata. New metadata can be created using 298 * {@link android.media.MediaMetadata.Builder}. 299 * 300 * @param metadata The new metadata 301 */ 302 public void setMetadata(MediaMetadataCompat metadata) { 303 mImpl.setMetadata(metadata); 304 } 305 306 /** 307 * Update the list of items in the play queue. It is an ordered list and 308 * should contain the current item, and previous or upcoming items if they 309 * exist. Specify null if there is no current play queue. 310 * <p> 311 * The queue should be of reasonable size. If the play queue is unbounded 312 * within your app, it is better to send a reasonable amount in a sliding 313 * window instead. 314 * 315 * @param queue A list of items in the play queue. 316 */ 317 public void setQueue(List<QueueItem> queue) { 318 mImpl.setQueue(queue); 319 } 320 321 /** 322 * Set the title of the play queue. The UI should display this title along 323 * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album 324 * name. 325 * 326 * @param title The title of the play queue. 327 */ 328 public void setQueueTitle(CharSequence title) { 329 mImpl.setQueueTitle(title); 330 } 331 332 /** 333 * Set the style of rating used by this session. Apps trying to set the 334 * rating should use this style. Must be one of the following: 335 * <ul> 336 * <li>{@link RatingCompat#RATING_NONE}</li> 337 * <li>{@link RatingCompat#RATING_3_STARS}</li> 338 * <li>{@link RatingCompat#RATING_4_STARS}</li> 339 * <li>{@link RatingCompat#RATING_5_STARS}</li> 340 * <li>{@link RatingCompat#RATING_HEART}</li> 341 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 342 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 343 * </ul> 344 */ 345 public void setRatingType(int type) { 346 mImpl.setRatingType(type); 347 } 348 349 /** 350 * Set some extras that can be associated with the 351 * {@link MediaSessionCompat}. No assumptions should be made as to how a 352 * {@link MediaControllerCompat} will handle these extras. Keys should be 353 * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. 354 * 355 * @param extras The extras associated with the session. 356 */ 357 public void setExtras(Bundle extras) { 358 mImpl.setExtras(extras); 359 } 360 361 /** 362 * Gets the underlying framework {@link android.media.session.MediaSession} 363 * object. 364 * <p> 365 * This method is only supported on API 21+. 366 * </p> 367 * 368 * @return The underlying {@link android.media.session.MediaSession} object, 369 * or null if none. 370 */ 371 public Object getMediaSession() { 372 return mImpl.getMediaSession(); 373 } 374 375 /** 376 * Gets the underlying framework {@link android.media.RemoteControlClient} 377 * object. 378 * <p> 379 * This method is only supported on APIs 14-20. On API 21+ 380 * {@link #getMediaSession()} should be used instead. 381 * 382 * @return The underlying {@link android.media.RemoteControlClient} object, 383 * or null if none. 384 */ 385 public Object getRemoteControlClient() { 386 return mImpl.getRemoteControlClient(); 387 } 388 389 /** 390 * Adds a listener to be notified when the active status of this session 391 * changes. This is primarily used by the support library and should not be 392 * needed by apps. 393 * 394 * @param listener The listener to add. 395 */ 396 public void addOnActiveChangeListener(OnActiveChangeListener listener) { 397 if (listener == null) { 398 throw new IllegalArgumentException("Listener may not be null"); 399 } 400 mActiveListeners.add(listener); 401 } 402 403 /** 404 * Stops the listener from being notified when the active status of this 405 * session changes. 406 * 407 * @param listener The listener to remove. 408 */ 409 public void removeOnActiveChangeListener(OnActiveChangeListener listener) { 410 if (listener == null) { 411 throw new IllegalArgumentException("Listener may not be null"); 412 } 413 mActiveListeners.remove(listener); 414 } 415 416 /** 417 * Obtain a compat wrapper for an existing MediaSession. 418 * 419 * @param mediaSession The {@link android.media.session.MediaSession} to 420 * wrap. 421 * @return A compat wrapper for the provided session. 422 */ 423 public static MediaSessionCompat obtain(Context context, Object mediaSession) { 424 return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession)); 425 } 426 427 /** 428 * Receives transport controls, media buttons, and commands from controllers 429 * and the system. The callback may be set using {@link #setCallback}. 430 */ 431 public abstract static class Callback { 432 final Object mCallbackObj; 433 434 public Callback() { 435 if (android.os.Build.VERSION.SDK_INT >= 21) { 436 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21()); 437 } else { 438 mCallbackObj = null; 439 } 440 } 441 442 /** 443 * Called when a controller has sent a custom command to this session. 444 * The owner of the session may handle custom commands but is not 445 * required to. 446 * 447 * @param command The command name. 448 * @param extras Optional parameters for the command, may be null. 449 * @param cb A result receiver to which a result may be sent by the command, may be null. 450 */ 451 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 452 } 453 454 /** 455 * Override to handle media button events. 456 * 457 * @param mediaButtonEvent The media button event intent. 458 * @return True if the event was handled, false otherwise. 459 */ 460 public boolean onMediaButtonEvent(Intent mediaButtonEvent) { 461 return false; 462 } 463 464 /** 465 * Override to handle requests to begin playback. 466 */ 467 public void onPlay() { 468 } 469 470 /** 471 * Override to handle requests to pause playback. 472 */ 473 public void onPause() { 474 } 475 476 /** 477 * Override to handle requests to skip to the next media item. 478 */ 479 public void onSkipToNext() { 480 } 481 482 /** 483 * Override to handle requests to skip to the previous media item. 484 */ 485 public void onSkipToPrevious() { 486 } 487 488 /** 489 * Override to handle requests to fast forward. 490 */ 491 public void onFastForward() { 492 } 493 494 /** 495 * Override to handle requests to rewind. 496 */ 497 public void onRewind() { 498 } 499 500 /** 501 * Override to handle requests to stop playback. 502 */ 503 public void onStop() { 504 } 505 506 /** 507 * Override to handle requests to seek to a specific position in ms. 508 * 509 * @param pos New position to move to, in milliseconds. 510 */ 511 public void onSeekTo(long pos) { 512 } 513 514 /** 515 * Override to handle the item being rated. 516 * 517 * @param rating 518 */ 519 public void onSetRating(RatingCompat rating) { 520 } 521 522 private class StubApi21 implements MediaSessionCompatApi21.Callback { 523 524 @Override 525 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 526 Callback.this.onCommand(command, extras, cb); 527 } 528 529 @Override 530 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 531 return Callback.this.onMediaButtonEvent(mediaButtonIntent); 532 } 533 534 @Override 535 public void onPlay() { 536 Callback.this.onPlay(); 537 } 538 539 @Override 540 public void onPause() { 541 Callback.this.onPause(); 542 } 543 544 @Override 545 public void onSkipToNext() { 546 Callback.this.onSkipToNext(); 547 } 548 549 @Override 550 public void onSkipToPrevious() { 551 Callback.this.onSkipToPrevious(); 552 } 553 554 @Override 555 public void onFastForward() { 556 Callback.this.onFastForward(); 557 } 558 559 @Override 560 public void onRewind() { 561 Callback.this.onRewind(); 562 } 563 564 @Override 565 public void onStop() { 566 Callback.this.onStop(); 567 } 568 569 @Override 570 public void onSeekTo(long pos) { 571 Callback.this.onSeekTo(pos); 572 } 573 574 @Override 575 public void onSetRating(Object ratingObj) { 576 Callback.this.onSetRating(RatingCompat.fromRating(ratingObj)); 577 } 578 } 579 } 580 581 /** 582 * Represents an ongoing session. This may be passed to apps by the session 583 * owner to allow them to create a {@link MediaControllerCompat} to communicate with 584 * the session. 585 */ 586 public static final class Token implements Parcelable { 587 private final Parcelable mInner; 588 589 Token(Parcelable inner) { 590 mInner = inner; 591 } 592 593 /** 594 * Creates a compat Token from a framework 595 * {@link android.media.session.MediaSession.Token} object. 596 * <p> 597 * This method is only supported on 598 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 599 * </p> 600 * 601 * @param token The framework token object. 602 * @return A compat Token for use with {@link MediaControllerCompat}. 603 */ 604 public static Token fromToken(Object token) { 605 if (token == null || android.os.Build.VERSION.SDK_INT < 21) { 606 return null; 607 } 608 return new Token((Parcelable) MediaSessionCompatApi21.verifyToken(token)); 609 } 610 611 @Override 612 public int describeContents() { 613 return mInner.describeContents(); 614 } 615 616 @Override 617 public void writeToParcel(Parcel dest, int flags) { 618 dest.writeParcelable(mInner, flags); 619 } 620 621 /** 622 * Gets the underlying framework {@link android.media.session.MediaSession.Token} object. 623 * <p> 624 * This method is only supported on API 21+. 625 * </p> 626 * 627 * @return The underlying {@link android.media.session.MediaSession.Token} object, 628 * or null if none. 629 */ 630 public Object getToken() { 631 return mInner; 632 } 633 634 public static final Parcelable.Creator<Token> CREATOR 635 = new Parcelable.Creator<Token>() { 636 @Override 637 public Token createFromParcel(Parcel in) { 638 return new Token(in.readParcelable(null)); 639 } 640 641 @Override 642 public Token[] newArray(int size) { 643 return new Token[size]; 644 } 645 }; 646 } 647 648 /** 649 * A single item that is part of the play queue. It contains a description 650 * of the item and its id in the queue. 651 */ 652 public static final class QueueItem implements Parcelable { 653 /** 654 * This id is reserved. No items can be explicitly asigned this id. 655 */ 656 public static final int UNKNOWN_ID = -1; 657 658 private final MediaDescriptionCompat mDescription; 659 private final long mId; 660 661 private Object mItem; 662 663 /** 664 * Create a new {@link MediaSessionCompat.QueueItem}. 665 * 666 * @param description The {@link MediaDescriptionCompat} for this item. 667 * @param id An identifier for this item. It must be unique within the 668 * play queue and cannot be {@link #UNKNOWN_ID}. 669 */ 670 public QueueItem(MediaDescriptionCompat description, long id) { 671 this(null, description, id); 672 } 673 674 private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) { 675 if (description == null) { 676 throw new IllegalArgumentException("Description cannot be null."); 677 } 678 if (id == UNKNOWN_ID) { 679 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); 680 } 681 mDescription = description; 682 mId = id; 683 mItem = queueItem; 684 } 685 686 private QueueItem(Parcel in) { 687 mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in); 688 mId = in.readLong(); 689 } 690 691 /** 692 * Get the description for this item. 693 */ 694 public MediaDescriptionCompat getDescription() { 695 return mDescription; 696 } 697 698 /** 699 * Get the queue id for this item. 700 */ 701 public long getQueueId() { 702 return mId; 703 } 704 705 @Override 706 public void writeToParcel(Parcel dest, int flags) { 707 mDescription.writeToParcel(dest, flags); 708 dest.writeLong(mId); 709 } 710 711 @Override 712 public int describeContents() { 713 return 0; 714 } 715 716 /** 717 * Get the underlying 718 * {@link android.media.session.MediaSession.QueueItem}. 719 * <p> 720 * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null 721 * is returned. 722 * 723 * @return The underlying 724 * {@link android.media.session.MediaSession.QueueItem} or null. 725 */ 726 public Object getQueueItem() { 727 if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) { 728 return mItem; 729 } 730 mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(), 731 mId); 732 return mItem; 733 } 734 735 /** 736 * Obtain a compat wrapper for an existing QueueItem. 737 * 738 * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to 739 * wrap. 740 * @return A compat wrapper for the provided item. 741 */ 742 public static QueueItem obtain(Object queueItem) { 743 Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem); 744 MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription( 745 descriptionObj); 746 long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem); 747 return new QueueItem(queueItem, description, id); 748 } 749 750 public static final Creator<MediaSessionCompat.QueueItem> 751 CREATOR = new Creator<MediaSessionCompat.QueueItem>() { 752 753 @Override 754 public MediaSessionCompat.QueueItem createFromParcel(Parcel p) { 755 return new MediaSessionCompat.QueueItem(p); 756 } 757 758 @Override 759 public MediaSessionCompat.QueueItem[] newArray(int size) { 760 return new MediaSessionCompat.QueueItem[size]; 761 } 762 }; 763 764 @Override 765 public String toString() { 766 return "MediaSession.QueueItem {" + 767 "Description=" + mDescription + 768 ", Id=" + mId + " }"; 769 } 770 } 771 772 public interface OnActiveChangeListener { 773 void onActiveChanged(); 774 } 775 776 interface MediaSessionImpl { 777 void setCallback(Callback callback, Handler handler); 778 void setFlags(int flags); 779 void setPlaybackToLocal(int stream); 780 void setPlaybackToRemote(VolumeProviderCompat volumeProvider); 781 void setActive(boolean active); 782 boolean isActive(); 783 void sendSessionEvent(String event, Bundle extras); 784 void release(); 785 Token getSessionToken(); 786 void setPlaybackState(PlaybackStateCompat state); 787 void setMetadata(MediaMetadataCompat metadata); 788 789 void setSessionActivity(PendingIntent pi); 790 791 void setMediaButtonReceiver(PendingIntent mbr); 792 void setQueue(List<QueueItem> queue); 793 void setQueueTitle(CharSequence title); 794 795 void setRatingType(int type); 796 void setExtras(Bundle extras); 797 798 Object getMediaSession(); 799 800 Object getRemoteControlClient(); 801 } 802 803 // TODO: compatibility implementation 804 static class MediaSessionImplBase implements MediaSessionImpl { 805 private final Context mContext; 806 private final ComponentName mComponentName; 807 private final PendingIntent mMediaButtonEventReceiver; 808 private final Object mRccObj; 809 private Object mToken; 810 811 private boolean mIsActive = false; 812 private boolean mIsRccRegistered = false; 813 private boolean mIsMbrRegistered = false; 814 private Callback mCallback; 815 816 private int mFlags; 817 818 private MediaMetadataCompat mMetadata; 819 private PlaybackStateCompat mState; 820 821 public MediaSessionImplBase(Context context, ComponentName mbrComponent, 822 PendingIntent mbr) { 823 if (mbrComponent == null) { 824 throw new IllegalArgumentException( 825 "MediaButtonReceiver component may not be null."); 826 } 827 if (mbr == null) { 828 // construct a PendingIntent for the media button 829 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 830 // the associated intent will be handled by the component being 831 // registered 832 mediaButtonIntent.setComponent(mbrComponent); 833 mbr = PendingIntent.getBroadcast(context, 834 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */); 835 } 836 mContext = context; 837 mComponentName = mbrComponent; 838 mMediaButtonEventReceiver = mbr; 839 if (android.os.Build.VERSION.SDK_INT >= 14) { 840 mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr); 841 } else { 842 mRccObj = null; 843 } 844 } 845 846 @Override 847 public void setCallback(final Callback callback, Handler handler) { 848 if (callback == mCallback) { 849 return; 850 } 851 if (callback == null || android.os.Build.VERSION.SDK_INT < 18) { 852 // There's nothing to register on API < 18 since media buttons 853 // all go through the media button receiver 854 if (android.os.Build.VERSION.SDK_INT >= 18) { 855 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null); 856 } 857 if (android.os.Build.VERSION.SDK_INT >= 19) { 858 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null); 859 } 860 } else { 861 if (handler == null) { 862 handler = new Handler(); 863 } 864 MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() { 865 @Override 866 public void onStop() { 867 callback.onStop(); 868 } 869 870 @Override 871 public void onSkipToPrevious() { 872 callback.onSkipToPrevious(); 873 } 874 875 @Override 876 public void onSkipToNext() { 877 callback.onSkipToNext(); 878 } 879 880 @Override 881 public void onSetRating(Object ratingObj) { 882 callback.onSetRating(RatingCompat.fromRating(ratingObj)); 883 } 884 885 @Override 886 public void onSeekTo(long pos) { 887 callback.onSeekTo(pos); 888 } 889 890 @Override 891 public void onRewind() { 892 callback.onRewind(); 893 } 894 895 @Override 896 public void onPlay() { 897 callback.onPlay(); 898 } 899 900 @Override 901 public void onPause() { 902 callback.onPause(); 903 } 904 905 @Override 906 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 907 return callback.onMediaButtonEvent(mediaButtonIntent); 908 } 909 910 @Override 911 public void onFastForward() { 912 callback.onFastForward(); 913 } 914 915 @Override 916 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 917 callback.onCommand(command, extras, cb); 918 } 919 }; 920 if (android.os.Build.VERSION.SDK_INT >= 18) { 921 Object onPositionUpdateObj = MediaSessionCompatApi18 922 .createPlaybackPositionUpdateListener(cb14); 923 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, 924 onPositionUpdateObj); 925 } 926 if (android.os.Build.VERSION.SDK_INT >= 19) { 927 Object onMetadataUpdateObj = MediaSessionCompatApi19 928 .createMetadataUpdateListener(cb14); 929 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, 930 onMetadataUpdateObj); 931 } 932 } 933 mCallback = callback; 934 } 935 936 @Override 937 public void setFlags(int flags) { 938 mFlags = flags; 939 update(); 940 } 941 942 @Override 943 public void setPlaybackToLocal(int stream) { 944 } 945 946 @Override 947 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 948 } 949 950 @Override 951 public void setActive(boolean active) { 952 if (active == mIsActive) { 953 return; 954 } 955 mIsActive = active; 956 if (update()) { 957 setMetadata(mMetadata); 958 setPlaybackState(mState); 959 } 960 } 961 962 @Override 963 public boolean isActive() { 964 return mIsActive; 965 } 966 967 @Override 968 public void sendSessionEvent(String event, Bundle extras) { 969 } 970 971 @Override 972 public void release() { 973 mIsActive = false; 974 update(); 975 } 976 977 @Override 978 public Token getSessionToken() { 979 return null; 980 } 981 982 @Override 983 public void setPlaybackState(PlaybackStateCompat state) { 984 mState = state; 985 if (!mIsActive) { 986 // Don't set the state until after the RCC is registered 987 return; 988 } 989 990 if (state == null) { 991 if (android.os.Build.VERSION.SDK_INT >= 14) { 992 MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE); 993 } 994 } else { 995 if (android.os.Build.VERSION.SDK_INT >= 18) { 996 MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(), 997 state.getPlaybackSpeed(), state.getLastPositionUpdateTime()); 998 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 999 MediaSessionCompatApi14.setState(mRccObj, state.getState()); 1000 } 1001 } 1002 } 1003 1004 @Override 1005 public void setMetadata(MediaMetadataCompat metadata) { 1006 mMetadata = metadata; 1007 if (!mIsActive) { 1008 // Don't set metadata until after the rcc has been registered 1009 return; 1010 } 1011 if (android.os.Build.VERSION.SDK_INT >= 19) { 1012 boolean canRate = mState != null 1013 && (mState.getActions() & PlaybackStateCompat.ACTION_SET_RATING) != 0; 1014 MediaSessionCompatApi19.setMetadata(mRccObj, 1015 metadata == null ? null : metadata.getBundle(), canRate); 1016 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1017 MediaSessionCompatApi14.setMetadata(mRccObj, 1018 metadata == null ? null : metadata.getBundle()); 1019 } 1020 } 1021 1022 @Override 1023 public void setSessionActivity(PendingIntent pi) { 1024 } 1025 1026 @Override 1027 public void setMediaButtonReceiver(PendingIntent mbr) { 1028 // Do nothing, changing this is not supported before API 21. 1029 } 1030 1031 @Override 1032 public void setQueue(List<QueueItem> queue) { 1033 } 1034 1035 @Override 1036 public void setQueueTitle(CharSequence title) { 1037 } 1038 1039 @Override 1040 public Object getMediaSession() { 1041 return null; 1042 } 1043 1044 @Override 1045 public Object getRemoteControlClient() { 1046 return mRccObj; 1047 } 1048 1049 @Override 1050 public void setRatingType(int type) { 1051 } 1052 1053 @Override 1054 public void setExtras(Bundle extras) { 1055 } 1056 1057 // Registers/unregisters the RCC and MediaButtonEventReceiver as needed. 1058 private boolean update() { 1059 boolean registeredRcc = false; 1060 if (mIsActive) { 1061 // On API 8+ register a MBR if it's supported, unregister it 1062 // if support was removed. 1063 if (android.os.Build.VERSION.SDK_INT >= 8) { 1064 if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) { 1065 if (android.os.Build.VERSION.SDK_INT >= 18) { 1066 MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext, 1067 mMediaButtonEventReceiver); 1068 } else { 1069 MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext, 1070 mComponentName); 1071 } 1072 mIsMbrRegistered = true; 1073 } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) { 1074 if (android.os.Build.VERSION.SDK_INT >= 18) { 1075 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1076 mMediaButtonEventReceiver); 1077 } else { 1078 MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext, 1079 mComponentName); 1080 } 1081 mIsMbrRegistered = false; 1082 } 1083 } 1084 // On API 14+ register a RCC if it's supported, unregister it if 1085 // not. 1086 if (android.os.Build.VERSION.SDK_INT >= 14) { 1087 if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) { 1088 MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj); 1089 mIsRccRegistered = true; 1090 registeredRcc = true; 1091 } else if (mIsRccRegistered 1092 && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) { 1093 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1094 mIsRccRegistered = false; 1095 } 1096 } 1097 } else { 1098 // When inactive remove any registered components. 1099 if (mIsMbrRegistered) { 1100 if (android.os.Build.VERSION.SDK_INT >= 18) { 1101 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1102 mMediaButtonEventReceiver); 1103 } else { 1104 MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext, 1105 mComponentName); 1106 } 1107 mIsMbrRegistered = false; 1108 } 1109 if (mIsRccRegistered) { 1110 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1111 mIsRccRegistered = false; 1112 } 1113 } 1114 return registeredRcc; 1115 } 1116 } 1117 1118 static class MediaSessionImplApi21 implements MediaSessionImpl { 1119 private final Object mSessionObj; 1120 private final Token mToken; 1121 1122 private PendingIntent mMediaButtonIntent; 1123 1124 public MediaSessionImplApi21(Context context, String tag) { 1125 mSessionObj = MediaSessionCompatApi21.createSession(context, tag); 1126 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj)); 1127 } 1128 1129 public MediaSessionImplApi21(Object mediaSession) { 1130 mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession); 1131 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj)); 1132 } 1133 1134 @Override 1135 public void setCallback(Callback callback, Handler handler) { 1136 MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler); 1137 } 1138 1139 @Override 1140 public void setFlags(int flags) { 1141 MediaSessionCompatApi21.setFlags(mSessionObj, flags); 1142 } 1143 1144 @Override 1145 public void setPlaybackToLocal(int stream) { 1146 MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream); 1147 } 1148 1149 @Override 1150 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 1151 MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj, 1152 volumeProvider.getVolumeProvider()); 1153 } 1154 1155 @Override 1156 public void setActive(boolean active) { 1157 MediaSessionCompatApi21.setActive(mSessionObj, active); 1158 } 1159 1160 @Override 1161 public boolean isActive() { 1162 return MediaSessionCompatApi21.isActive(mSessionObj); 1163 } 1164 1165 @Override 1166 public void sendSessionEvent(String event, Bundle extras) { 1167 MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras); 1168 } 1169 1170 @Override 1171 public void release() { 1172 MediaSessionCompatApi21.release(mSessionObj); 1173 } 1174 1175 @Override 1176 public Token getSessionToken() { 1177 return mToken; 1178 } 1179 1180 @Override 1181 public void setPlaybackState(PlaybackStateCompat state) { 1182 MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState()); 1183 } 1184 1185 @Override 1186 public void setMetadata(MediaMetadataCompat metadata) { 1187 MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata()); 1188 } 1189 1190 @Override 1191 public void setSessionActivity(PendingIntent pi) { 1192 MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi); 1193 } 1194 1195 @Override 1196 public void setMediaButtonReceiver(PendingIntent mbr) { 1197 mMediaButtonIntent = mbr; 1198 MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr); 1199 } 1200 1201 @Override 1202 public void setQueue(List<QueueItem> queue) { 1203 List<Object> queueObjs = null; 1204 if (queue != null) { 1205 queueObjs = new ArrayList<Object>(); 1206 for (QueueItem item : queue) { 1207 queueObjs.add(item.getQueueItem()); 1208 } 1209 } 1210 MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs); 1211 } 1212 1213 @Override 1214 public void setQueueTitle(CharSequence title) { 1215 MediaSessionCompatApi21.setQueueTitle(mSessionObj, title); 1216 } 1217 1218 @Override 1219 public void setRatingType(int type) { 1220 if (android.os.Build.VERSION.SDK_INT < 22) { 1221 // TODO figure out 21 implementation 1222 } else { 1223 MediaSessionCompatApi22.setRatingType(mSessionObj, type); 1224 } 1225 } 1226 1227 @Override 1228 public void setExtras(Bundle extras) { 1229 MediaSessionCompatApi21.setExtras(mSessionObj, extras); 1230 } 1231 1232 @Override 1233 public Object getMediaSession() { 1234 return mSessionObj; 1235 } 1236 1237 @Override 1238 public Object getRemoteControlClient() { 1239 return null; 1240 } 1241 } 1242} 1243