MediaSessionCompat.java revision 19801df0ed4375e8dfbd57d020baaaa13113e93a
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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21 22import android.app.Activity; 23import android.app.PendingIntent; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.media.AudioManager; 29import android.net.Uri; 30import android.os.Build; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.IBinder; 34import android.os.Looper; 35import android.os.Message; 36import android.os.Parcel; 37import android.os.Parcelable; 38import android.os.RemoteCallbackList; 39import android.os.RemoteException; 40import android.os.ResultReceiver; 41import android.os.SystemClock; 42import android.support.annotation.IntDef; 43import android.support.annotation.RestrictTo; 44import android.support.v4.app.BundleCompat; 45import android.support.v4.media.MediaDescriptionCompat; 46import android.support.v4.media.MediaMetadataCompat; 47import android.support.v4.media.RatingCompat; 48import android.support.v4.media.VolumeProviderCompat; 49import android.text.TextUtils; 50import android.util.Log; 51import android.util.TypedValue; 52import android.view.KeyEvent; 53 54import java.lang.annotation.Retention; 55import java.lang.annotation.RetentionPolicy; 56import java.lang.ref.WeakReference; 57import java.util.ArrayList; 58import java.util.List; 59 60/** 61 * Allows interaction with media controllers, volume keys, media buttons, and 62 * transport controls. 63 * <p> 64 * A MediaSession should be created when an app wants to publish media playback 65 * information or handle media keys. In general an app only needs one session 66 * for all playback, though multiple sessions can be created to provide finer 67 * grain controls of media. 68 * <p> 69 * Once a session is created the owner of the session may pass its 70 * {@link #getSessionToken() session token} to other processes to allow them to 71 * create a {@link MediaControllerCompat} to interact with the session. 72 * <p> 73 * To receive commands, media keys, and other events a {@link Callback} must be 74 * set with {@link #setCallback(Callback)}. 75 * <p> 76 * When an app is finished performing playback it must call {@link #release()} 77 * to clean up the session and notify any controllers. 78 * <p> 79 * MediaSessionCompat objects are not thread safe and all calls should be made 80 * from the same thread. 81 * <p> 82 * This is a helper for accessing features in 83 * {@link android.media.session.MediaSession} introduced after API level 4 in a 84 * backwards compatible fashion. 85 */ 86public class MediaSessionCompat { 87 static final String TAG = "MediaSessionCompat"; 88 89 private final MediaSessionImpl mImpl; 90 private final MediaControllerCompat mController; 91 private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>(); 92 93 /** 94 * @hide 95 */ 96 @RestrictTo(LIBRARY_GROUP) 97 @IntDef(flag=true, value={ 98 FLAG_HANDLES_MEDIA_BUTTONS, 99 FLAG_HANDLES_TRANSPORT_CONTROLS, 100 FLAG_HANDLES_QUEUE_COMMANDS }) 101 @Retention(RetentionPolicy.SOURCE) 102 public @interface SessionFlags {} 103 104 /** 105 * Set this flag on the session to indicate that it can handle media button 106 * events. 107 */ 108 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 109 110 /** 111 * Set this flag on the session to indicate that it handles transport 112 * control commands through its {@link Callback}. 113 */ 114 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 115 116 /** 117 * Set this flag on the session to indicate that it handles queue 118 * management commands through its {@link Callback}. 119 */ 120 public static final int FLAG_HANDLES_QUEUE_COMMANDS = 1 << 2; 121 122 /** 123 * Predefined custom action to flag the media that is currently playing as inappropriate. 124 * 125 * @see Callback#onCustomAction 126 */ 127 public static final String ACTION_FLAG_AS_INAPPROPRIATE = 128 "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE"; 129 130 /** 131 * Predefined custom action to skip the advertisement that is currently playing. 132 * 133 * @see Callback#onCustomAction 134 */ 135 public static final String ACTION_SKIP_AD = 136 "android.support.v4.media.session.action.SKIP_AD"; 137 138 /** 139 * Custom action to invoke playFromUri() for the forward compatibility. 140 */ 141 static final String ACTION_PLAY_FROM_URI = 142 "android.support.v4.media.session.action.PLAY_FROM_URI"; 143 144 /** 145 * Custom action to invoke prepare() for the forward compatibility. 146 */ 147 static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE"; 148 149 /** 150 * Custom action to invoke prepareFromMediaId() for the forward compatibility. 151 */ 152 static final String ACTION_PREPARE_FROM_MEDIA_ID = 153 "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID"; 154 155 /** 156 * Custom action to invoke prepareFromSearch() for the forward compatibility. 157 */ 158 static final String ACTION_PREPARE_FROM_SEARCH = 159 "android.support.v4.media.session.action.PREPARE_FROM_SEARCH"; 160 161 /** 162 * Custom action to invoke prepareFromUri() for the forward compatibility. 163 */ 164 static final String ACTION_PREPARE_FROM_URI = 165 "android.support.v4.media.session.action.PREPARE_FROM_URI"; 166 167 /** 168 * Custom action to invoke setRepeatMode() for the forward compatibility. 169 */ 170 static final String ACTION_SET_REPEAT_MODE = 171 "android.support.v4.media.session.action.SET_REPEAT_MODE"; 172 173 /** 174 * Custom action to invoke setShuffleModeEnabled() for the forward compatibility. 175 */ 176 static final String ACTION_SET_SHUFFLE_MODE_ENABLED = 177 "android.support.v4.media.session.action.SET_SHUFFLE_MODE_ENABLED"; 178 179 /** 180 * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play. 181 */ 182 static final String ACTION_ARGUMENT_MEDIA_ID = 183 "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID"; 184 185 /** 186 * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query. 187 */ 188 static final String ACTION_ARGUMENT_QUERY = 189 "android.support.v4.media.session.action.ARGUMENT_QUERY"; 190 191 /** 192 * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI} 193 * indicating URI to play. 194 */ 195 static final String ACTION_ARGUMENT_URI = 196 "android.support.v4.media.session.action.ARGUMENT_URI"; 197 198 /** 199 * Argument for use with various actions indicating extra bundle. 200 */ 201 static final String ACTION_ARGUMENT_EXTRAS = 202 "android.support.v4.media.session.action.ARGUMENT_EXTRAS"; 203 204 /** 205 * Argument for use with {@link #ACTION_SET_REPEAT_MODE} indicating repeat mode. 206 */ 207 static final String ACTION_ARGUMENT_REPEAT_MODE = 208 "android.support.v4.media.session.action.ARGUMENT_REPEAT_MODE"; 209 210 /** 211 * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE_ENABLED} indicating that shuffle mode 212 * is enabled. 213 */ 214 static final String ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED = 215 "android.support.v4.media.session.action.ARGUMENT_SHUFFLE_MODE_ENABLED"; 216 217 static final String EXTRA_BINDER = "android.support.v4.media.session.EXTRA_BINDER"; 218 219 // Maximum size of the bitmap in dp. 220 private static final int MAX_BITMAP_SIZE_IN_DP = 320; 221 222 // Maximum size of the bitmap in px. It shouldn't be changed. 223 static int sMaxBitmapSize; 224 225 /** 226 * Creates a new session. You must call {@link #release()} when finished with the session. 227 * <p> 228 * The session will automatically be registered with the system but will not be published 229 * until {@link #setActive(boolean) setActive(true)} is called. 230 * </p><p> 231 * For API 20 or earlier, note that a media button receiver is required for handling 232 * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate 233 * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more 234 * details. 235 * </p> 236 * @param context The context to use to create the session. 237 * @param tag A short name for debugging purposes. 238 */ 239 public MediaSessionCompat(Context context, String tag) { 240 this(context, tag, null, null); 241 } 242 243 /** 244 * Creates a new session with a specified media button receiver (a component name and/or 245 * a pending intent). You must call {@link #release()} when finished with the session. 246 * <p> 247 * The session will automatically be registered with the system but will not be published 248 * until {@link #setActive(boolean) setActive(true)} is called. 249 * </p><p> 250 * For API 20 or earlier, note that a media button receiver is required for handling 251 * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate 252 * {@link BroadcastReceiver} from your manifest if it's not specified. See 253 * {@link MediaButtonReceiver} for more details. 254 * </p> 255 * @param context The context to use to create the session. 256 * @param tag A short name for debugging purposes. 257 * @param mbrComponent The component name for your media button receiver. 258 * @param mbrIntent The PendingIntent for your receiver component that handles 259 * media button events. This is optional and will be used on between 260 * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and 261 * {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the 262 * component name. 263 */ 264 public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent, 265 PendingIntent mbrIntent) { 266 if (context == null) { 267 throw new IllegalArgumentException("context must not be null"); 268 } 269 if (TextUtils.isEmpty(tag)) { 270 throw new IllegalArgumentException("tag must not be null or empty"); 271 } 272 273 if (mbrComponent == null) { 274 mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context); 275 if (mbrComponent == null) { 276 Log.w(TAG, "Couldn't find a unique registered media button receiver in the " 277 + "given context."); 278 } 279 } 280 if (mbrComponent != null && mbrIntent == null) { 281 // construct a PendingIntent for the media button 282 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 283 // the associated intent will be handled by the component being registered 284 mediaButtonIntent.setComponent(mbrComponent); 285 mbrIntent = PendingIntent.getBroadcast(context, 286 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */); 287 } 288 if (android.os.Build.VERSION.SDK_INT >= 21) { 289 mImpl = new MediaSessionImplApi21(context, tag); 290 mImpl.setMediaButtonReceiver(mbrIntent); 291 // Set default callback to respond to controllers' extra binder requests. 292 setCallback(new Callback() {}); 293 } else { 294 mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent); 295 } 296 mController = new MediaControllerCompat(context, this); 297 298 if (sMaxBitmapSize == 0) { 299 sMaxBitmapSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300 MAX_BITMAP_SIZE_IN_DP, context.getResources().getDisplayMetrics()); 301 } 302 } 303 304 private MediaSessionCompat(Context context, MediaSessionImpl impl) { 305 mImpl = impl; 306 if (android.os.Build.VERSION.SDK_INT >= 21) { 307 // Set default callback to respond to controllers' extra binder requests. 308 setCallback(new Callback() {}); 309 } 310 mController = new MediaControllerCompat(context, this); 311 } 312 313 /** 314 * Add a callback to receive updates on for the MediaSession. This includes 315 * media button and volume events. The caller's thread will be used to post 316 * events. 317 * 318 * @param callback The callback object 319 */ 320 public void setCallback(Callback callback) { 321 setCallback(callback, null); 322 } 323 324 /** 325 * Set the callback to receive updates for the MediaSession. This includes 326 * media button and volume events. Set the callback to null to stop 327 * receiving events. 328 * 329 * @param callback The callback to receive updates on. 330 * @param handler The handler that events should be posted on. 331 */ 332 public void setCallback(Callback callback, Handler handler) { 333 mImpl.setCallback(callback, handler != null ? handler : new Handler()); 334 } 335 336 /** 337 * Set an intent for launching UI for this Session. This can be used as a 338 * quick link to an ongoing media screen. The intent should be for an 339 * activity that may be started using 340 * {@link Activity#startActivity(Intent)}. 341 * 342 * @param pi The intent to launch to show UI for this Session. 343 */ 344 public void setSessionActivity(PendingIntent pi) { 345 mImpl.setSessionActivity(pi); 346 } 347 348 /** 349 * Set a pending intent for your media button receiver to allow restarting 350 * playback after the session has been stopped. If your app is started in 351 * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via 352 * the pending intent. 353 * <p> 354 * This method will only work on 355 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier 356 * platform versions must include the media button receiver in the 357 * constructor. 358 * 359 * @param mbr The {@link PendingIntent} to send the media button event to. 360 */ 361 public void setMediaButtonReceiver(PendingIntent mbr) { 362 mImpl.setMediaButtonReceiver(mbr); 363 } 364 365 /** 366 * Set any flags for the session. 367 * 368 * @param flags The flags to set for this session. 369 */ 370 public void setFlags(@SessionFlags int flags) { 371 mImpl.setFlags(flags); 372 } 373 374 /** 375 * Set the stream this session is playing on. This will affect the system's 376 * volume handling for this session. If {@link #setPlaybackToRemote} was 377 * previously called it will stop receiving volume commands and the system 378 * will begin sending volume changes to the appropriate stream. 379 * <p> 380 * By default sessions are on {@link AudioManager#STREAM_MUSIC}. 381 * 382 * @param stream The {@link AudioManager} stream this session is playing on. 383 */ 384 public void setPlaybackToLocal(int stream) { 385 mImpl.setPlaybackToLocal(stream); 386 } 387 388 /** 389 * Configure this session to use remote volume handling. This must be called 390 * to receive volume button events, otherwise the system will adjust the 391 * current stream volume for this session. If {@link #setPlaybackToLocal} 392 * was previously called that stream will stop receiving volume changes for 393 * this session. 394 * <p> 395 * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP} 396 * this will only allow an app to handle volume commands sent directly to 397 * the session by a {@link MediaControllerCompat}. System routing of volume 398 * keys will not use the volume provider. 399 * 400 * @param volumeProvider The provider that will handle volume changes. May 401 * not be null. 402 */ 403 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 404 if (volumeProvider == null) { 405 throw new IllegalArgumentException("volumeProvider may not be null!"); 406 } 407 mImpl.setPlaybackToRemote(volumeProvider); 408 } 409 410 /** 411 * Set if this session is currently active and ready to receive commands. If 412 * set to false your session's controller may not be discoverable. You must 413 * set the session to active before it can start receiving media button 414 * events or transport commands. 415 * <p> 416 * On platforms earlier than 417 * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, 418 * a media button event receiver should be set via the constructor to 419 * receive media button events. 420 * 421 * @param active Whether this session is active or not. 422 */ 423 public void setActive(boolean active) { 424 mImpl.setActive(active); 425 for (OnActiveChangeListener listener : mActiveListeners) { 426 listener.onActiveChanged(); 427 } 428 } 429 430 /** 431 * Get the current active state of this session. 432 * 433 * @return True if the session is active, false otherwise. 434 */ 435 public boolean isActive() { 436 return mImpl.isActive(); 437 } 438 439 /** 440 * Send a proprietary event to all MediaControllers listening to this 441 * Session. It's up to the Controller/Session owner to determine the meaning 442 * of any events. 443 * 444 * @param event The name of the event to send 445 * @param extras Any extras included with the event 446 */ 447 public void sendSessionEvent(String event, Bundle extras) { 448 if (TextUtils.isEmpty(event)) { 449 throw new IllegalArgumentException("event cannot be null or empty"); 450 } 451 mImpl.sendSessionEvent(event, extras); 452 } 453 454 /** 455 * This must be called when an app has finished performing playback. If 456 * playback is expected to start again shortly the session can be left open, 457 * but it must be released if your activity or service is being destroyed. 458 */ 459 public void release() { 460 mImpl.release(); 461 } 462 463 /** 464 * Retrieve a token object that can be used by apps to create a 465 * {@link MediaControllerCompat} for interacting with this session. The 466 * owner of the session is responsible for deciding how to distribute these 467 * tokens. 468 * <p> 469 * On platform versions before 470 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be 471 * used within your app as there is no way to guarantee other apps are using 472 * the same version of the support library. 473 * 474 * @return A token that can be used to create a media controller for this 475 * session. 476 */ 477 public Token getSessionToken() { 478 return mImpl.getSessionToken(); 479 } 480 481 /** 482 * Get a controller for this session. This is a convenience method to avoid 483 * having to cache your own controller in process. 484 * 485 * @return A controller for this session. 486 */ 487 public MediaControllerCompat getController() { 488 return mController; 489 } 490 491 /** 492 * Update the current playback state. 493 * 494 * @param state The current state of playback 495 */ 496 public void setPlaybackState(PlaybackStateCompat state) { 497 mImpl.setPlaybackState(state); 498 } 499 500 /** 501 * Update the current metadata. New metadata can be created using 502 * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time 503 * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy. 504 * 505 * @param metadata The new metadata 506 * @see android.support.v4.media.MediaMetadataCompat.Builder#putBitmap 507 */ 508 public void setMetadata(MediaMetadataCompat metadata) { 509 mImpl.setMetadata(metadata); 510 } 511 512 /** 513 * Update the list of items in the play queue. It is an ordered list and 514 * should contain the current item, and previous or upcoming items if they 515 * exist. Specify null if there is no current play queue. 516 * <p> 517 * The queue should be of reasonable size. If the play queue is unbounded 518 * within your app, it is better to send a reasonable amount in a sliding 519 * window instead. 520 * 521 * @param queue A list of items in the play queue. 522 */ 523 public void setQueue(List<QueueItem> queue) { 524 mImpl.setQueue(queue); 525 } 526 527 /** 528 * Set the title of the play queue. The UI should display this title along 529 * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album 530 * name. 531 * 532 * @param title The title of the play queue. 533 */ 534 public void setQueueTitle(CharSequence title) { 535 mImpl.setQueueTitle(title); 536 } 537 538 /** 539 * Set the style of rating used by this session. Apps trying to set the 540 * rating should use this style. Must be one of the following: 541 * <ul> 542 * <li>{@link RatingCompat#RATING_NONE}</li> 543 * <li>{@link RatingCompat#RATING_3_STARS}</li> 544 * <li>{@link RatingCompat#RATING_4_STARS}</li> 545 * <li>{@link RatingCompat#RATING_5_STARS}</li> 546 * <li>{@link RatingCompat#RATING_HEART}</li> 547 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 548 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 549 * </ul> 550 */ 551 public void setRatingType(@RatingCompat.Style int type) { 552 mImpl.setRatingType(type); 553 } 554 555 /** 556 * Set the repeat mode for this session. 557 * <p> 558 * Note that if this method is not called before, {@link MediaControllerCompat#getRepeatMode} 559 * will return {@link PlaybackStateCompat#REPEAT_MODE_NONE}. 560 * 561 * @param repeatMode The repeat mode. Must be one of the followings: 562 * {@link PlaybackStateCompat#REPEAT_MODE_NONE}, 563 * {@link PlaybackStateCompat#REPEAT_MODE_ONE}, 564 * {@link PlaybackStateCompat#REPEAT_MODE_ALL} 565 */ 566 public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 567 mImpl.setRepeatMode(repeatMode); 568 } 569 570 /** 571 * Set the shuffle mode for this session. 572 * <p> 573 * Note that if this method is not called before, 574 * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}. 575 * 576 * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable. 577 */ 578 public void setShuffleModeEnabled(boolean enabled) { 579 mImpl.setShuffleModeEnabled(enabled); 580 } 581 582 /** 583 * Set some extras that can be associated with the 584 * {@link MediaSessionCompat}. No assumptions should be made as to how a 585 * {@link MediaControllerCompat} will handle these extras. Keys should be 586 * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. 587 * 588 * @param extras The extras associated with the session. 589 */ 590 public void setExtras(Bundle extras) { 591 mImpl.setExtras(extras); 592 } 593 594 /** 595 * Gets the underlying framework {@link android.media.session.MediaSession} 596 * object. 597 * <p> 598 * This method is only supported on API 21+. 599 * </p> 600 * 601 * @return The underlying {@link android.media.session.MediaSession} object, 602 * or null if none. 603 */ 604 public Object getMediaSession() { 605 return mImpl.getMediaSession(); 606 } 607 608 /** 609 * Gets the underlying framework {@link android.media.RemoteControlClient} 610 * object. 611 * <p> 612 * This method is only supported on APIs 14-20. On API 21+ 613 * {@link #getMediaSession()} should be used instead. 614 * 615 * @return The underlying {@link android.media.RemoteControlClient} object, 616 * or null if none. 617 */ 618 public Object getRemoteControlClient() { 619 return mImpl.getRemoteControlClient(); 620 } 621 622 /** 623 * Returns the name of the package that sent the last media button, transport control, or 624 * command from controllers and the system. This is only valid while in a request callback, such 625 * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices. 626 * 627 * @hide 628 */ 629 @RestrictTo(LIBRARY_GROUP) 630 public String getCallingPackage() { 631 return mImpl.getCallingPackage(); 632 } 633 634 /** 635 * Adds a listener to be notified when the active status of this session 636 * changes. This is primarily used by the support library and should not be 637 * needed by apps. 638 * 639 * @param listener The listener to add. 640 */ 641 public void addOnActiveChangeListener(OnActiveChangeListener listener) { 642 if (listener == null) { 643 throw new IllegalArgumentException("Listener may not be null"); 644 } 645 mActiveListeners.add(listener); 646 } 647 648 /** 649 * Stops the listener from being notified when the active status of this 650 * session changes. 651 * 652 * @param listener The listener to remove. 653 */ 654 public void removeOnActiveChangeListener(OnActiveChangeListener listener) { 655 if (listener == null) { 656 throw new IllegalArgumentException("Listener may not be null"); 657 } 658 mActiveListeners.remove(listener); 659 } 660 661 /** 662 * Creates an instance from a framework {@link android.media.session.MediaSession} object. 663 * <p> 664 * This method is only supported on API 21+. On API 20 and below, it returns null. 665 * </p> 666 * 667 * @param context The context to use to create the session. 668 * @param mediaSession A {@link android.media.session.MediaSession} object. 669 * @return An equivalent {@link MediaSessionCompat} object, or null if none. 670 * @deprecated Use {@link #fromMediaSession(Context, Object)} instead. 671 */ 672 @Deprecated 673 public static MediaSessionCompat obtain(Context context, Object mediaSession) { 674 return fromMediaSession(context, mediaSession); 675 } 676 677 /** 678 * Creates an instance from a framework {@link android.media.session.MediaSession} object. 679 * <p> 680 * This method is only supported on API 21+. On API 20 and below, it returns null. 681 * </p> 682 * 683 * @param context The context to use to create the session. 684 * @param mediaSession A {@link android.media.session.MediaSession} object. 685 * @return An equivalent {@link MediaSessionCompat} object, or null if none. 686 */ 687 public static MediaSessionCompat fromMediaSession(Context context, Object mediaSession) { 688 if (context == null || mediaSession == null || Build.VERSION.SDK_INT < 21) { 689 return null; 690 } 691 return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession)); 692 } 693 694 /** 695 * Receives transport controls, media buttons, and commands from controllers 696 * and the system. The callback may be set using {@link #setCallback}. 697 */ 698 public abstract static class Callback { 699 final Object mCallbackObj; 700 WeakReference<MediaSessionImpl> mSessionImpl; 701 702 public Callback() { 703 if (android.os.Build.VERSION.SDK_INT >= 24) { 704 mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24()); 705 } else if (android.os.Build.VERSION.SDK_INT >= 23) { 706 mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23()); 707 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 708 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21()); 709 } else { 710 mCallbackObj = null; 711 } 712 } 713 714 /** 715 * Called when a controller has sent a custom command to this session. 716 * The owner of the session may handle custom commands but is not 717 * required to. 718 * 719 * @param command The command name. 720 * @param extras Optional parameters for the command, may be null. 721 * @param cb A result receiver to which a result may be sent by the command, may be null. 722 */ 723 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 724 } 725 726 /** 727 * Override to handle media button events. 728 * 729 * @param mediaButtonEvent The media button event intent. 730 * @return True if the event was handled, false otherwise. 731 */ 732 public boolean onMediaButtonEvent(Intent mediaButtonEvent) { 733 return false; 734 } 735 736 /** 737 * Override to handle requests to prepare playback. During the preparation, a session 738 * should not hold audio focus in order to allow other session play seamlessly. 739 * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED} 740 * after the preparation is done. 741 */ 742 public void onPrepare() { 743 } 744 745 /** 746 * Override to handle requests to prepare for playing a specific mediaId that was provided 747 * by your app. During the preparation, a session should not hold audio focus in order to 748 * allow other session play seamlessly. The state of playback should be updated to 749 * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback 750 * of the prepared content should start in the implementation of {@link #onPlay}. Override 751 * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation. 752 */ 753 public void onPrepareFromMediaId(String mediaId, Bundle extras) { 754 } 755 756 /** 757 * Override to handle requests to prepare playback from a search query. An 758 * empty query indicates that the app may prepare any music. The 759 * implementation should attempt to make a smart choice about what to 760 * play. During the preparation, a session should not hold audio focus in order to allow 761 * other session play seamlessly. The state of playback should be updated to 762 * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. 763 * The playback of the prepared content should start in the implementation of 764 * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for 765 * starting playback without preparation. 766 */ 767 public void onPrepareFromSearch(String query, Bundle extras) { 768 } 769 770 /** 771 * Override to handle requests to prepare a specific media item represented by a URI. 772 * During the preparation, a session should not hold audio focus in order to allow other 773 * session play seamlessly. The state of playback should be updated to 774 * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of 775 * the prepared content should start in the implementation of {@link #onPlay}. Override 776 * {@link #onPlayFromUri} to handle requests for starting playback without preparation. 777 */ 778 public void onPrepareFromUri(Uri uri, Bundle extras) { 779 } 780 781 /** 782 * Override to handle requests to begin playback. 783 */ 784 public void onPlay() { 785 } 786 787 /** 788 * Override to handle requests to play a specific mediaId that was 789 * provided by your app. 790 */ 791 public void onPlayFromMediaId(String mediaId, Bundle extras) { 792 } 793 794 /** 795 * Override to handle requests to begin playback from a search query. An 796 * empty query indicates that the app may play any music. The 797 * implementation should attempt to make a smart choice about what to 798 * play. 799 */ 800 public void onPlayFromSearch(String query, Bundle extras) { 801 } 802 803 /** 804 * Override to handle requests to play a specific media item represented by a URI. 805 */ 806 public void onPlayFromUri(Uri uri, Bundle extras) { 807 } 808 809 /** 810 * Override to handle requests to play an item with a given id from the 811 * play queue. 812 */ 813 public void onSkipToQueueItem(long id) { 814 } 815 816 /** 817 * Override to handle requests to pause playback. 818 */ 819 public void onPause() { 820 } 821 822 /** 823 * Override to handle requests to skip to the next media item. 824 */ 825 public void onSkipToNext() { 826 } 827 828 /** 829 * Override to handle requests to skip to the previous media item. 830 */ 831 public void onSkipToPrevious() { 832 } 833 834 /** 835 * Override to handle requests to fast forward. 836 */ 837 public void onFastForward() { 838 } 839 840 /** 841 * Override to handle requests to rewind. 842 */ 843 public void onRewind() { 844 } 845 846 /** 847 * Override to handle requests to stop playback. 848 */ 849 public void onStop() { 850 } 851 852 /** 853 * Override to handle requests to seek to a specific position in ms. 854 * 855 * @param pos New position to move to, in milliseconds. 856 */ 857 public void onSeekTo(long pos) { 858 } 859 860 /** 861 * Override to handle the item being rated. 862 * 863 * @param rating 864 */ 865 public void onSetRating(RatingCompat rating) { 866 } 867 868 /** 869 * Override to handle the setting of the repeat mode. 870 * <p> 871 * You should call {@link #setRepeatMode} before end of this method in order to notify 872 * the change to the {@link MediaControllerCompat}, or 873 * {@link MediaControllerCompat#getRepeatMode} could return an invalid value. 874 * 875 * @param repeatMode The repeat mode which is one of followings: 876 * {@link PlaybackStateCompat#REPEAT_MODE_NONE}, 877 * {@link PlaybackStateCompat#REPEAT_MODE_ONE}, 878 * {@link PlaybackStateCompat#REPEAT_MODE_ALL} 879 */ 880 public void onSetRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 881 } 882 883 /** 884 * Override to handle the setting of the shuffle mode. 885 * <p> 886 * You should call {@link #setShuffleModeEnabled} before the end of this method in order to 887 * notify the change to the {@link MediaControllerCompat}, or 888 * {@link MediaControllerCompat#isShuffleModeEnabled} could return an invalid value. 889 * 890 * @param enabled true when the shuffle mode is enabled, false otherwise. 891 */ 892 public void onSetShuffleModeEnabled(boolean enabled) { 893 } 894 895 /** 896 * Called when a {@link MediaControllerCompat} wants a 897 * {@link PlaybackStateCompat.CustomAction} to be performed. 898 * 899 * @param action The action that was originally sent in the 900 * {@link PlaybackStateCompat.CustomAction}. 901 * @param extras Optional extras specified by the 902 * {@link MediaControllerCompat}. 903 * @see #ACTION_FLAG_AS_INAPPROPRIATE 904 * @see #ACTION_SKIP_AD 905 */ 906 public void onCustomAction(String action, Bundle extras) { 907 } 908 909 /** 910 * Called when a {@link MediaControllerCompat} wants to add a {@link QueueItem} 911 * with the given {@link MediaDescriptionCompat description} at the end of the play queue. 912 * 913 * @param description The {@link MediaDescriptionCompat} for creating the {@link QueueItem} 914 * to be inserted. 915 */ 916 public void onAddQueueItem(MediaDescriptionCompat description) { 917 } 918 919 /** 920 * Called when a {@link MediaControllerCompat} wants to add a {@link QueueItem} 921 * with the given {@link MediaDescriptionCompat description} at the specified position 922 * in the play queue. 923 * 924 * @param description The {@link MediaDescriptionCompat} for creating the {@link QueueItem} 925 * to be inserted. 926 * @param index The index at which the created {@link QueueItem} is to be inserted. 927 */ 928 public void onAddQueueItem(MediaDescriptionCompat description, int index) { 929 } 930 931 /** 932 * Called when a {@link MediaControllerCompat} wants to remove the first occurrence of the 933 * specified {@link QueueItem} with the given {@link MediaDescriptionCompat description} 934 * in the play queue. 935 * 936 * @param description The {@link MediaDescriptionCompat} for denoting the {@link QueueItem} 937 * to be removed. 938 */ 939 public void onRemoveQueueItem(MediaDescriptionCompat description) { 940 } 941 942 /** 943 * Called when a {@link MediaControllerCompat} wants to remove a {@link QueueItem} at the 944 * specified position in the play queue. 945 * 946 * @param index The index of the element to be removed. 947 */ 948 public void onRemoveQueueItemAt(int index) { 949 } 950 951 private class StubApi21 implements MediaSessionCompatApi21.Callback { 952 953 StubApi21() { 954 } 955 956 @Override 957 public void onCommand(String command, Bundle extras, ResultReceiver cb) { 958 if (command.equals(MediaControllerCompat.COMMAND_GET_EXTRA_BINDER)) { 959 MediaSessionImplApi21 impl = (MediaSessionImplApi21) mSessionImpl.get(); 960 if (impl != null) { 961 Bundle result = new Bundle(); 962 IMediaSession extraBinder = impl.getSessionToken().getExtraBinder(); 963 BundleCompat.putBinder(result, EXTRA_BINDER, 964 extraBinder == null ? null : extraBinder.asBinder()); 965 cb.send(0, result); 966 } 967 } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM)) { 968 extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader()); 969 Callback.this.onAddQueueItem( 970 (MediaDescriptionCompat) extras.getParcelable( 971 MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION)); 972 } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM_AT)) { 973 extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader()); 974 Callback.this.onAddQueueItem( 975 (MediaDescriptionCompat) extras.getParcelable( 976 MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION), 977 extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX)); 978 } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM)) { 979 extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader()); 980 Callback.this.onRemoveQueueItem( 981 (MediaDescriptionCompat) extras.getParcelable( 982 MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION)); 983 } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM_AT)) { 984 Callback.this.onRemoveQueueItemAt( 985 extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX)); 986 } else { 987 Callback.this.onCommand(command, extras, cb); 988 } 989 } 990 991 @Override 992 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 993 return Callback.this.onMediaButtonEvent(mediaButtonIntent); 994 } 995 996 @Override 997 public void onPlay() { 998 Callback.this.onPlay(); 999 } 1000 1001 @Override 1002 public void onPlayFromMediaId(String mediaId, Bundle extras) { 1003 Callback.this.onPlayFromMediaId(mediaId, extras); 1004 } 1005 1006 @Override 1007 public void onPlayFromSearch(String search, Bundle extras) { 1008 Callback.this.onPlayFromSearch(search, extras); 1009 } 1010 1011 @Override 1012 public void onSkipToQueueItem(long id) { 1013 Callback.this.onSkipToQueueItem(id); 1014 } 1015 1016 @Override 1017 public void onPause() { 1018 Callback.this.onPause(); 1019 } 1020 1021 @Override 1022 public void onSkipToNext() { 1023 Callback.this.onSkipToNext(); 1024 } 1025 1026 @Override 1027 public void onSkipToPrevious() { 1028 Callback.this.onSkipToPrevious(); 1029 } 1030 1031 @Override 1032 public void onFastForward() { 1033 Callback.this.onFastForward(); 1034 } 1035 1036 @Override 1037 public void onRewind() { 1038 Callback.this.onRewind(); 1039 } 1040 1041 @Override 1042 public void onStop() { 1043 Callback.this.onStop(); 1044 } 1045 1046 @Override 1047 public void onSeekTo(long pos) { 1048 Callback.this.onSeekTo(pos); 1049 } 1050 1051 @Override 1052 public void onSetRating(Object ratingObj) { 1053 Callback.this.onSetRating(RatingCompat.fromRating(ratingObj)); 1054 } 1055 1056 @Override 1057 public void onCustomAction(String action, Bundle extras) { 1058 if (action.equals(ACTION_PLAY_FROM_URI)) { 1059 Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI); 1060 Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS); 1061 Callback.this.onPlayFromUri(uri, bundle); 1062 } else if (action.equals(ACTION_PREPARE)) { 1063 Callback.this.onPrepare(); 1064 } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) { 1065 String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID); 1066 Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS); 1067 Callback.this.onPrepareFromMediaId(mediaId, bundle); 1068 } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) { 1069 String query = extras.getString(ACTION_ARGUMENT_QUERY); 1070 Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS); 1071 Callback.this.onPrepareFromSearch(query, bundle); 1072 } else if (action.equals(ACTION_PREPARE_FROM_URI)) { 1073 Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI); 1074 Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS); 1075 Callback.this.onPrepareFromUri(uri, bundle); 1076 } else if (action.equals(ACTION_SET_REPEAT_MODE)) { 1077 int repeatMode = extras.getInt(ACTION_ARGUMENT_REPEAT_MODE); 1078 Callback.this.onSetRepeatMode(repeatMode); 1079 } else if (action.equals(ACTION_SET_SHUFFLE_MODE_ENABLED)) { 1080 boolean enabled = extras.getBoolean(ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED); 1081 Callback.this.onSetShuffleModeEnabled(enabled); 1082 } else { 1083 Callback.this.onCustomAction(action, extras); 1084 } 1085 } 1086 } 1087 1088 private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback { 1089 1090 StubApi23() { 1091 } 1092 1093 @Override 1094 public void onPlayFromUri(Uri uri, Bundle extras) { 1095 Callback.this.onPlayFromUri(uri, extras); 1096 } 1097 } 1098 1099 private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback { 1100 1101 StubApi24() { 1102 } 1103 1104 @Override 1105 public void onPrepare() { 1106 Callback.this.onPrepare(); 1107 } 1108 1109 @Override 1110 public void onPrepareFromMediaId(String mediaId, Bundle extras) { 1111 Callback.this.onPrepareFromMediaId(mediaId, extras); 1112 } 1113 1114 @Override 1115 public void onPrepareFromSearch(String query, Bundle extras) { 1116 Callback.this.onPrepareFromSearch(query, extras); 1117 } 1118 1119 @Override 1120 public void onPrepareFromUri(Uri uri, Bundle extras) { 1121 Callback.this.onPrepareFromUri(uri, extras); 1122 } 1123 } 1124 } 1125 1126 /** 1127 * Represents an ongoing session. This may be passed to apps by the session 1128 * owner to allow them to create a {@link MediaControllerCompat} to communicate with 1129 * the session. 1130 */ 1131 public static final class Token implements Parcelable { 1132 private final Object mInner; 1133 private final IMediaSession mExtraBinder; 1134 1135 Token(Object inner) { 1136 this(inner, null); 1137 } 1138 1139 Token(Object inner, IMediaSession extraBinder) { 1140 mInner = inner; 1141 mExtraBinder = extraBinder; 1142 } 1143 1144 /** 1145 * Creates a compat Token from a framework 1146 * {@link android.media.session.MediaSession.Token} object. 1147 * <p> 1148 * This method is only supported on 1149 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 1150 * </p> 1151 * 1152 * @param token The framework token object. 1153 * @return A compat Token for use with {@link MediaControllerCompat}. 1154 */ 1155 public static Token fromToken(Object token) { 1156 return fromToken(token, null); 1157 } 1158 1159 /** 1160 * Creates a compat Token from a framework 1161 * {@link android.media.session.MediaSession.Token} object, and the extra binder. 1162 * <p> 1163 * This method is only supported on 1164 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 1165 * </p> 1166 * 1167 * @param token The framework token object. 1168 * @param extraBinder The extra binder. 1169 * @return A compat Token for use with {@link MediaControllerCompat}. 1170 * @hide 1171 */ 1172 @RestrictTo(LIBRARY_GROUP) 1173 public static Token fromToken(Object token, IMediaSession extraBinder) { 1174 if (token == null || android.os.Build.VERSION.SDK_INT < 21) { 1175 return null; 1176 } 1177 return new Token(MediaSessionCompatApi21.verifyToken(token), extraBinder); 1178 } 1179 1180 @Override 1181 public int describeContents() { 1182 return 0; 1183 } 1184 1185 @Override 1186 public void writeToParcel(Parcel dest, int flags) { 1187 if (android.os.Build.VERSION.SDK_INT >= 21) { 1188 dest.writeParcelable((Parcelable) mInner, flags); 1189 dest.writeStrongBinder(mExtraBinder == null ? null : mExtraBinder.asBinder()); 1190 } else { 1191 dest.writeStrongBinder((IBinder) mInner); 1192 } 1193 } 1194 1195 @Override 1196 public int hashCode() { 1197 if (mInner == null) { 1198 return 0; 1199 } 1200 return mInner.hashCode(); 1201 } 1202 1203 @Override 1204 public boolean equals(Object obj) { 1205 if (this == obj) { 1206 return true; 1207 } 1208 if (!(obj instanceof Token)) { 1209 return false; 1210 } 1211 1212 Token other = (Token) obj; 1213 if (mInner == null) { 1214 return other.mInner == null; 1215 } 1216 if (other.mInner == null) { 1217 return false; 1218 } 1219 return mInner.equals(other.mInner); 1220 } 1221 1222 /** 1223 * Gets the underlying framework {@link android.media.session.MediaSession.Token} object. 1224 * <p> 1225 * This method is only supported on API 21+. 1226 * </p> 1227 * 1228 * @return The underlying {@link android.media.session.MediaSession.Token} object, 1229 * or null if none. 1230 */ 1231 public Object getToken() { 1232 return mInner; 1233 } 1234 1235 /** 1236 * @hide 1237 */ 1238 @RestrictTo(LIBRARY_GROUP) 1239 public IMediaSession getExtraBinder() { 1240 return mExtraBinder; 1241 } 1242 1243 public static final Parcelable.Creator<Token> CREATOR 1244 = new Parcelable.Creator<Token>() { 1245 @Override 1246 public Token createFromParcel(Parcel in) { 1247 Object inner; 1248 IMediaSession extraBinder = null; 1249 if (android.os.Build.VERSION.SDK_INT >= 21) { 1250 inner = in.readParcelable(null); 1251 extraBinder = IMediaSession.Stub.asInterface(in.readStrongBinder()); 1252 } else { 1253 inner = in.readStrongBinder(); 1254 } 1255 return new Token(inner, extraBinder); 1256 } 1257 1258 @Override 1259 public Token[] newArray(int size) { 1260 return new Token[size]; 1261 } 1262 }; 1263 } 1264 1265 /** 1266 * A single item that is part of the play queue. It contains a description 1267 * of the item and its id in the queue. 1268 */ 1269 public static final class QueueItem implements Parcelable { 1270 /** 1271 * This id is reserved. No items can be explicitly assigned this id. 1272 */ 1273 public static final int UNKNOWN_ID = -1; 1274 1275 private final MediaDescriptionCompat mDescription; 1276 private final long mId; 1277 1278 private Object mItem; 1279 1280 /** 1281 * Create a new {@link MediaSessionCompat.QueueItem}. 1282 * 1283 * @param description The {@link MediaDescriptionCompat} for this item. 1284 * @param id An identifier for this item. It must be unique within the 1285 * play queue and cannot be {@link #UNKNOWN_ID}. 1286 */ 1287 public QueueItem(MediaDescriptionCompat description, long id) { 1288 this(null, description, id); 1289 } 1290 1291 private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) { 1292 if (description == null) { 1293 throw new IllegalArgumentException("Description cannot be null."); 1294 } 1295 if (id == UNKNOWN_ID) { 1296 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); 1297 } 1298 mDescription = description; 1299 mId = id; 1300 mItem = queueItem; 1301 } 1302 1303 QueueItem(Parcel in) { 1304 mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in); 1305 mId = in.readLong(); 1306 } 1307 1308 /** 1309 * Get the description for this item. 1310 */ 1311 public MediaDescriptionCompat getDescription() { 1312 return mDescription; 1313 } 1314 1315 /** 1316 * Get the queue id for this item. 1317 */ 1318 public long getQueueId() { 1319 return mId; 1320 } 1321 1322 @Override 1323 public void writeToParcel(Parcel dest, int flags) { 1324 mDescription.writeToParcel(dest, flags); 1325 dest.writeLong(mId); 1326 } 1327 1328 @Override 1329 public int describeContents() { 1330 return 0; 1331 } 1332 1333 /** 1334 * Get the underlying 1335 * {@link android.media.session.MediaSession.QueueItem}. 1336 * <p> 1337 * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null 1338 * is returned. 1339 * 1340 * @return The underlying 1341 * {@link android.media.session.MediaSession.QueueItem} or null. 1342 */ 1343 public Object getQueueItem() { 1344 if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) { 1345 return mItem; 1346 } 1347 mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(), 1348 mId); 1349 return mItem; 1350 } 1351 1352 /** 1353 * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem} 1354 * object. 1355 * <p> 1356 * This method is only supported on API 21+. On API 20 and below, it returns null. 1357 * </p> 1358 * 1359 * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object. 1360 * @return An equivalent {@link QueueItem} object, or null if none. 1361 * @deprecated Use {@link #fromQueueItem(Object)} instead. 1362 */ 1363 @Deprecated 1364 public static QueueItem obtain(Object queueItem) { 1365 return fromQueueItem(queueItem); 1366 } 1367 1368 /** 1369 * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem} 1370 * object. 1371 * <p> 1372 * This method is only supported on API 21+. On API 20 and below, it returns null. 1373 * </p> 1374 * 1375 * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object. 1376 * @return An equivalent {@link QueueItem} object, or null if none. 1377 */ 1378 public static QueueItem fromQueueItem(Object queueItem) { 1379 if (queueItem == null || Build.VERSION.SDK_INT < 21) { 1380 return null; 1381 } 1382 Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem); 1383 MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription( 1384 descriptionObj); 1385 long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem); 1386 return new QueueItem(queueItem, description, id); 1387 } 1388 1389 /** 1390 * Creates a list of {@link QueueItem} objects from a framework 1391 * {@link android.media.session.MediaSession.QueueItem} object list. 1392 * <p> 1393 * This method is only supported on API 21+. On API 20 and below, it returns null. 1394 * </p> 1395 * 1396 * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects. 1397 * @return An equivalent list of {@link QueueItem} objects, or null if none. 1398 */ 1399 public static List<QueueItem> fromQueueItemList(List<?> itemList) { 1400 if (itemList == null || Build.VERSION.SDK_INT < 21) { 1401 return null; 1402 } 1403 List<QueueItem> items = new ArrayList<>(); 1404 for (Object itemObj : itemList) { 1405 items.add(fromQueueItem(itemObj)); 1406 } 1407 return items; 1408 } 1409 1410 public static final Creator<MediaSessionCompat.QueueItem> CREATOR 1411 = new Creator<MediaSessionCompat.QueueItem>() { 1412 1413 @Override 1414 public MediaSessionCompat.QueueItem createFromParcel(Parcel p) { 1415 return new MediaSessionCompat.QueueItem(p); 1416 } 1417 1418 @Override 1419 public MediaSessionCompat.QueueItem[] newArray(int size) { 1420 return new MediaSessionCompat.QueueItem[size]; 1421 } 1422 }; 1423 1424 @Override 1425 public String toString() { 1426 return "MediaSession.QueueItem {" + 1427 "Description=" + mDescription + 1428 ", Id=" + mId + " }"; 1429 } 1430 } 1431 1432 /** 1433 * This is a wrapper for {@link ResultReceiver} for sending over aidl 1434 * interfaces. The framework version was not exposed to aidls until 1435 * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 1436 */ 1437 static final class ResultReceiverWrapper implements Parcelable { 1438 private ResultReceiver mResultReceiver; 1439 1440 public ResultReceiverWrapper(ResultReceiver resultReceiver) { 1441 mResultReceiver = resultReceiver; 1442 } 1443 1444 ResultReceiverWrapper(Parcel in) { 1445 mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in); 1446 } 1447 1448 public static final Creator<ResultReceiverWrapper> 1449 CREATOR = new Creator<ResultReceiverWrapper>() { 1450 @Override 1451 public ResultReceiverWrapper createFromParcel(Parcel p) { 1452 return new ResultReceiverWrapper(p); 1453 } 1454 1455 @Override 1456 public ResultReceiverWrapper[] newArray(int size) { 1457 return new ResultReceiverWrapper[size]; 1458 } 1459 }; 1460 1461 @Override 1462 public int describeContents() { 1463 return 0; 1464 } 1465 1466 @Override 1467 public void writeToParcel(Parcel dest, int flags) { 1468 mResultReceiver.writeToParcel(dest, flags); 1469 } 1470 } 1471 1472 public interface OnActiveChangeListener { 1473 void onActiveChanged(); 1474 } 1475 1476 interface MediaSessionImpl { 1477 void setCallback(Callback callback, Handler handler); 1478 void setFlags(@SessionFlags int flags); 1479 void setPlaybackToLocal(int stream); 1480 void setPlaybackToRemote(VolumeProviderCompat volumeProvider); 1481 void setActive(boolean active); 1482 boolean isActive(); 1483 void sendSessionEvent(String event, Bundle extras); 1484 void release(); 1485 Token getSessionToken(); 1486 void setPlaybackState(PlaybackStateCompat state); 1487 void setMetadata(MediaMetadataCompat metadata); 1488 1489 void setSessionActivity(PendingIntent pi); 1490 1491 void setMediaButtonReceiver(PendingIntent mbr); 1492 void setQueue(List<QueueItem> queue); 1493 void setQueueTitle(CharSequence title); 1494 1495 void setRatingType(@RatingCompat.Style int type); 1496 void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode); 1497 void setShuffleModeEnabled(boolean enabled); 1498 void setExtras(Bundle extras); 1499 1500 Object getMediaSession(); 1501 1502 Object getRemoteControlClient(); 1503 1504 String getCallingPackage(); 1505 } 1506 1507 static class MediaSessionImplBase implements MediaSessionImpl { 1508 private final Context mContext; 1509 private final ComponentName mMediaButtonReceiverComponentName; 1510 private final PendingIntent mMediaButtonReceiverIntent; 1511 private final Object mRccObj; 1512 private final MediaSessionStub mStub; 1513 private final Token mToken; 1514 final String mPackageName; 1515 final String mTag; 1516 final AudioManager mAudioManager; 1517 1518 final Object mLock = new Object(); 1519 final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks 1520 = new RemoteCallbackList<>(); 1521 1522 private MessageHandler mHandler; 1523 boolean mDestroyed = false; 1524 private boolean mIsActive = false; 1525 private boolean mIsRccRegistered = false; 1526 private boolean mIsMbrRegistered = false; 1527 volatile Callback mCallback; 1528 1529 @SessionFlags int mFlags; 1530 1531 MediaMetadataCompat mMetadata; 1532 PlaybackStateCompat mState; 1533 PendingIntent mSessionActivity; 1534 List<QueueItem> mQueue; 1535 CharSequence mQueueTitle; 1536 @RatingCompat.Style int mRatingType; 1537 @PlaybackStateCompat.RepeatMode int mRepeatMode; 1538 boolean mShuffleModeEnabled; 1539 Bundle mExtras; 1540 1541 int mVolumeType; 1542 int mLocalStream; 1543 VolumeProviderCompat mVolumeProvider; 1544 1545 private VolumeProviderCompat.Callback mVolumeCallback 1546 = new VolumeProviderCompat.Callback() { 1547 @Override 1548 public void onVolumeChanged(VolumeProviderCompat volumeProvider) { 1549 if (mVolumeProvider != volumeProvider) { 1550 return; 1551 } 1552 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 1553 volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(), 1554 volumeProvider.getCurrentVolume()); 1555 sendVolumeInfoChanged(info); 1556 } 1557 }; 1558 1559 public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent, 1560 PendingIntent mbrIntent) { 1561 if (mbrComponent == null) { 1562 throw new IllegalArgumentException( 1563 "MediaButtonReceiver component may not be null."); 1564 } 1565 mContext = context; 1566 mPackageName = context.getPackageName(); 1567 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1568 mTag = tag; 1569 mMediaButtonReceiverComponentName = mbrComponent; 1570 mMediaButtonReceiverIntent = mbrIntent; 1571 mStub = new MediaSessionStub(); 1572 mToken = new Token(mStub); 1573 1574 mRatingType = RatingCompat.RATING_NONE; 1575 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL; 1576 mLocalStream = AudioManager.STREAM_MUSIC; 1577 if (android.os.Build.VERSION.SDK_INT >= 14) { 1578 mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent); 1579 } else { 1580 mRccObj = null; 1581 } 1582 } 1583 1584 @Override 1585 public void setCallback(Callback callback, Handler handler) { 1586 mCallback = callback; 1587 if (callback == null) { 1588 // There's nothing to unregister on API < 18 since media buttons 1589 // all go through the media button receiver 1590 if (android.os.Build.VERSION.SDK_INT >= 18) { 1591 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null); 1592 } 1593 if (android.os.Build.VERSION.SDK_INT >= 19) { 1594 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null); 1595 } 1596 } else { 1597 if (handler == null) { 1598 handler = new Handler(); 1599 } 1600 synchronized (mLock) { 1601 mHandler = new MessageHandler(handler.getLooper()); 1602 } 1603 MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() { 1604 @Override 1605 public void onSetRating(Object ratingObj) { 1606 postToHandler(MessageHandler.MSG_RATE, 1607 RatingCompat.fromRating(ratingObj)); 1608 } 1609 1610 @Override 1611 public void onSeekTo(long pos) { 1612 postToHandler(MessageHandler.MSG_SEEK_TO, pos); 1613 } 1614 }; 1615 if (android.os.Build.VERSION.SDK_INT >= 18) { 1616 Object onPositionUpdateObj = MediaSessionCompatApi18 1617 .createPlaybackPositionUpdateListener(cb19); 1618 MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, 1619 onPositionUpdateObj); 1620 } 1621 if (android.os.Build.VERSION.SDK_INT >= 19) { 1622 Object onMetadataUpdateObj = MediaSessionCompatApi19 1623 .createMetadataUpdateListener(cb19); 1624 MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, 1625 onMetadataUpdateObj); 1626 } 1627 } 1628 } 1629 1630 void postToHandler(int what) { 1631 postToHandler(what, null); 1632 } 1633 1634 void postToHandler(int what, int arg1) { 1635 postToHandler(what, null, arg1); 1636 } 1637 1638 void postToHandler(int what, Object obj) { 1639 postToHandler(what, obj, null); 1640 } 1641 1642 void postToHandler(int what, Object obj, int arg1) { 1643 synchronized (mLock) { 1644 if (mHandler != null) { 1645 mHandler.post(what, obj, arg1); 1646 } 1647 } 1648 } 1649 1650 void postToHandler(int what, Object obj, Bundle extras) { 1651 synchronized (mLock) { 1652 if (mHandler != null) { 1653 mHandler.post(what, obj, extras); 1654 } 1655 } 1656 } 1657 1658 @Override 1659 public void setFlags(@SessionFlags int flags) { 1660 synchronized (mLock) { 1661 mFlags = flags; 1662 } 1663 update(); 1664 } 1665 1666 @Override 1667 public void setPlaybackToLocal(int stream) { 1668 if (mVolumeProvider != null) { 1669 mVolumeProvider.setCallback(null); 1670 } 1671 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL; 1672 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 1673 VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 1674 mAudioManager.getStreamMaxVolume(mLocalStream), 1675 mAudioManager.getStreamVolume(mLocalStream)); 1676 sendVolumeInfoChanged(info); 1677 } 1678 1679 @Override 1680 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 1681 if (volumeProvider == null) { 1682 throw new IllegalArgumentException("volumeProvider may not be null"); 1683 } 1684 if (mVolumeProvider != null) { 1685 mVolumeProvider.setCallback(null); 1686 } 1687 mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE; 1688 mVolumeProvider = volumeProvider; 1689 ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream, 1690 mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(), 1691 mVolumeProvider.getCurrentVolume()); 1692 sendVolumeInfoChanged(info); 1693 1694 volumeProvider.setCallback(mVolumeCallback); 1695 } 1696 1697 @Override 1698 public void setActive(boolean active) { 1699 if (active == mIsActive) { 1700 return; 1701 } 1702 mIsActive = active; 1703 if (update()) { 1704 setMetadata(mMetadata); 1705 setPlaybackState(mState); 1706 } 1707 } 1708 1709 @Override 1710 public boolean isActive() { 1711 return mIsActive; 1712 } 1713 1714 @Override 1715 public void sendSessionEvent(String event, Bundle extras) { 1716 sendEvent(event, extras); 1717 } 1718 1719 @Override 1720 public void release() { 1721 mIsActive = false; 1722 mDestroyed = true; 1723 update(); 1724 sendSessionDestroyed(); 1725 } 1726 1727 @Override 1728 public Token getSessionToken() { 1729 return mToken; 1730 } 1731 1732 @Override 1733 public void setPlaybackState(PlaybackStateCompat state) { 1734 synchronized (mLock) { 1735 mState = state; 1736 } 1737 sendState(state); 1738 if (!mIsActive) { 1739 // Don't set the state until after the RCC is registered 1740 return; 1741 } 1742 if (state == null) { 1743 if (android.os.Build.VERSION.SDK_INT >= 14) { 1744 MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE); 1745 MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0); 1746 } 1747 } else { 1748 // Set state 1749 if (android.os.Build.VERSION.SDK_INT >= 18) { 1750 MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(), 1751 state.getPlaybackSpeed(), state.getLastPositionUpdateTime()); 1752 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1753 MediaSessionCompatApi14.setState(mRccObj, state.getState()); 1754 } 1755 1756 // Set transport control flags 1757 if (android.os.Build.VERSION.SDK_INT >= 19) { 1758 MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions()); 1759 } else if (android.os.Build.VERSION.SDK_INT >= 18) { 1760 MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions()); 1761 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1762 MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions()); 1763 } 1764 } 1765 } 1766 1767 @Override 1768 public void setMetadata(MediaMetadataCompat metadata) { 1769 if (metadata != null) { 1770 // Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the 1771 // metadata if necessary. Bitmaps can be scaled down if they are large. 1772 metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build(); 1773 } 1774 1775 synchronized (mLock) { 1776 mMetadata = metadata; 1777 } 1778 sendMetadata(metadata); 1779 if (!mIsActive) { 1780 // Don't set metadata until after the rcc has been registered 1781 return; 1782 } 1783 if (android.os.Build.VERSION.SDK_INT >= 19) { 1784 MediaSessionCompatApi19.setMetadata(mRccObj, 1785 metadata == null ? null : metadata.getBundle(), 1786 mState == null ? 0 : mState.getActions()); 1787 } else if (android.os.Build.VERSION.SDK_INT >= 14) { 1788 MediaSessionCompatApi14.setMetadata(mRccObj, 1789 metadata == null ? null : metadata.getBundle()); 1790 } 1791 } 1792 1793 @Override 1794 public void setSessionActivity(PendingIntent pi) { 1795 synchronized (mLock) { 1796 mSessionActivity = pi; 1797 } 1798 } 1799 1800 @Override 1801 public void setMediaButtonReceiver(PendingIntent mbr) { 1802 // Do nothing, changing this is not supported before API 21. 1803 } 1804 1805 @Override 1806 public void setQueue(List<QueueItem> queue) { 1807 mQueue = queue; 1808 sendQueue(queue); 1809 } 1810 1811 @Override 1812 public void setQueueTitle(CharSequence title) { 1813 mQueueTitle = title; 1814 sendQueueTitle(title); 1815 } 1816 1817 @Override 1818 public Object getMediaSession() { 1819 return null; 1820 } 1821 1822 @Override 1823 public Object getRemoteControlClient() { 1824 return mRccObj; 1825 } 1826 1827 @Override 1828 public String getCallingPackage() { 1829 return null; 1830 } 1831 1832 @Override 1833 public void setRatingType(@RatingCompat.Style int type) { 1834 mRatingType = type; 1835 } 1836 1837 @Override 1838 public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 1839 if (mRepeatMode != repeatMode) { 1840 mRepeatMode = repeatMode; 1841 sendRepeatMode(repeatMode); 1842 } 1843 } 1844 1845 @Override 1846 public void setShuffleModeEnabled(boolean enabled) { 1847 if (mShuffleModeEnabled != enabled) { 1848 mShuffleModeEnabled = enabled; 1849 sendShuffleModeEnabled(enabled); 1850 } 1851 } 1852 1853 @Override 1854 public void setExtras(Bundle extras) { 1855 mExtras = extras; 1856 sendExtras(extras); 1857 } 1858 1859 // Registers/unregisters the RCC and MediaButtonEventReceiver as needed. 1860 private boolean update() { 1861 boolean registeredRcc = false; 1862 if (mIsActive) { 1863 // Register a MBR if it's supported, unregister it 1864 // if support was removed. 1865 if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) { 1866 if (android.os.Build.VERSION.SDK_INT >= 18) { 1867 MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext, 1868 mMediaButtonReceiverIntent, 1869 mMediaButtonReceiverComponentName); 1870 } else { 1871 AudioManager am = (AudioManager) mContext.getSystemService( 1872 Context.AUDIO_SERVICE); 1873 am.registerMediaButtonEventReceiver(mMediaButtonReceiverComponentName); 1874 } 1875 mIsMbrRegistered = true; 1876 } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) { 1877 if (android.os.Build.VERSION.SDK_INT >= 18) { 1878 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1879 mMediaButtonReceiverIntent, 1880 mMediaButtonReceiverComponentName); 1881 } else { 1882 AudioManager am = (AudioManager) mContext.getSystemService( 1883 Context.AUDIO_SERVICE); 1884 am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName); 1885 } 1886 mIsMbrRegistered = false; 1887 } 1888 // On API 14+ register a RCC if it's supported, unregister it if 1889 // not. 1890 if (android.os.Build.VERSION.SDK_INT >= 14) { 1891 if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) { 1892 MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj); 1893 mIsRccRegistered = true; 1894 registeredRcc = true; 1895 } else if (mIsRccRegistered 1896 && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) { 1897 // RCC keeps the state while the system resets its state internally when 1898 // we register RCC. Reset the state so that the states in RCC and the system 1899 // are in sync when we re-register the RCC. 1900 MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE); 1901 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1902 mIsRccRegistered = false; 1903 } 1904 } 1905 } else { 1906 // When inactive remove any registered components. 1907 if (mIsMbrRegistered) { 1908 if (android.os.Build.VERSION.SDK_INT >= 18) { 1909 MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext, 1910 mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName); 1911 } else { 1912 AudioManager am = (AudioManager) mContext.getSystemService( 1913 Context.AUDIO_SERVICE); 1914 am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName); 1915 } 1916 mIsMbrRegistered = false; 1917 } 1918 if (mIsRccRegistered) { 1919 // RCC keeps the state while the system resets its state internally when 1920 // we register RCC. Reset the state so that the states in RCC and the system 1921 // are in sync when we re-register the RCC. 1922 MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE); 1923 MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj); 1924 mIsRccRegistered = false; 1925 } 1926 } 1927 return registeredRcc; 1928 } 1929 1930 void adjustVolume(int direction, int flags) { 1931 if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1932 if (mVolumeProvider != null) { 1933 mVolumeProvider.onAdjustVolume(direction); 1934 } 1935 } else { 1936 mAudioManager.adjustStreamVolume(mLocalStream, direction, flags); 1937 } 1938 } 1939 1940 void setVolumeTo(int value, int flags) { 1941 if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1942 if (mVolumeProvider != null) { 1943 mVolumeProvider.onSetVolumeTo(value); 1944 } 1945 } else { 1946 mAudioManager.setStreamVolume(mLocalStream, value, flags); 1947 } 1948 } 1949 1950 PlaybackStateCompat getStateWithUpdatedPosition() { 1951 PlaybackStateCompat state; 1952 long duration = -1; 1953 synchronized (mLock) { 1954 state = mState; 1955 if (mMetadata != null 1956 && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) { 1957 duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION); 1958 } 1959 } 1960 1961 PlaybackStateCompat result = null; 1962 if (state != null) { 1963 if (state.getState() == PlaybackStateCompat.STATE_PLAYING 1964 || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING 1965 || state.getState() == PlaybackStateCompat.STATE_REWINDING) { 1966 long updateTime = state.getLastPositionUpdateTime(); 1967 long currentTime = SystemClock.elapsedRealtime(); 1968 if (updateTime > 0) { 1969 long position = (long) (state.getPlaybackSpeed() 1970 * (currentTime - updateTime)) + state.getPosition(); 1971 if (duration >= 0 && position > duration) { 1972 position = duration; 1973 } else if (position < 0) { 1974 position = 0; 1975 } 1976 PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder( 1977 state); 1978 builder.setState(state.getState(), position, state.getPlaybackSpeed(), 1979 currentTime); 1980 result = builder.build(); 1981 } 1982 } 1983 } 1984 return result == null ? state : result; 1985 } 1986 1987 void sendVolumeInfoChanged(ParcelableVolumeInfo info) { 1988 int size = mControllerCallbacks.beginBroadcast(); 1989 for (int i = size - 1; i >= 0; i--) { 1990 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 1991 try { 1992 cb.onVolumeInfoChanged(info); 1993 } catch (RemoteException e) { 1994 } 1995 } 1996 mControllerCallbacks.finishBroadcast(); 1997 } 1998 1999 private void sendSessionDestroyed() { 2000 int size = mControllerCallbacks.beginBroadcast(); 2001 for (int i = size - 1; i >= 0; i--) { 2002 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2003 try { 2004 cb.onSessionDestroyed(); 2005 } catch (RemoteException e) { 2006 } 2007 } 2008 mControllerCallbacks.finishBroadcast(); 2009 mControllerCallbacks.kill(); 2010 } 2011 2012 private void sendEvent(String event, Bundle extras) { 2013 int size = mControllerCallbacks.beginBroadcast(); 2014 for (int i = size - 1; i >= 0; i--) { 2015 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2016 try { 2017 cb.onEvent(event, extras); 2018 } catch (RemoteException e) { 2019 } 2020 } 2021 mControllerCallbacks.finishBroadcast(); 2022 } 2023 2024 private void sendState(PlaybackStateCompat state) { 2025 int size = mControllerCallbacks.beginBroadcast(); 2026 for (int i = size - 1; i >= 0; i--) { 2027 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2028 try { 2029 cb.onPlaybackStateChanged(state); 2030 } catch (RemoteException e) { 2031 } 2032 } 2033 mControllerCallbacks.finishBroadcast(); 2034 } 2035 2036 private void sendMetadata(MediaMetadataCompat metadata) { 2037 int size = mControllerCallbacks.beginBroadcast(); 2038 for (int i = size - 1; i >= 0; i--) { 2039 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2040 try { 2041 cb.onMetadataChanged(metadata); 2042 } catch (RemoteException e) { 2043 } 2044 } 2045 mControllerCallbacks.finishBroadcast(); 2046 } 2047 2048 private void sendQueue(List<QueueItem> queue) { 2049 int size = mControllerCallbacks.beginBroadcast(); 2050 for (int i = size - 1; i >= 0; i--) { 2051 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2052 try { 2053 cb.onQueueChanged(queue); 2054 } catch (RemoteException e) { 2055 } 2056 } 2057 mControllerCallbacks.finishBroadcast(); 2058 } 2059 2060 private void sendQueueTitle(CharSequence queueTitle) { 2061 int size = mControllerCallbacks.beginBroadcast(); 2062 for (int i = size - 1; i >= 0; i--) { 2063 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2064 try { 2065 cb.onQueueTitleChanged(queueTitle); 2066 } catch (RemoteException e) { 2067 } 2068 } 2069 mControllerCallbacks.finishBroadcast(); 2070 } 2071 2072 private void sendRepeatMode(int repeatMode) { 2073 int size = mControllerCallbacks.beginBroadcast(); 2074 for (int i = size - 1; i >= 0; i--) { 2075 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2076 try { 2077 cb.onRepeatModeChanged(repeatMode); 2078 } catch (RemoteException e) { 2079 } 2080 } 2081 mControllerCallbacks.finishBroadcast(); 2082 } 2083 2084 private void sendShuffleModeEnabled(boolean enabled) { 2085 int size = mControllerCallbacks.beginBroadcast(); 2086 for (int i = size - 1; i >= 0; i--) { 2087 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2088 try { 2089 cb.onShuffleModeChanged(enabled); 2090 } catch (RemoteException e) { 2091 } 2092 } 2093 mControllerCallbacks.finishBroadcast(); 2094 } 2095 2096 private void sendExtras(Bundle extras) { 2097 int size = mControllerCallbacks.beginBroadcast(); 2098 for (int i = size - 1; i >= 0; i--) { 2099 IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i); 2100 try { 2101 cb.onExtrasChanged(extras); 2102 } catch (RemoteException e) { 2103 } 2104 } 2105 mControllerCallbacks.finishBroadcast(); 2106 } 2107 2108 class MediaSessionStub extends IMediaSession.Stub { 2109 @Override 2110 public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) { 2111 postToHandler(MessageHandler.MSG_COMMAND, 2112 new Command(command, args, cb.mResultReceiver)); 2113 } 2114 2115 @Override 2116 public boolean sendMediaButton(KeyEvent mediaButton) { 2117 boolean handlesMediaButtons = 2118 (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0; 2119 if (handlesMediaButtons) { 2120 postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton); 2121 } 2122 return handlesMediaButtons; 2123 } 2124 2125 @Override 2126 public void registerCallbackListener(IMediaControllerCallback cb) { 2127 // If this session is already destroyed tell the caller and 2128 // don't add them. 2129 if (mDestroyed) { 2130 try { 2131 cb.onSessionDestroyed(); 2132 } catch (Exception e) { 2133 // ignored 2134 } 2135 return; 2136 } 2137 mControllerCallbacks.register(cb); 2138 } 2139 2140 @Override 2141 public void unregisterCallbackListener(IMediaControllerCallback cb) { 2142 mControllerCallbacks.unregister(cb); 2143 } 2144 2145 @Override 2146 public String getPackageName() { 2147 // mPackageName is final so doesn't need synchronize block 2148 return mPackageName; 2149 } 2150 2151 @Override 2152 public String getTag() { 2153 // mTag is final so doesn't need synchronize block 2154 return mTag; 2155 } 2156 2157 @Override 2158 public PendingIntent getLaunchPendingIntent() { 2159 synchronized (mLock) { 2160 return mSessionActivity; 2161 } 2162 } 2163 2164 @Override 2165 @SessionFlags 2166 public long getFlags() { 2167 synchronized (mLock) { 2168 return mFlags; 2169 } 2170 } 2171 2172 @Override 2173 public ParcelableVolumeInfo getVolumeAttributes() { 2174 int controlType; 2175 int max; 2176 int current; 2177 int stream; 2178 int volumeType; 2179 synchronized (mLock) { 2180 volumeType = mVolumeType; 2181 stream = mLocalStream; 2182 VolumeProviderCompat vp = mVolumeProvider; 2183 if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 2184 controlType = vp.getVolumeControl(); 2185 max = vp.getMaxVolume(); 2186 current = vp.getCurrentVolume(); 2187 } else { 2188 controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 2189 max = mAudioManager.getStreamMaxVolume(stream); 2190 current = mAudioManager.getStreamVolume(stream); 2191 } 2192 } 2193 return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current); 2194 } 2195 2196 @Override 2197 public void adjustVolume(int direction, int flags, String packageName) { 2198 MediaSessionImplBase.this.adjustVolume(direction, flags); 2199 } 2200 2201 @Override 2202 public void setVolumeTo(int value, int flags, String packageName) { 2203 MediaSessionImplBase.this.setVolumeTo(value, flags); 2204 } 2205 2206 @Override 2207 public void prepare() throws RemoteException { 2208 postToHandler(MessageHandler.MSG_PREPARE); 2209 } 2210 2211 @Override 2212 public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException { 2213 postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras); 2214 } 2215 2216 @Override 2217 public void prepareFromSearch(String query, Bundle extras) throws RemoteException { 2218 postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras); 2219 } 2220 2221 @Override 2222 public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException { 2223 postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras); 2224 } 2225 2226 @Override 2227 public void play() throws RemoteException { 2228 postToHandler(MessageHandler.MSG_PLAY); 2229 } 2230 2231 @Override 2232 public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { 2233 postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); 2234 } 2235 2236 @Override 2237 public void playFromSearch(String query, Bundle extras) throws RemoteException { 2238 postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras); 2239 } 2240 2241 @Override 2242 public void playFromUri(Uri uri, Bundle extras) throws RemoteException { 2243 postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras); 2244 } 2245 2246 @Override 2247 public void skipToQueueItem(long id) { 2248 postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id); 2249 } 2250 2251 @Override 2252 public void pause() throws RemoteException { 2253 postToHandler(MessageHandler.MSG_PAUSE); 2254 } 2255 2256 @Override 2257 public void stop() throws RemoteException { 2258 postToHandler(MessageHandler.MSG_STOP); 2259 } 2260 2261 @Override 2262 public void next() throws RemoteException { 2263 postToHandler(MessageHandler.MSG_NEXT); 2264 } 2265 2266 @Override 2267 public void previous() throws RemoteException { 2268 postToHandler(MessageHandler.MSG_PREVIOUS); 2269 } 2270 2271 @Override 2272 public void fastForward() throws RemoteException { 2273 postToHandler(MessageHandler.MSG_FAST_FORWARD); 2274 } 2275 2276 @Override 2277 public void rewind() throws RemoteException { 2278 postToHandler(MessageHandler.MSG_REWIND); 2279 } 2280 2281 @Override 2282 public void seekTo(long pos) throws RemoteException { 2283 postToHandler(MessageHandler.MSG_SEEK_TO, pos); 2284 } 2285 2286 @Override 2287 public void rate(RatingCompat rating) throws RemoteException { 2288 postToHandler(MessageHandler.MSG_RATE, rating); 2289 } 2290 2291 @Override 2292 public void setRepeatMode(int repeatMode) throws RemoteException { 2293 postToHandler(MessageHandler.MSG_SET_REPEAT_MODE, repeatMode); 2294 } 2295 2296 @Override 2297 public void setShuffleModeEnabled(boolean enabled) throws RemoteException { 2298 postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE_ENABLED, enabled); 2299 } 2300 2301 @Override 2302 public void sendCustomAction(String action, Bundle args) 2303 throws RemoteException { 2304 postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args); 2305 } 2306 2307 @Override 2308 public MediaMetadataCompat getMetadata() { 2309 return mMetadata; 2310 } 2311 2312 @Override 2313 public PlaybackStateCompat getPlaybackState() { 2314 return getStateWithUpdatedPosition(); 2315 } 2316 2317 @Override 2318 public List<QueueItem> getQueue() { 2319 synchronized (mLock) { 2320 return mQueue; 2321 } 2322 } 2323 2324 @Override 2325 public void addQueueItem(MediaDescriptionCompat description) { 2326 postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM, description); 2327 } 2328 2329 @Override 2330 public void addQueueItemAt(MediaDescriptionCompat description, int index) { 2331 postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM_AT, description, index); 2332 } 2333 2334 @Override 2335 public void removeQueueItem(MediaDescriptionCompat description) { 2336 postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM, description); 2337 } 2338 2339 @Override 2340 public void removeQueueItemAt(int index) { 2341 postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM_AT, index); 2342 } 2343 2344 @Override 2345 public CharSequence getQueueTitle() { 2346 return mQueueTitle; 2347 } 2348 2349 @Override 2350 public Bundle getExtras() { 2351 synchronized (mLock) { 2352 return mExtras; 2353 } 2354 } 2355 2356 @Override 2357 @RatingCompat.Style 2358 public int getRatingType() { 2359 return mRatingType; 2360 } 2361 2362 @Override 2363 @PlaybackStateCompat.RepeatMode 2364 public int getRepeatMode() { 2365 return mRepeatMode; 2366 } 2367 2368 @Override 2369 public boolean isShuffleModeEnabled() { 2370 return mShuffleModeEnabled; 2371 } 2372 2373 @Override 2374 public boolean isTransportControlEnabled() { 2375 return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0; 2376 } 2377 } 2378 2379 private static final class Command { 2380 public final String command; 2381 public final Bundle extras; 2382 public final ResultReceiver stub; 2383 2384 public Command(String command, Bundle extras, ResultReceiver stub) { 2385 this.command = command; 2386 this.extras = extras; 2387 this.stub = stub; 2388 } 2389 } 2390 2391 private class MessageHandler extends Handler { 2392 2393 private static final int MSG_COMMAND = 1; 2394 private static final int MSG_ADJUST_VOLUME = 2; 2395 private static final int MSG_PREPARE = 3; 2396 private static final int MSG_PREPARE_MEDIA_ID = 4; 2397 private static final int MSG_PREPARE_SEARCH = 5; 2398 private static final int MSG_PREPARE_URI = 6; 2399 private static final int MSG_PLAY = 7; 2400 private static final int MSG_PLAY_MEDIA_ID = 8; 2401 private static final int MSG_PLAY_SEARCH = 9; 2402 private static final int MSG_PLAY_URI = 10; 2403 private static final int MSG_SKIP_TO_ITEM = 11; 2404 private static final int MSG_PAUSE = 12; 2405 private static final int MSG_STOP = 13; 2406 private static final int MSG_NEXT = 14; 2407 private static final int MSG_PREVIOUS = 15; 2408 private static final int MSG_FAST_FORWARD = 16; 2409 private static final int MSG_REWIND = 17; 2410 private static final int MSG_SEEK_TO = 18; 2411 private static final int MSG_RATE = 19; 2412 private static final int MSG_CUSTOM_ACTION = 20; 2413 private static final int MSG_MEDIA_BUTTON = 21; 2414 private static final int MSG_SET_VOLUME = 22; 2415 private static final int MSG_SET_REPEAT_MODE = 23; 2416 private static final int MSG_SET_SHUFFLE_MODE_ENABLED = 24; 2417 private static final int MSG_ADD_QUEUE_ITEM = 25; 2418 private static final int MSG_ADD_QUEUE_ITEM_AT = 26; 2419 private static final int MSG_REMOVE_QUEUE_ITEM = 27; 2420 private static final int MSG_REMOVE_QUEUE_ITEM_AT = 28; 2421 2422 // KeyEvent constants only available on API 11+ 2423 private static final int KEYCODE_MEDIA_PAUSE = 127; 2424 private static final int KEYCODE_MEDIA_PLAY = 126; 2425 2426 public MessageHandler(Looper looper) { 2427 super(looper); 2428 } 2429 2430 public void post(int what, Object obj, Bundle bundle) { 2431 Message msg = obtainMessage(what, obj); 2432 msg.setData(bundle); 2433 msg.sendToTarget(); 2434 } 2435 2436 public void post(int what, Object obj) { 2437 obtainMessage(what, obj).sendToTarget(); 2438 } 2439 2440 public void post(int what) { 2441 post(what, null); 2442 } 2443 2444 public void post(int what, Object obj, int arg1) { 2445 obtainMessage(what, arg1, 0, obj).sendToTarget(); 2446 } 2447 2448 @Override 2449 public void handleMessage(Message msg) { 2450 MediaSessionCompat.Callback cb = mCallback; 2451 if (cb == null) { 2452 return; 2453 } 2454 switch (msg.what) { 2455 case MSG_COMMAND: 2456 Command cmd = (Command) msg.obj; 2457 cb.onCommand(cmd.command, cmd.extras, cmd.stub); 2458 break; 2459 case MSG_MEDIA_BUTTON: 2460 KeyEvent keyEvent = (KeyEvent) msg.obj; 2461 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); 2462 intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 2463 // Let the Callback handle events first before using the default behavior 2464 if (!cb.onMediaButtonEvent(intent)) { 2465 onMediaButtonEvent(keyEvent, cb); 2466 } 2467 break; 2468 case MSG_PREPARE: 2469 cb.onPrepare(); 2470 break; 2471 case MSG_PREPARE_MEDIA_ID: 2472 cb.onPrepareFromMediaId((String) msg.obj, msg.getData()); 2473 break; 2474 case MSG_PREPARE_SEARCH: 2475 cb.onPrepareFromSearch((String) msg.obj, msg.getData()); 2476 break; 2477 case MSG_PREPARE_URI: 2478 cb.onPrepareFromUri((Uri) msg.obj, msg.getData()); 2479 break; 2480 case MSG_PLAY: 2481 cb.onPlay(); 2482 break; 2483 case MSG_PLAY_MEDIA_ID: 2484 cb.onPlayFromMediaId((String) msg.obj, msg.getData()); 2485 break; 2486 case MSG_PLAY_SEARCH: 2487 cb.onPlayFromSearch((String) msg.obj, msg.getData()); 2488 break; 2489 case MSG_PLAY_URI: 2490 cb.onPlayFromUri((Uri) msg.obj, msg.getData()); 2491 break; 2492 case MSG_SKIP_TO_ITEM: 2493 cb.onSkipToQueueItem((Long) msg.obj); 2494 break; 2495 case MSG_PAUSE: 2496 cb.onPause(); 2497 break; 2498 case MSG_STOP: 2499 cb.onStop(); 2500 break; 2501 case MSG_NEXT: 2502 cb.onSkipToNext(); 2503 break; 2504 case MSG_PREVIOUS: 2505 cb.onSkipToPrevious(); 2506 break; 2507 case MSG_FAST_FORWARD: 2508 cb.onFastForward(); 2509 break; 2510 case MSG_REWIND: 2511 cb.onRewind(); 2512 break; 2513 case MSG_SEEK_TO: 2514 cb.onSeekTo((Long) msg.obj); 2515 break; 2516 case MSG_RATE: 2517 cb.onSetRating((RatingCompat) msg.obj); 2518 break; 2519 case MSG_CUSTOM_ACTION: 2520 cb.onCustomAction((String) msg.obj, msg.getData()); 2521 break; 2522 case MSG_ADD_QUEUE_ITEM: 2523 cb.onAddQueueItem((MediaDescriptionCompat) msg.obj); 2524 break; 2525 case MSG_ADD_QUEUE_ITEM_AT: 2526 cb.onAddQueueItem((MediaDescriptionCompat) msg.obj, msg.arg1); 2527 break; 2528 case MSG_REMOVE_QUEUE_ITEM: 2529 cb.onRemoveQueueItem((MediaDescriptionCompat) msg.obj); 2530 break; 2531 case MSG_REMOVE_QUEUE_ITEM_AT: 2532 cb.onRemoveQueueItemAt(msg.arg1); 2533 break; 2534 case MSG_ADJUST_VOLUME: 2535 adjustVolume(msg.arg1, 0); 2536 break; 2537 case MSG_SET_VOLUME: 2538 setVolumeTo(msg.arg1, 0); 2539 break; 2540 case MSG_SET_REPEAT_MODE: 2541 cb.onSetRepeatMode(msg.arg1); 2542 break; 2543 case MSG_SET_SHUFFLE_MODE_ENABLED: 2544 cb.onSetShuffleModeEnabled((boolean) msg.obj); 2545 break; 2546 } 2547 } 2548 2549 private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) { 2550 if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) { 2551 return; 2552 } 2553 long validActions = mState == null ? 0 : mState.getActions(); 2554 switch (ke.getKeyCode()) { 2555 // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+ 2556 case KEYCODE_MEDIA_PLAY: 2557 if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) { 2558 cb.onPlay(); 2559 } 2560 break; 2561 // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+ 2562 case KEYCODE_MEDIA_PAUSE: 2563 if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) { 2564 cb.onPause(); 2565 } 2566 break; 2567 case KeyEvent.KEYCODE_MEDIA_NEXT: 2568 if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) { 2569 cb.onSkipToNext(); 2570 } 2571 break; 2572 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 2573 if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) { 2574 cb.onSkipToPrevious(); 2575 } 2576 break; 2577 case KeyEvent.KEYCODE_MEDIA_STOP: 2578 if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) { 2579 cb.onStop(); 2580 } 2581 break; 2582 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 2583 if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) { 2584 cb.onFastForward(); 2585 } 2586 break; 2587 case KeyEvent.KEYCODE_MEDIA_REWIND: 2588 if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) { 2589 cb.onRewind(); 2590 } 2591 break; 2592 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 2593 case KeyEvent.KEYCODE_HEADSETHOOK: 2594 boolean isPlaying = mState != null 2595 && mState.getState() == PlaybackStateCompat.STATE_PLAYING; 2596 boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE 2597 | PlaybackStateCompat.ACTION_PLAY)) != 0; 2598 boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE 2599 | PlaybackStateCompat.ACTION_PAUSE)) != 0; 2600 if (isPlaying && canPause) { 2601 cb.onPause(); 2602 } else if (!isPlaying && canPlay) { 2603 cb.onPlay(); 2604 } 2605 break; 2606 } 2607 } 2608 } 2609 } 2610 2611 static class MediaSessionImplApi21 implements MediaSessionImpl { 2612 private final Object mSessionObj; 2613 private final Token mToken; 2614 2615 private boolean mDestroyed = false; 2616 private final RemoteCallbackList<IMediaControllerCallback> mExtraControllerCallbacks = 2617 new RemoteCallbackList<>(); 2618 2619 private PlaybackStateCompat mPlaybackState; 2620 @RatingCompat.Style int mRatingType; 2621 @PlaybackStateCompat.RepeatMode int mRepeatMode; 2622 boolean mShuffleModeEnabled; 2623 2624 public MediaSessionImplApi21(Context context, String tag) { 2625 mSessionObj = MediaSessionCompatApi21.createSession(context, tag); 2626 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj), 2627 new ExtraSession()); 2628 } 2629 2630 public MediaSessionImplApi21(Object mediaSession) { 2631 mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession); 2632 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj), 2633 new ExtraSession()); 2634 } 2635 2636 @Override 2637 public void setCallback(Callback callback, Handler handler) { 2638 MediaSessionCompatApi21.setCallback(mSessionObj, 2639 callback == null ? null : callback.mCallbackObj, handler); 2640 if (callback != null) { 2641 callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this); 2642 } 2643 } 2644 2645 @Override 2646 public void setFlags(@SessionFlags int flags) { 2647 MediaSessionCompatApi21.setFlags(mSessionObj, flags); 2648 } 2649 2650 @Override 2651 public void setPlaybackToLocal(int stream) { 2652 MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream); 2653 } 2654 2655 @Override 2656 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 2657 MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj, 2658 volumeProvider.getVolumeProvider()); 2659 } 2660 2661 @Override 2662 public void setActive(boolean active) { 2663 MediaSessionCompatApi21.setActive(mSessionObj, active); 2664 } 2665 2666 @Override 2667 public boolean isActive() { 2668 return MediaSessionCompatApi21.isActive(mSessionObj); 2669 } 2670 2671 @Override 2672 public void sendSessionEvent(String event, Bundle extras) { 2673 if (android.os.Build.VERSION.SDK_INT < 23) { 2674 int size = mExtraControllerCallbacks.beginBroadcast(); 2675 for (int i = size - 1; i >= 0; i--) { 2676 IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i); 2677 try { 2678 cb.onEvent(event, extras); 2679 } catch (RemoteException e) { 2680 } 2681 } 2682 mExtraControllerCallbacks.finishBroadcast(); 2683 } 2684 MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras); 2685 } 2686 2687 @Override 2688 public void release() { 2689 mDestroyed = true; 2690 MediaSessionCompatApi21.release(mSessionObj); 2691 } 2692 2693 @Override 2694 public Token getSessionToken() { 2695 return mToken; 2696 } 2697 2698 @Override 2699 public void setPlaybackState(PlaybackStateCompat state) { 2700 mPlaybackState = state; 2701 int size = mExtraControllerCallbacks.beginBroadcast(); 2702 for (int i = size - 1; i >= 0; i--) { 2703 IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i); 2704 try { 2705 cb.onPlaybackStateChanged(state); 2706 } catch (RemoteException e) { 2707 } 2708 } 2709 mExtraControllerCallbacks.finishBroadcast(); 2710 MediaSessionCompatApi21.setPlaybackState(mSessionObj, 2711 state == null ? null : state.getPlaybackState()); 2712 } 2713 2714 @Override 2715 public void setMetadata(MediaMetadataCompat metadata) { 2716 MediaSessionCompatApi21.setMetadata(mSessionObj, 2717 metadata == null ? null : metadata.getMediaMetadata()); 2718 } 2719 2720 @Override 2721 public void setSessionActivity(PendingIntent pi) { 2722 MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi); 2723 } 2724 2725 @Override 2726 public void setMediaButtonReceiver(PendingIntent mbr) { 2727 MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr); 2728 } 2729 2730 @Override 2731 public void setQueue(List<QueueItem> queue) { 2732 List<Object> queueObjs = null; 2733 if (queue != null) { 2734 queueObjs = new ArrayList<>(); 2735 for (QueueItem item : queue) { 2736 queueObjs.add(item.getQueueItem()); 2737 } 2738 } 2739 MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs); 2740 } 2741 2742 @Override 2743 public void setQueueTitle(CharSequence title) { 2744 MediaSessionCompatApi21.setQueueTitle(mSessionObj, title); 2745 } 2746 2747 @Override 2748 public void setRatingType(@RatingCompat.Style int type) { 2749 if (android.os.Build.VERSION.SDK_INT < 22) { 2750 mRatingType = type; 2751 } else { 2752 MediaSessionCompatApi22.setRatingType(mSessionObj, type); 2753 } 2754 } 2755 2756 @Override 2757 public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 2758 if (mRepeatMode != repeatMode) { 2759 mRepeatMode = repeatMode; 2760 int size = mExtraControllerCallbacks.beginBroadcast(); 2761 for (int i = size - 1; i >= 0; i--) { 2762 IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i); 2763 try { 2764 cb.onRepeatModeChanged(repeatMode); 2765 } catch (RemoteException e) { 2766 } 2767 } 2768 mExtraControllerCallbacks.finishBroadcast(); 2769 } 2770 } 2771 2772 @Override 2773 public void setShuffleModeEnabled(boolean enabled) { 2774 if (mShuffleModeEnabled != enabled) { 2775 mShuffleModeEnabled = enabled; 2776 int size = mExtraControllerCallbacks.beginBroadcast(); 2777 for (int i = size - 1; i >= 0; i--) { 2778 IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i); 2779 try { 2780 cb.onShuffleModeChanged(enabled); 2781 } catch (RemoteException e) { 2782 } 2783 } 2784 mExtraControllerCallbacks.finishBroadcast(); 2785 } 2786 } 2787 2788 @Override 2789 public void setExtras(Bundle extras) { 2790 MediaSessionCompatApi21.setExtras(mSessionObj, extras); 2791 } 2792 2793 @Override 2794 public Object getMediaSession() { 2795 return mSessionObj; 2796 } 2797 2798 @Override 2799 public Object getRemoteControlClient() { 2800 return null; 2801 } 2802 2803 @Override 2804 public String getCallingPackage() { 2805 if (android.os.Build.VERSION.SDK_INT < 24) { 2806 return null; 2807 } else { 2808 return MediaSessionCompatApi24.getCallingPackage(mSessionObj); 2809 } 2810 } 2811 2812 class ExtraSession extends IMediaSession.Stub { 2813 @Override 2814 public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) { 2815 // Will not be called. 2816 throw new AssertionError(); 2817 } 2818 2819 @Override 2820 public boolean sendMediaButton(KeyEvent mediaButton) { 2821 // Will not be called. 2822 throw new AssertionError(); 2823 } 2824 2825 @Override 2826 public void registerCallbackListener(IMediaControllerCallback cb) { 2827 if (!mDestroyed) { 2828 mExtraControllerCallbacks.register(cb); 2829 } 2830 } 2831 2832 @Override 2833 public void unregisterCallbackListener(IMediaControllerCallback cb) { 2834 mExtraControllerCallbacks.unregister(cb); 2835 } 2836 2837 @Override 2838 public String getPackageName() { 2839 // Will not be called. 2840 throw new AssertionError(); 2841 } 2842 2843 @Override 2844 public String getTag() { 2845 // Will not be called. 2846 throw new AssertionError(); 2847 } 2848 2849 @Override 2850 public PendingIntent getLaunchPendingIntent() { 2851 // Will not be called. 2852 throw new AssertionError(); 2853 } 2854 2855 @Override 2856 @SessionFlags 2857 public long getFlags() { 2858 // Will not be called. 2859 throw new AssertionError(); 2860 } 2861 2862 @Override 2863 public ParcelableVolumeInfo getVolumeAttributes() { 2864 // Will not be called. 2865 throw new AssertionError(); 2866 } 2867 2868 @Override 2869 public void adjustVolume(int direction, int flags, String packageName) { 2870 // Will not be called. 2871 throw new AssertionError(); 2872 } 2873 2874 @Override 2875 public void setVolumeTo(int value, int flags, String packageName) { 2876 // Will not be called. 2877 throw new AssertionError(); 2878 } 2879 2880 @Override 2881 public void prepare() throws RemoteException { 2882 // Will not be called. 2883 throw new AssertionError(); 2884 } 2885 2886 @Override 2887 public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException { 2888 // Will not be called. 2889 throw new AssertionError(); 2890 } 2891 2892 @Override 2893 public void prepareFromSearch(String query, Bundle extras) throws RemoteException { 2894 // Will not be called. 2895 throw new AssertionError(); 2896 } 2897 2898 @Override 2899 public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException { 2900 // Will not be called. 2901 throw new AssertionError(); 2902 } 2903 2904 @Override 2905 public void play() throws RemoteException { 2906 // Will not be called. 2907 throw new AssertionError(); 2908 } 2909 2910 @Override 2911 public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { 2912 // Will not be called. 2913 throw new AssertionError(); 2914 } 2915 2916 @Override 2917 public void playFromSearch(String query, Bundle extras) throws RemoteException { 2918 // Will not be called. 2919 throw new AssertionError(); 2920 } 2921 2922 @Override 2923 public void playFromUri(Uri uri, Bundle extras) throws RemoteException { 2924 // Will not be called. 2925 throw new AssertionError(); 2926 } 2927 2928 @Override 2929 public void skipToQueueItem(long id) { 2930 // Will not be called. 2931 throw new AssertionError(); 2932 } 2933 2934 @Override 2935 public void pause() throws RemoteException { 2936 // Will not be called. 2937 throw new AssertionError(); 2938 } 2939 2940 @Override 2941 public void stop() throws RemoteException { 2942 // Will not be called. 2943 throw new AssertionError(); 2944 } 2945 2946 @Override 2947 public void next() throws RemoteException { 2948 // Will not be called. 2949 throw new AssertionError(); 2950 } 2951 2952 @Override 2953 public void previous() throws RemoteException { 2954 // Will not be called. 2955 throw new AssertionError(); 2956 } 2957 2958 @Override 2959 public void fastForward() throws RemoteException { 2960 // Will not be called. 2961 throw new AssertionError(); 2962 } 2963 2964 @Override 2965 public void rewind() throws RemoteException { 2966 // Will not be called. 2967 throw new AssertionError(); 2968 } 2969 2970 @Override 2971 public void seekTo(long pos) throws RemoteException { 2972 // Will not be called. 2973 throw new AssertionError(); 2974 } 2975 2976 @Override 2977 public void rate(RatingCompat rating) throws RemoteException { 2978 // Will not be called. 2979 throw new AssertionError(); 2980 } 2981 2982 @Override 2983 public void setRepeatMode(int repeatMode) throws RemoteException { 2984 // Will not be called. 2985 throw new AssertionError(); 2986 } 2987 2988 @Override 2989 public void setShuffleModeEnabled(boolean enabled) throws RemoteException { 2990 // Will not be called. 2991 throw new AssertionError(); 2992 } 2993 2994 @Override 2995 public void sendCustomAction(String action, Bundle args) throws RemoteException { 2996 // Will not be called. 2997 throw new AssertionError(); 2998 } 2999 3000 @Override 3001 public MediaMetadataCompat getMetadata() { 3002 // Will not be called. 3003 throw new AssertionError(); 3004 } 3005 3006 @Override 3007 public PlaybackStateCompat getPlaybackState() { 3008 return mPlaybackState; 3009 } 3010 3011 @Override 3012 public List<QueueItem> getQueue() { 3013 // Will not be called. 3014 return null; 3015 } 3016 3017 @Override 3018 public void addQueueItem(MediaDescriptionCompat descriptionCompat) { 3019 // Will not be called. 3020 throw new AssertionError(); 3021 } 3022 3023 @Override 3024 public void addQueueItemAt(MediaDescriptionCompat descriptionCompat, int index) { 3025 // Will not be called. 3026 throw new AssertionError(); 3027 } 3028 3029 @Override 3030 public void removeQueueItem(MediaDescriptionCompat description) { 3031 // Will not be called. 3032 throw new AssertionError(); 3033 } 3034 3035 @Override 3036 public void removeQueueItemAt(int index) { 3037 // Will not be called. 3038 throw new AssertionError(); 3039 } 3040 3041 @Override 3042 public CharSequence getQueueTitle() { 3043 // Will not be called. 3044 throw new AssertionError(); 3045 } 3046 3047 @Override 3048 public Bundle getExtras() { 3049 // Will not be called. 3050 throw new AssertionError(); 3051 } 3052 3053 @Override 3054 @RatingCompat.Style 3055 public int getRatingType() { 3056 return mRatingType; 3057 } 3058 3059 @Override 3060 @PlaybackStateCompat.RepeatMode 3061 public int getRepeatMode() { 3062 return mRepeatMode; 3063 } 3064 3065 @Override 3066 public boolean isShuffleModeEnabled() { 3067 return mShuffleModeEnabled; 3068 } 3069 3070 @Override 3071 public boolean isTransportControlEnabled() { 3072 // Will not be called. 3073 throw new AssertionError(); 3074 } 3075 } 3076 } 3077} 3078