MediaSessionCompat.java revision 24fa6c0dd42df057729e1a258388183f94da7f82
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.content.Context; 21import android.content.Intent; 22import android.media.AudioManager; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.os.ResultReceiver; 28import android.support.v4.media.MediaMetadataCompat; 29import android.support.v4.media.RatingCompat; 30import android.support.v4.media.VolumeProviderCompat; 31import android.text.TextUtils; 32 33/** 34 * Allows interaction with media controllers, volume keys, media buttons, and 35 * transport controls. 36 * <p> 37 * A MediaSession should be created when an app wants to publish media playback 38 * information or handle media keys. In general an app only needs one session 39 * for all playback, though multiple sessions can be created to provide finer 40 * grain controls of media. 41 * <p> 42 * Once a session is created the owner of the session may pass its 43 * {@link #getSessionToken() session token} to other processes to allow them to 44 * create a {@link MediaControllerCompat} to interact with the session. 45 * <p> 46 * To receive commands, media keys, and other events a {@link Callback} must be 47 * set with {@link #addCallback(Callback)}. To receive transport control 48 * commands a {@link TransportControlsCallback} must be set with 49 * {@link #addTransportControlsCallback}. 50 * <p> 51 * When an app is finished performing playback it must call {@link #release()} 52 * to clean up the session and notify any controllers. 53 * <p> 54 * MediaSession objects are thread safe. 55 * <p> 56 * This is a helper for accessing features in {@link android.media.session.MediaSession} 57 * introduced after API level 4 in a backwards compatible fashion. 58 */ 59public class MediaSessionCompat { 60 private final MediaSessionImpl mImpl; 61 62 /** 63 * Set this flag on the session to indicate that it can handle media button 64 * events. 65 */ 66 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 67 68 /** 69 * Set this flag on the session to indicate that it handles transport 70 * control commands through a {@link TransportControlsCallback}. 71 * The callback can be retrieved by calling {@link #addTransportControlsCallback}. 72 */ 73 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 74 75 /** 76 * The session uses local playback. 77 */ 78 public static final int VOLUME_TYPE_LOCAL = 1; 79 80 /** 81 * The session uses remote playback. 82 */ 83 public static final int VOLUME_TYPE_REMOTE = 2; 84 85 /** 86 * Creates a new session. 87 * 88 * @param context The context. 89 * @param tag A short name for debugging purposes. 90 */ 91 public MediaSessionCompat(Context context, String tag) { 92 if (context == null) { 93 throw new IllegalArgumentException("context must not be null"); 94 } 95 if (TextUtils.isEmpty(tag)) { 96 throw new IllegalArgumentException("tag must not be null or empty"); 97 } 98 99 if (android.os.Build.VERSION.SDK_INT >= 21) { 100 mImpl = new MediaSessionImplApi21(context, tag); 101 } else { 102 mImpl = new MediaSessionImplBase(); 103 } 104 } 105 106 /** 107 * Add a callback to receive updates on for the MediaSession. This includes 108 * media button and volume events. The caller's thread will be used to post 109 * events. 110 * 111 * @param callback The callback object 112 */ 113 public void addCallback(Callback callback) { 114 addCallback(callback, null); 115 } 116 117 /** 118 * Add a callback to receive updates for the MediaSession. This includes 119 * media button and volume events. 120 * 121 * @param callback The callback to receive updates on. 122 * @param handler The handler that events should be posted on. 123 */ 124 public void addCallback(Callback callback, Handler handler) { 125 if (callback == null) { 126 throw new IllegalArgumentException("callback must not be null"); 127 } 128 mImpl.addCallback(callback, handler != null ? handler : new Handler()); 129 } 130 131 /** 132 * Remove a callback. It will no longer receive updates. 133 * 134 * @param callback The callback to remove. 135 */ 136 public void removeCallback(Callback callback) { 137 if (callback == null) { 138 throw new IllegalArgumentException("callback must not be null"); 139 } 140 mImpl.removeCallback(callback); 141 } 142 143 /** 144 * Set any flags for the session. 145 * 146 * @param flags The flags to set for this session. 147 */ 148 public void setFlags(int flags) { 149 mImpl.setFlags(flags); 150 } 151 152 /** 153 * Set the stream this session is playing on. This will affect the system's 154 * volume handling for this session. If {@link #setPlaybackToRemote} was 155 * previously called it will stop receiving volume commands and the system 156 * will begin sending volume changes to the appropriate stream. 157 * <p> 158 * By default sessions are on {@link AudioManager#STREAM_MUSIC}. 159 * 160 * @param stream The {@link AudioManager} stream this session is playing on. 161 */ 162 public void setPlaybackToLocal(int stream) { 163 mImpl.setPlaybackToLocal(stream); 164 } 165 166 /** 167 * Configure this session to use remote volume handling. This must be called 168 * to receive volume button events, otherwise the system will adjust the 169 * current stream volume for this session. If {@link #setPlaybackToLocal} 170 * was previously called that stream will stop receiving volume changes for 171 * this session. 172 * 173 * @param volumeProvider The provider that will handle volume changes. May 174 * not be null. 175 */ 176 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 177 if (volumeProvider == null) { 178 throw new IllegalArgumentException("volumeProvider may not be null!"); 179 } 180 mImpl.setPlaybackToRemote(volumeProvider); 181 } 182 183 /** 184 * Set if this session is currently active and ready to receive commands. If 185 * set to false your session's controller may not be discoverable. You must 186 * set the session to active before it can start receiving media button 187 * events or transport commands. 188 * 189 * @param active Whether this session is active or not. 190 */ 191 public void setActive(boolean active) { 192 mImpl.setActive(active); 193 } 194 195 /** 196 * Get the current active state of this session. 197 * 198 * @return True if the session is active, false otherwise. 199 */ 200 public boolean isActive() { 201 return mImpl.isActive(); 202 } 203 204 /** 205 * Send a proprietary event to all MediaControllers listening to this 206 * Session. It's up to the Controller/Session owner to determine the meaning 207 * of any events. 208 * 209 * @param event The name of the event to send 210 * @param extras Any extras included with the event 211 */ 212 public void sendSessionEvent(String event, Bundle extras) { 213 if (TextUtils.isEmpty(event)) { 214 throw new IllegalArgumentException("event cannot be null or empty"); 215 } 216 mImpl.sendSessionEvent(event, extras); 217 } 218 219 /** 220 * This must be called when an app has finished performing playback. If 221 * playback is expected to start again shortly the session can be left open, 222 * but it must be released if your activity or service is being destroyed. 223 */ 224 public void release() { 225 mImpl.release(); 226 } 227 228 /** 229 * Retrieve a token object that can be used by apps to create a 230 * {@link MediaControllerCompat} for interacting with this session. The owner of 231 * the session is responsible for deciding how to distribute these tokens. 232 * 233 * @return A token that can be used to create a MediaController for this 234 * session. 235 */ 236 public Token getSessionToken() { 237 return mImpl.getSessionToken(); 238 } 239 240 /** 241 * Add a callback to receive transport controls on, such as play, rewind, or 242 * fast forward. 243 * 244 * @param callback The callback object. 245 */ 246 public void addTransportControlsCallback(TransportControlsCallback callback) { 247 addTransportControlsCallback(callback, null); 248 } 249 250 /** 251 * Add a callback to receive transport controls on, such as play, rewind, or 252 * fast forward. The updates will be posted to the specified handler. If no 253 * handler is provided they will be posted to the caller's thread. 254 * 255 * @param callback The callback to receive updates on. 256 * @param handler The handler to post the updates on. 257 */ 258 public void addTransportControlsCallback(TransportControlsCallback callback, 259 Handler handler) { 260 if (callback == null) { 261 throw new IllegalArgumentException("Callback cannot be null"); 262 } 263 mImpl.addTransportControlsCallback(callback, handler != null ? handler : new Handler()); 264 } 265 266 /** 267 * Stop receiving transport controls on the specified callback. If an update 268 * has already been posted you may still receive it after this call returns. 269 * 270 * @param callback The callback to stop receiving updates on. 271 */ 272 public void removeTransportControlsCallback(TransportControlsCallback callback) { 273 if (callback == null) { 274 throw new IllegalArgumentException("Callback cannot be null"); 275 } 276 mImpl.removeTransportControlsCallback(callback); 277 } 278 279 /** 280 * Update the current playback state. 281 * 282 * @param state The current state of playback 283 */ 284 public void setPlaybackState(PlaybackStateCompat state) { 285 mImpl.setPlaybackState(state); 286 } 287 288 /** 289 * Update the current metadata. New metadata can be created using 290 * {@link android.media.MediaMetadata.Builder}. 291 * 292 * @param metadata The new metadata 293 */ 294 public void setMetadata(MediaMetadataCompat metadata) { 295 mImpl.setMetadata(metadata); 296 } 297 298 /** 299 * Gets the underlying framework {@link android.media.session.MediaSession} object. 300 * <p> 301 * This method is only supported on API 21+. 302 * </p> 303 * 304 * @return The underlying {@link android.media.session.MediaSession} object, 305 * or null if none. 306 */ 307 public Object getMediaSession() { 308 return mImpl.getMediaSession(); 309 } 310 311 /** 312 * Receives generic commands or updates from controllers and the system. 313 * Callbacks may be registered using {@link #addCallback}. 314 */ 315 public abstract static class Callback { 316 final Object mCallbackObj; 317 318 public Callback() { 319 if (android.os.Build.VERSION.SDK_INT >= 21) { 320 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21()); 321 } else { 322 mCallbackObj = null; 323 } 324 } 325 326 /** 327 * Called when a media button is pressed and this session has the 328 * highest priority or a controller sends a media button event to the 329 * session. TODO determine if using Intents identical to the ones 330 * RemoteControlClient receives is useful 331 * <p> 332 * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a 333 * KeyEvent in {@link Intent#EXTRA_KEY_EVENT} 334 * 335 * @param mediaButtonIntent an intent containing the KeyEvent as an 336 * extra 337 */ 338 public void onMediaButtonEvent(Intent mediaButtonIntent) { 339 } 340 341 /** 342 * Called when a controller has sent a custom command to this session. 343 * The owner of the session may handle custom commands but is not 344 * required to. 345 * 346 * @param command The command name. 347 * @param extras Optional parameters for the command, may be null. 348 * @param cb A result receiver to which a result may be sent by the command, may be null. 349 */ 350 public void onControlCommand(String command, Bundle extras, ResultReceiver cb) { 351 } 352 353 private class StubApi21 implements MediaSessionCompatApi21.Callback { 354 @Override 355 public void onMediaButtonEvent(Intent mediaButtonIntent) { 356 Callback.this.onMediaButtonEvent(mediaButtonIntent); 357 } 358 359 @Override 360 public void onControlCommand( 361 String command, Bundle extras, ResultReceiver cb) { 362 Callback.this.onControlCommand(command, extras, cb); 363 } 364 } 365 } 366 367 /** 368 * Receives transport control commands. Callbacks may be registered using 369 * {@link #addTransportControlsCallback}. 370 */ 371 public static abstract class TransportControlsCallback { 372 final Object mCallbackObj; 373 374 public TransportControlsCallback() { 375 if (android.os.Build.VERSION.SDK_INT >= 21) { 376 mCallbackObj = MediaSessionCompatApi21.createTransportControlsCallback( 377 new StubApi21()); 378 } else { 379 mCallbackObj = null; 380 } 381 } 382 383 /** 384 * Override to handle requests to begin playback. 385 */ 386 public void onPlay() { 387 } 388 389 /** 390 * Override to handle requests to pause playback. 391 */ 392 public void onPause() { 393 } 394 395 /** 396 * Override to handle requests to skip to the next media item. 397 */ 398 public void onSkipToNext() { 399 } 400 401 /** 402 * Override to handle requests to skip to the previous media item. 403 */ 404 public void onSkipToPrevious() { 405 } 406 407 /** 408 * Override to handle requests to fast forward. 409 */ 410 public void onFastForward() { 411 } 412 413 /** 414 * Override to handle requests to rewind. 415 */ 416 public void onRewind() { 417 } 418 419 /** 420 * Override to handle requests to stop playback. 421 */ 422 public void onStop() { 423 } 424 425 /** 426 * Override to handle requests to seek to a specific position in ms. 427 * 428 * @param pos New position to move to, in milliseconds. 429 */ 430 public void onSeekTo(long pos) { 431 } 432 433 /** 434 * Override to handle the item being rated. 435 * 436 * @param rating 437 */ 438 public void onSetRating(RatingCompat rating) { 439 } 440 441 private class StubApi21 implements MediaSessionCompatApi21.TransportControlsCallback { 442 @Override 443 public void onPlay() { 444 TransportControlsCallback.this.onPlay(); 445 } 446 447 @Override 448 public void onPause() { 449 TransportControlsCallback.this.onPause(); 450 } 451 452 @Override 453 public void onSkipToNext() { 454 TransportControlsCallback.this.onSkipToNext(); 455 } 456 457 @Override 458 public void onSkipToPrevious() { 459 TransportControlsCallback.this.onSkipToPrevious(); 460 } 461 462 @Override 463 public void onFastForward() { 464 TransportControlsCallback.this.onFastForward(); 465 } 466 467 @Override 468 public void onRewind() { 469 TransportControlsCallback.this.onRewind(); 470 } 471 472 @Override 473 public void onStop() { 474 TransportControlsCallback.this.onStop(); 475 } 476 477 @Override 478 public void onSeekTo(long pos) { 479 TransportControlsCallback.this.onSeekTo(pos); 480 } 481 482 @Override 483 public void onSetRating(Object ratingObj) { 484 TransportControlsCallback.this.onSetRating(RatingCompat.fromRating(ratingObj)); 485 } 486 } 487 } 488 489 /** 490 * Represents an ongoing session. This may be passed to apps by the session 491 * owner to allow them to create a {@link MediaControllerCompat} to communicate with 492 * the session. 493 */ 494 public static final class Token implements Parcelable { 495 private final Parcelable mInner; 496 497 Token(Parcelable inner) { 498 mInner = inner; 499 } 500 501 @Override 502 public int describeContents() { 503 return mInner.describeContents(); 504 } 505 506 @Override 507 public void writeToParcel(Parcel dest, int flags) { 508 dest.writeParcelable(mInner, flags); 509 } 510 511 /** 512 * Gets the underlying framework {@link android.media.session.MediaSessionToken} object. 513 * <p> 514 * This method is only supported on API 21+. 515 * </p> 516 * 517 * @return The underlying {@link android.media.session.MediaSessionToken} object, 518 * or null if none. 519 */ 520 public Object getToken() { 521 return mInner; 522 } 523 524 public static final Parcelable.Creator<Token> CREATOR 525 = new Parcelable.Creator<Token>() { 526 @Override 527 public Token createFromParcel(Parcel in) { 528 return new Token(in.readParcelable(null)); 529 } 530 531 @Override 532 public Token[] newArray(int size) { 533 return new Token[size]; 534 } 535 }; 536 } 537 538 interface MediaSessionImpl { 539 void addCallback(Callback callback, Handler handler); 540 void removeCallback(Callback callback); 541 void setFlags(int flags); 542 void setPlaybackToLocal(int stream); 543 void setPlaybackToRemote(VolumeProviderCompat volumeProvider); 544 void setActive(boolean active); 545 boolean isActive(); 546 void sendSessionEvent(String event, Bundle extras); 547 void release(); 548 Token getSessionToken(); 549 void addTransportControlsCallback(TransportControlsCallback callback, Handler handler); 550 void removeTransportControlsCallback(TransportControlsCallback callback); 551 void setPlaybackState(PlaybackStateCompat state); 552 void setMetadata(MediaMetadataCompat metadata); 553 Object getMediaSession(); 554 } 555 556 // TODO: compatibility implementation 557 static class MediaSessionImplBase implements MediaSessionImpl { 558 @Override 559 public void addCallback(Callback callback, Handler handler) { 560 } 561 562 @Override 563 public void removeCallback(Callback callback) { 564 } 565 566 @Override 567 public void setFlags(int flags) { 568 } 569 570 @Override 571 public void setPlaybackToLocal(int stream) { 572 } 573 574 @Override 575 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 576 } 577 578 @Override 579 public void setActive(boolean active) { 580 } 581 582 @Override 583 public boolean isActive() { 584 return false; 585 } 586 587 @Override 588 public void sendSessionEvent(String event, Bundle extras) { 589 } 590 591 @Override 592 public void release() { 593 } 594 595 @Override 596 public Token getSessionToken() { 597 return null; 598 } 599 600 @Override 601 public void addTransportControlsCallback(TransportControlsCallback callback, 602 Handler handler) { 603 } 604 605 @Override 606 public void removeTransportControlsCallback(TransportControlsCallback callback) { 607 } 608 609 @Override 610 public void setPlaybackState(PlaybackStateCompat state) { 611 } 612 613 @Override 614 public void setMetadata(MediaMetadataCompat metadata) { 615 } 616 617 @Override 618 public Object getMediaSession() { 619 return null; 620 } 621 } 622 623 static class MediaSessionImplApi21 implements MediaSessionImpl { 624 private final Object mSessionObj; 625 private final Token mToken; 626 627 public MediaSessionImplApi21(Context context, String tag) { 628 mSessionObj = MediaSessionCompatApi21.createSession(context, tag); 629 mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj)); 630 } 631 632 @Override 633 public void addCallback(Callback callback, Handler handler) { 634 MediaSessionCompatApi21.addCallback(mSessionObj, callback.mCallbackObj, handler); 635 } 636 637 @Override 638 public void removeCallback(Callback callback) { 639 MediaSessionCompatApi21.removeCallback(mSessionObj, callback.mCallbackObj); 640 } 641 642 @Override 643 public void setFlags(int flags) { 644 MediaSessionCompatApi21.setFlags(mSessionObj, flags); 645 } 646 647 @Override 648 public void setPlaybackToLocal(int stream) { 649 MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream); 650 } 651 652 @Override 653 public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) { 654 MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj, 655 volumeProvider.getVolumeProvider()); 656 } 657 658 @Override 659 public void setActive(boolean active) { 660 MediaSessionCompatApi21.setActive(mSessionObj, active); 661 } 662 663 @Override 664 public boolean isActive() { 665 return MediaSessionCompatApi21.isActive(mSessionObj); 666 } 667 668 @Override 669 public void sendSessionEvent(String event, Bundle extras) { 670 MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras); 671 } 672 673 @Override 674 public void release() { 675 MediaSessionCompatApi21.release(mSessionObj); 676 } 677 678 @Override 679 public Token getSessionToken() { 680 return mToken; 681 } 682 683 @Override 684 public void addTransportControlsCallback(TransportControlsCallback callback, 685 Handler handler) { 686 MediaSessionCompatApi21.addTransportControlsCallback( 687 mSessionObj, callback.mCallbackObj, handler); 688 } 689 690 @Override 691 public void removeTransportControlsCallback(TransportControlsCallback callback) { 692 MediaSessionCompatApi21.removeTransportControlsCallback( 693 mSessionObj, callback.mCallbackObj); 694 } 695 696 @Override 697 public void setPlaybackState(PlaybackStateCompat state) { 698 MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState()); 699 } 700 701 @Override 702 public void setMetadata(MediaMetadataCompat metadata) { 703 MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata()); 704 } 705 706 @Override 707 public Object getMediaSession() { 708 return mSessionObj; 709 } 710 } 711} 712