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