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