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