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