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