MediaControllerCompat.java revision 24fa6c0dd42df057729e1a258388183f94da7f82
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 private final int mAudioStream; 354 private final int mVolumeControl; 355 private final int mMaxVolume; 356 private final int mCurrentVolume; 357 358 VolumeInfo(int type, int stream, int control, int max, int current) { 359 mVolumeType = type; 360 mAudioStream = stream; 361 mVolumeControl = control; 362 mMaxVolume = max; 363 mCurrentVolume = current; 364 } 365 366 /** 367 * Get the type of volume handling, either local or remote. One of: 368 * <ul> 369 * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li> 370 * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li> 371 * </ul> 372 * 373 * @return The type of volume handling this session is using. 374 */ 375 public int getVolumeType() { 376 return mVolumeType; 377 } 378 379 /** 380 * Get the stream this is currently controlling volume on. When the volume 381 * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not 382 * have meaning and should be ignored. 383 * 384 * @return The stream this session is playing on. 385 */ 386 public int getAudioStream() { 387 return mAudioStream; 388 } 389 390 /** 391 * Get the type of volume control that can be used. One of: 392 * <ul> 393 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 394 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 395 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 396 * </ul> 397 * 398 * @return The type of volume control that may be used with this 399 * session. 400 */ 401 public int getVolumeControl() { 402 return mVolumeControl; 403 } 404 405 /** 406 * Get the maximum volume that may be set for this session. 407 * 408 * @return The maximum allowed volume where this session is playing. 409 */ 410 public int getMaxVolume() { 411 return mMaxVolume; 412 } 413 414 /** 415 * Get the current volume for this session. 416 * 417 * @return The current volume where this session is playing. 418 */ 419 public int getCurrentVolume() { 420 return mCurrentVolume; 421 } 422 } 423 424 interface MediaControllerImpl { 425 void addCallback(Callback callback, Handler handler); 426 void removeCallback(Callback callback); 427 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 428 TransportControls getTransportControls(); 429 PlaybackStateCompat getPlaybackState(); 430 MediaMetadataCompat getMetadata(); 431 int getRatingType(); 432 VolumeInfo getVolumeInfo(); 433 void sendControlCommand(String command, Bundle params, ResultReceiver cb); 434 Object getMediaController(); 435 } 436 437 // TODO: compatibility implementation 438 static class MediaControllerImplBase implements MediaControllerImpl { 439 @Override 440 public void addCallback(Callback callback, Handler handler) { 441 } 442 443 @Override 444 public void removeCallback(Callback callback) { 445 } 446 447 @Override 448 public boolean dispatchMediaButtonEvent(KeyEvent event) { 449 return false; 450 } 451 452 @Override 453 public TransportControls getTransportControls() { 454 return null; 455 } 456 457 @Override 458 public PlaybackStateCompat getPlaybackState() { 459 return null; 460 } 461 462 @Override 463 public MediaMetadataCompat getMetadata() { 464 return null; 465 } 466 467 @Override 468 public int getRatingType() { 469 return 0; 470 } 471 472 @Override 473 public VolumeInfo getVolumeInfo() { 474 return null; 475 } 476 477 @Override 478 public void sendControlCommand(String command, Bundle params, ResultReceiver cb) { 479 } 480 481 @Override 482 public Object getMediaController() { 483 return null; 484 } 485 } 486 487 static class MediaControllerImplApi21 implements MediaControllerImpl { 488 private final Object mControllerObj; 489 490 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 491 mControllerObj = MediaControllerCompatApi21.fromToken( 492 session.getSessionToken().getToken()); 493 } 494 495 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 496 throws RemoteException { 497 // TODO: refactor framework implementation 498 mControllerObj = MediaControllerCompatApi21.fromToken( 499 sessionToken.getToken()); 500 if (mControllerObj == null) throw new RemoteException(); 501 } 502 503 @Override 504 public void addCallback(Callback callback, Handler handler) { 505 MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler); 506 } 507 508 @Override 509 public void removeCallback(Callback callback) { 510 MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj); 511 } 512 513 @Override 514 public boolean dispatchMediaButtonEvent(KeyEvent event) { 515 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 516 } 517 518 @Override 519 public TransportControls getTransportControls() { 520 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 521 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 522 } 523 524 @Override 525 public PlaybackStateCompat getPlaybackState() { 526 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 527 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 528 } 529 530 @Override 531 public MediaMetadataCompat getMetadata() { 532 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 533 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 534 } 535 536 @Override 537 public int getRatingType() { 538 return MediaControllerCompatApi21.getRatingType(mControllerObj); 539 } 540 541 @Override 542 public VolumeInfo getVolumeInfo() { 543 Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj); 544 return volumeInfoObj != null ? new VolumeInfo( 545 MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj), 546 MediaControllerCompatApi21.VolumeInfo.getAudioStream(volumeInfoObj), 547 MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj), 548 MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj), 549 MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null; 550 } 551 552 @Override 553 public void sendControlCommand(String command, Bundle params, ResultReceiver cb) { 554 MediaControllerCompatApi21.sendControlCommand(mControllerObj, 555 command, params, cb); 556 } 557 558 @Override 559 public Object getMediaController() { 560 return mControllerObj; 561 } 562 } 563 564 static class TransportControlsApi21 extends TransportControls { 565 private final Object mControlsObj; 566 567 public TransportControlsApi21(Object controlsObj) { 568 mControlsObj = controlsObj; 569 } 570 571 @Override 572 public void play() { 573 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 574 } 575 576 @Override 577 public void pause() { 578 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 579 } 580 581 @Override 582 public void stop() { 583 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 584 } 585 586 @Override 587 public void seekTo(long pos) { 588 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 589 } 590 591 @Override 592 public void fastForward() { 593 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 594 } 595 596 @Override 597 public void rewind() { 598 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 599 } 600 601 @Override 602 public void skipToNext() { 603 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 604 } 605 606 @Override 607 public void skipToPrevious() { 608 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 609 } 610 611 @Override 612 public void setRating(RatingCompat rating) { 613 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 614 rating != null ? rating.getRating() : null); 615 } 616 } 617} 618