MediaControllerCompat.java revision 23138c4b9be07abdab0cfdde2c62186359c9e7fa
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v4.media.session; 18 19import android.content.Context; 20import android.os.Bundle; 21import android.os.Handler; 22import android.os.RemoteException; 23import android.os.ResultReceiver; 24import android.support.v4.media.MediaMetadataCompat; 25import android.support.v4.media.RatingCompat; 26import android.support.v4.media.VolumeProviderCompat; 27import android.text.TextUtils; 28import android.view.KeyEvent; 29 30/** 31 * Allows an app to interact with an ongoing media session. Media buttons and 32 * other commands can be sent to the session. A callback may be registered to 33 * receive updates from the session, such as metadata and play state changes. 34 * <p> 35 * A MediaController can be created if you have a {@link MediaSessionCompat.Token} 36 * from the session owner. 37 * <p> 38 * MediaController objects are thread-safe. 39 * <p> 40 * This is a helper for accessing features in {@link android.media.session.MediaSession} 41 * introduced after API level 4 in a backwards compatible fashion. 42 */ 43public final class MediaControllerCompat { 44 private final MediaControllerImpl mImpl; 45 46 /** 47 * Creates a media controller from a session. 48 * 49 * @param session The session to be controlled. 50 */ 51 public MediaControllerCompat(Context context, MediaSessionCompat session) { 52 if (session == null) { 53 throw new IllegalArgumentException("session must not be null"); 54 } 55 56 if (android.os.Build.VERSION.SDK_INT >= 21) { 57 mImpl = new MediaControllerImplApi21(context, session); 58 } else { 59 mImpl = new MediaControllerImplBase(); 60 } 61 } 62 63 /** 64 * Creates a media controller from a session token which may have 65 * been obtained from another process. 66 * 67 * @param sessionToken The token of the session to be controlled. 68 * @throws RemoteException if the session is not accessible. 69 */ 70 public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) 71 throws RemoteException { 72 if (sessionToken == null) { 73 throw new IllegalArgumentException("sessionToken must not be null"); 74 } 75 76 if (android.os.Build.VERSION.SDK_INT >= 21) { 77 mImpl = new MediaControllerImplApi21(context, sessionToken); 78 } else { 79 mImpl = new MediaControllerImplBase(); 80 } 81 } 82 83 /** 84 * Get a {@link TransportControls} instance for this session. 85 * 86 * @return A controls instance 87 */ 88 public TransportControls getTransportControls() { 89 return mImpl.getTransportControls(); 90 } 91 92 /** 93 * Send the specified media button event to the session. Only media keys can 94 * be sent by this method, other keys will be ignored. 95 * 96 * @param keyEvent The media button event to dispatch. 97 * @return true if the event was sent to the session, false otherwise. 98 */ 99 public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) { 100 if (keyEvent == null) { 101 throw new IllegalArgumentException("KeyEvent may not be null"); 102 } 103 return mImpl.dispatchMediaButtonEvent(keyEvent); 104 } 105 106 /** 107 * Get the current playback state for this session. 108 * 109 * @return The current PlaybackState or null 110 */ 111 public PlaybackStateCompat getPlaybackState() { 112 return mImpl.getPlaybackState(); 113 } 114 115 /** 116 * Get the current metadata for this session. 117 * 118 * @return The current MediaMetadata or null. 119 */ 120 public MediaMetadataCompat getMetadata() { 121 return mImpl.getMetadata(); 122 } 123 124 /** 125 * Get the rating type supported by the session. One of: 126 * <ul> 127 * <li>{@link RatingCompat#RATING_NONE}</li> 128 * <li>{@link RatingCompat#RATING_HEART}</li> 129 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 130 * <li>{@link RatingCompat#RATING_3_STARS}</li> 131 * <li>{@link RatingCompat#RATING_4_STARS}</li> 132 * <li>{@link RatingCompat#RATING_5_STARS}</li> 133 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 134 * </ul> 135 * 136 * @return The supported rating type 137 */ 138 public int getRatingType() { 139 return mImpl.getRatingType(); 140 } 141 142 /** 143 * Get the current volume info for this session. 144 * 145 * @return The current volume info or null. 146 */ 147 public VolumeInfo getVolumeInfo() { 148 return mImpl.getVolumeInfo(); 149 } 150 151 /** 152 * Adds a callback to receive updates from the Session. Updates will be 153 * posted on the caller's thread. 154 * 155 * @param callback The callback object, must not be null. 156 */ 157 public void addCallback(Callback callback) { 158 addCallback(callback, null); 159 } 160 161 /** 162 * Adds a callback to receive updates from the session. Updates will be 163 * posted on the specified handler's thread. 164 * 165 * @param callback The callback object, must not be null. 166 * @param handler The handler to post updates on. If null the callers thread 167 * will be used. 168 */ 169 public void addCallback(Callback callback, Handler handler) { 170 if (callback == null) { 171 throw new IllegalArgumentException("callback cannot be null"); 172 } 173 if (handler == null) { 174 handler = new Handler(); 175 } 176 mImpl.addCallback(callback, handler); 177 } 178 179 /** 180 * Stop receiving updates on the specified callback. If an update has 181 * already been posted you may still receive it after calling this method. 182 * 183 * @param callback The callback to remove 184 */ 185 public void removeCallback(Callback callback) { 186 if (callback == null) { 187 throw new IllegalArgumentException("callback cannot be null"); 188 } 189 mImpl.removeCallback(callback); 190 } 191 192 /** 193 * Sends a generic command to the session. It is up to the session creator 194 * to decide what commands and parameters they will support. As such, 195 * commands should only be sent to sessions that the controller owns. 196 * 197 * @param command The command to send 198 * @param params Any parameters to include with the command 199 * @param cb The callback to receive the result on 200 */ 201 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 202 if (TextUtils.isEmpty(command)) { 203 throw new IllegalArgumentException("command cannot be null or empty"); 204 } 205 mImpl.sendCommand(command, params, cb); 206 } 207 208 /** 209 * Gets the underlying framework {@link android.media.session.MediaController} object. 210 * <p> 211 * This method is only supported on API 21+. 212 * </p> 213 * 214 * @return The underlying {@link android.media.session.MediaController} object, 215 * or null if none. 216 */ 217 public Object getMediaController() { 218 return mImpl.getMediaController(); 219 } 220 221 /** 222 * Callback for receiving updates on from the session. A Callback can be 223 * registered using {@link #addCallback} 224 */ 225 public static abstract class Callback { 226 final Object mCallbackObj; 227 228 public Callback() { 229 if (android.os.Build.VERSION.SDK_INT >= 21) { 230 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21()); 231 } else { 232 mCallbackObj = null; 233 } 234 } 235 236 /** 237 * Override to handle the session being destroyed. The session is no 238 * longer valid after this call and calls to it will be ignored. 239 */ 240 public void onSessionDestroyed() { 241 } 242 243 /** 244 * Override to handle custom events sent by the session owner without a 245 * specified interface. Controllers should only handle these for 246 * sessions they own. 247 * 248 * @param event The event from the session. 249 * @param extras Optional parameters for the event. 250 */ 251 public void onSessionEvent(String event, Bundle extras) { 252 } 253 254 /** 255 * Override to handle changes in playback state. 256 * 257 * @param state The new playback state of the session 258 */ 259 public void onPlaybackStateChanged(PlaybackStateCompat state) { 260 } 261 262 /** 263 * Override to handle changes to the current metadata. 264 * 265 * @param metadata The current metadata for the session or null if none. 266 * @see MediaMetadata 267 */ 268 public void onMetadataChanged(MediaMetadataCompat metadata) { 269 } 270 271 private class StubApi21 implements MediaControllerCompatApi21.Callback { 272 @Override 273 public void onSessionDestroyed() { 274 Callback.this.onSessionDestroyed(); 275 } 276 277 @Override 278 public void onSessionEvent(String event, Bundle extras) { 279 Callback.this.onSessionEvent(event, extras); 280 } 281 282 @Override 283 public void onPlaybackStateChanged(Object stateObj) { 284 Callback.this.onPlaybackStateChanged( 285 PlaybackStateCompat.fromPlaybackState(stateObj)); 286 } 287 288 @Override 289 public void onMetadataChanged(Object metadataObj) { 290 Callback.this.onMetadataChanged( 291 MediaMetadataCompat.fromMediaMetadata(metadataObj)); 292 } 293 } 294 } 295 296 /** 297 * Interface for controlling media playback on a session. This allows an app 298 * to send media transport commands to the session. 299 */ 300 public static abstract class TransportControls { 301 TransportControls() { 302 } 303 304 /** 305 * Request that the player start its playback at its current position. 306 */ 307 public abstract void play(); 308 309 /** 310 * Request that the player pause its playback and stay at its current 311 * position. 312 */ 313 public abstract void pause(); 314 315 /** 316 * Request that the player stop its playback; it may clear its state in 317 * whatever way is appropriate. 318 */ 319 public abstract void stop(); 320 321 /** 322 * Move to a new location in the media stream. 323 * 324 * @param pos Position to move to, in milliseconds. 325 */ 326 public abstract void seekTo(long pos); 327 328 /** 329 * Start fast forwarding. If playback is already fast forwarding this 330 * may increase the rate. 331 */ 332 public abstract void fastForward(); 333 334 /** 335 * Skip to the next item. 336 */ 337 public abstract void skipToNext(); 338 339 /** 340 * Start rewinding. If playback is already rewinding this may increase 341 * the rate. 342 */ 343 public abstract void rewind(); 344 345 /** 346 * Skip to the previous item. 347 */ 348 public abstract void skipToPrevious(); 349 350 /** 351 * Rate the current content. This will cause the rating to be set for 352 * the current user. The Rating type must match the type returned by 353 * {@link #getRatingType()}. 354 * 355 * @param rating The rating to set for the current content 356 */ 357 public abstract void setRating(RatingCompat rating); 358 } 359 360 /** 361 * Holds information about the way volume is handled for this session. 362 */ 363 public static final class VolumeInfo { 364 private final int mVolumeType; 365 // TODO update audio stream with AudioAttributes support version 366 private final int mAudioStream; 367 private final int mVolumeControl; 368 private final int mMaxVolume; 369 private final int mCurrentVolume; 370 371 VolumeInfo(int type, int stream, int control, int max, int current) { 372 mVolumeType = type; 373 mAudioStream = stream; 374 mVolumeControl = control; 375 mMaxVolume = max; 376 mCurrentVolume = current; 377 } 378 379 /** 380 * Get the type of volume handling, either local or remote. One of: 381 * <ul> 382 * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li> 383 * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li> 384 * </ul> 385 * 386 * @return The type of volume handling this session is using. 387 */ 388 public int getVolumeType() { 389 return mVolumeType; 390 } 391 392 /** 393 * Get the stream this is currently controlling volume on. When the volume 394 * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not 395 * have meaning and should be ignored. 396 * 397 * @return The stream this session is playing on. 398 */ 399 public int getAudioStream() { 400 return mAudioStream; 401 } 402 403 /** 404 * Get the type of volume control that can be used. One of: 405 * <ul> 406 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 407 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 408 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 409 * </ul> 410 * 411 * @return The type of volume control that may be used with this 412 * session. 413 */ 414 public int getVolumeControl() { 415 return mVolumeControl; 416 } 417 418 /** 419 * Get the maximum volume that may be set for this session. 420 * 421 * @return The maximum allowed volume where this session is playing. 422 */ 423 public int getMaxVolume() { 424 return mMaxVolume; 425 } 426 427 /** 428 * Get the current volume for this session. 429 * 430 * @return The current volume where this session is playing. 431 */ 432 public int getCurrentVolume() { 433 return mCurrentVolume; 434 } 435 } 436 437 interface MediaControllerImpl { 438 void addCallback(Callback callback, Handler handler); 439 void removeCallback(Callback callback); 440 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 441 TransportControls getTransportControls(); 442 PlaybackStateCompat getPlaybackState(); 443 MediaMetadataCompat getMetadata(); 444 int getRatingType(); 445 VolumeInfo getVolumeInfo(); 446 void sendCommand(String command, Bundle params, ResultReceiver cb); 447 Object getMediaController(); 448 } 449 450 // TODO: compatibility implementation 451 static class MediaControllerImplBase implements MediaControllerImpl { 452 @Override 453 public void addCallback(Callback callback, Handler handler) { 454 } 455 456 @Override 457 public void removeCallback(Callback callback) { 458 } 459 460 @Override 461 public boolean dispatchMediaButtonEvent(KeyEvent event) { 462 return false; 463 } 464 465 @Override 466 public TransportControls getTransportControls() { 467 return null; 468 } 469 470 @Override 471 public PlaybackStateCompat getPlaybackState() { 472 return null; 473 } 474 475 @Override 476 public MediaMetadataCompat getMetadata() { 477 return null; 478 } 479 480 @Override 481 public int getRatingType() { 482 return 0; 483 } 484 485 @Override 486 public VolumeInfo getVolumeInfo() { 487 return null; 488 } 489 490 @Override 491 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 492 } 493 494 @Override 495 public Object getMediaController() { 496 return null; 497 } 498 } 499 500 static class MediaControllerImplApi21 implements MediaControllerImpl { 501 private final Object mControllerObj; 502 503 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 504 mControllerObj = MediaControllerCompatApi21.fromToken(context, 505 session.getSessionToken().getToken()); 506 } 507 508 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 509 throws RemoteException { 510 // TODO: refactor framework implementation 511 mControllerObj = MediaControllerCompatApi21.fromToken(context, 512 sessionToken.getToken()); 513 if (mControllerObj == null) throw new RemoteException(); 514 } 515 516 @Override 517 public void addCallback(Callback callback, Handler handler) { 518 MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler); 519 } 520 521 @Override 522 public void removeCallback(Callback callback) { 523 MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj); 524 } 525 526 @Override 527 public boolean dispatchMediaButtonEvent(KeyEvent event) { 528 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 529 } 530 531 @Override 532 public TransportControls getTransportControls() { 533 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 534 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 535 } 536 537 @Override 538 public PlaybackStateCompat getPlaybackState() { 539 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 540 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 541 } 542 543 @Override 544 public MediaMetadataCompat getMetadata() { 545 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 546 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 547 } 548 549 @Override 550 public int getRatingType() { 551 return MediaControllerCompatApi21.getRatingType(mControllerObj); 552 } 553 554 @Override 555 public VolumeInfo getVolumeInfo() { 556 Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj); 557 return volumeInfoObj != null ? new VolumeInfo( 558 MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj), 559 MediaControllerCompatApi21.VolumeInfo.getLegacyAudioStream(volumeInfoObj), 560 MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj), 561 MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj), 562 MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null; 563 } 564 565 @Override 566 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 567 MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb); 568 } 569 570 @Override 571 public Object getMediaController() { 572 return mControllerObj; 573 } 574 } 575 576 static class TransportControlsApi21 extends TransportControls { 577 private final Object mControlsObj; 578 579 public TransportControlsApi21(Object controlsObj) { 580 mControlsObj = controlsObj; 581 } 582 583 @Override 584 public void play() { 585 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 586 } 587 588 @Override 589 public void pause() { 590 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 591 } 592 593 @Override 594 public void stop() { 595 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 596 } 597 598 @Override 599 public void seekTo(long pos) { 600 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 601 } 602 603 @Override 604 public void fastForward() { 605 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 606 } 607 608 @Override 609 public void rewind() { 610 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 611 } 612 613 @Override 614 public void skipToNext() { 615 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 616 } 617 618 @Override 619 public void skipToPrevious() { 620 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 621 } 622 623 @Override 624 public void setRating(RatingCompat rating) { 625 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 626 rating != null ? rating.getRating() : null); 627 } 628 } 629} 630