MediaControllerCompat.java revision 1435afe32073dee10e721dfb6122ce6a194a6412
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 sendControlCommand(String command, Bundle params, ResultReceiver cb) { 202 if (TextUtils.isEmpty(command)) { 203 throw new IllegalArgumentException("command cannot be null or empty"); 204 } 205 mImpl.sendControlCommand(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 custom events sent by the session owner without a 238 * specified interface. Controllers should only handle these for 239 * sessions they own. 240 * 241 * @param event The event from the session. 242 * @param extras Optional parameters for the event. 243 */ 244 public void onSessionEvent(String event, Bundle extras) { 245 } 246 247 /** 248 * Override to handle changes in playback state. 249 * 250 * @param state The new playback state of the session 251 */ 252 public void onPlaybackStateChanged(PlaybackStateCompat state) { 253 } 254 255 /** 256 * Override to handle changes to the current metadata. 257 * 258 * @param metadata The current metadata for the session or null if none. 259 * @see MediaMetadata 260 */ 261 public void onMetadataChanged(MediaMetadataCompat metadata) { 262 } 263 264 private class StubApi21 implements MediaControllerCompatApi21.Callback { 265 @Override 266 public void onSessionEvent(String event, Bundle extras) { 267 Callback.this.onSessionEvent(event, extras); 268 } 269 270 @Override 271 public void onPlaybackStateChanged(Object stateObj) { 272 Callback.this.onPlaybackStateChanged( 273 PlaybackStateCompat.fromPlaybackState(stateObj)); 274 } 275 276 @Override 277 public void onMetadataChanged(Object metadataObj) { 278 Callback.this.onMetadataChanged( 279 MediaMetadataCompat.fromMediaMetadata(metadataObj)); 280 } 281 } 282 } 283 284 /** 285 * Interface for controlling media playback on a session. This allows an app 286 * to send media transport commands to the session. 287 */ 288 public static abstract class TransportControls { 289 TransportControls() { 290 } 291 292 /** 293 * Request that the player start its playback at its current position. 294 */ 295 public abstract void play(); 296 297 /** 298 * Request that the player pause its playback and stay at its current 299 * position. 300 */ 301 public abstract void pause(); 302 303 /** 304 * Request that the player stop its playback; it may clear its state in 305 * whatever way is appropriate. 306 */ 307 public abstract void stop(); 308 309 /** 310 * Move to a new location in the media stream. 311 * 312 * @param pos Position to move to, in milliseconds. 313 */ 314 public abstract void seekTo(long pos); 315 316 /** 317 * Start fast forwarding. If playback is already fast forwarding this 318 * may increase the rate. 319 */ 320 public abstract void fastForward(); 321 322 /** 323 * Skip to the next item. 324 */ 325 public abstract void skipToNext(); 326 327 /** 328 * Start rewinding. If playback is already rewinding this may increase 329 * the rate. 330 */ 331 public abstract void rewind(); 332 333 /** 334 * Skip to the previous item. 335 */ 336 public abstract void skipToPrevious(); 337 338 /** 339 * Rate the current content. This will cause the rating to be set for 340 * the current user. The Rating type must match the type returned by 341 * {@link #getRatingType()}. 342 * 343 * @param rating The rating to set for the current content 344 */ 345 public abstract void setRating(RatingCompat rating); 346 } 347 348 /** 349 * Holds information about the way volume is handled for this session. 350 */ 351 public static final class VolumeInfo { 352 private final int mVolumeType; 353 // TODO update audio stream with AudioAttributes support version 354 private final int mAudioStream; 355 private final int mVolumeControl; 356 private final int mMaxVolume; 357 private final int mCurrentVolume; 358 359 VolumeInfo(int type, int stream, int control, int max, int current) { 360 mVolumeType = type; 361 mAudioStream = stream; 362 mVolumeControl = control; 363 mMaxVolume = max; 364 mCurrentVolume = current; 365 } 366 367 /** 368 * Get the type of volume handling, either local or remote. One of: 369 * <ul> 370 * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li> 371 * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li> 372 * </ul> 373 * 374 * @return The type of volume handling this session is using. 375 */ 376 public int getVolumeType() { 377 return mVolumeType; 378 } 379 380 /** 381 * Get the stream this is currently controlling volume on. When the volume 382 * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not 383 * have meaning and should be ignored. 384 * 385 * @return The stream this session is playing on. 386 */ 387 public int getAudioStream() { 388 return mAudioStream; 389 } 390 391 /** 392 * Get the type of volume control that can be used. One of: 393 * <ul> 394 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 395 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 396 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 397 * </ul> 398 * 399 * @return The type of volume control that may be used with this 400 * session. 401 */ 402 public int getVolumeControl() { 403 return mVolumeControl; 404 } 405 406 /** 407 * Get the maximum volume that may be set for this session. 408 * 409 * @return The maximum allowed volume where this session is playing. 410 */ 411 public int getMaxVolume() { 412 return mMaxVolume; 413 } 414 415 /** 416 * Get the current volume for this session. 417 * 418 * @return The current volume where this session is playing. 419 */ 420 public int getCurrentVolume() { 421 return mCurrentVolume; 422 } 423 } 424 425 interface MediaControllerImpl { 426 void addCallback(Callback callback, Handler handler); 427 void removeCallback(Callback callback); 428 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 429 TransportControls getTransportControls(); 430 PlaybackStateCompat getPlaybackState(); 431 MediaMetadataCompat getMetadata(); 432 int getRatingType(); 433 VolumeInfo getVolumeInfo(); 434 void sendControlCommand(String command, Bundle params, ResultReceiver cb); 435 Object getMediaController(); 436 } 437 438 // TODO: compatibility implementation 439 static class MediaControllerImplBase implements MediaControllerImpl { 440 @Override 441 public void addCallback(Callback callback, Handler handler) { 442 } 443 444 @Override 445 public void removeCallback(Callback callback) { 446 } 447 448 @Override 449 public boolean dispatchMediaButtonEvent(KeyEvent event) { 450 return false; 451 } 452 453 @Override 454 public TransportControls getTransportControls() { 455 return null; 456 } 457 458 @Override 459 public PlaybackStateCompat getPlaybackState() { 460 return null; 461 } 462 463 @Override 464 public MediaMetadataCompat getMetadata() { 465 return null; 466 } 467 468 @Override 469 public int getRatingType() { 470 return 0; 471 } 472 473 @Override 474 public VolumeInfo getVolumeInfo() { 475 return null; 476 } 477 478 @Override 479 public void sendControlCommand(String command, Bundle params, ResultReceiver cb) { 480 } 481 482 @Override 483 public Object getMediaController() { 484 return null; 485 } 486 } 487 488 static class MediaControllerImplApi21 implements MediaControllerImpl { 489 private final Object mControllerObj; 490 491 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 492 mControllerObj = MediaControllerCompatApi21.fromToken( 493 session.getSessionToken().getToken()); 494 } 495 496 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 497 throws RemoteException { 498 // TODO: refactor framework implementation 499 mControllerObj = MediaControllerCompatApi21.fromToken( 500 sessionToken.getToken()); 501 if (mControllerObj == null) throw new RemoteException(); 502 } 503 504 @Override 505 public void addCallback(Callback callback, Handler handler) { 506 MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler); 507 } 508 509 @Override 510 public void removeCallback(Callback callback) { 511 MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj); 512 } 513 514 @Override 515 public boolean dispatchMediaButtonEvent(KeyEvent event) { 516 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 517 } 518 519 @Override 520 public TransportControls getTransportControls() { 521 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 522 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 523 } 524 525 @Override 526 public PlaybackStateCompat getPlaybackState() { 527 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 528 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 529 } 530 531 @Override 532 public MediaMetadataCompat getMetadata() { 533 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 534 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 535 } 536 537 @Override 538 public int getRatingType() { 539 return MediaControllerCompatApi21.getRatingType(mControllerObj); 540 } 541 542 @Override 543 public VolumeInfo getVolumeInfo() { 544 Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj); 545 return volumeInfoObj != null ? new VolumeInfo( 546 MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj), 547 MediaControllerCompatApi21.VolumeInfo.getLegacyAudioStream(volumeInfoObj), 548 MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj), 549 MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj), 550 MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null; 551 } 552 553 @Override 554 public void sendControlCommand(String command, Bundle params, ResultReceiver cb) { 555 MediaControllerCompatApi21.sendControlCommand(mControllerObj, 556 command, params, cb); 557 } 558 559 @Override 560 public Object getMediaController() { 561 return mControllerObj; 562 } 563 } 564 565 static class TransportControlsApi21 extends TransportControls { 566 private final Object mControlsObj; 567 568 public TransportControlsApi21(Object controlsObj) { 569 mControlsObj = controlsObj; 570 } 571 572 @Override 573 public void play() { 574 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 575 } 576 577 @Override 578 public void pause() { 579 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 580 } 581 582 @Override 583 public void stop() { 584 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 585 } 586 587 @Override 588 public void seekTo(long pos) { 589 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 590 } 591 592 @Override 593 public void fastForward() { 594 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 595 } 596 597 @Override 598 public void rewind() { 599 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 600 } 601 602 @Override 603 public void skipToNext() { 604 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 605 } 606 607 @Override 608 public void skipToPrevious() { 609 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 610 } 611 612 @Override 613 public void setRating(RatingCompat rating) { 614 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 615 rating != null ? rating.getRating() : null); 616 } 617 } 618} 619