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