RemoteControlClient.java revision b2e93efcac593f5f27722219b274bd0fa5c1b0fe
1/* 2 * Copyright (C) 2011 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.media; 18 19import android.app.PendingIntent; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.graphics.Bitmap; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.RectF; 27import android.media.session.MediaSessionLegacyHelper; 28import android.media.session.PlaybackState; 29import android.media.session.MediaSession; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.IBinder; 33import android.os.Looper; 34import android.os.Message; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.os.SystemClock; 38import android.util.Log; 39 40import java.lang.IllegalArgumentException; 41import java.util.ArrayList; 42import java.util.Iterator; 43 44/** 45 * RemoteControlClient enables exposing information meant to be consumed by remote controls 46 * capable of displaying metadata, artwork and media transport control buttons. 47 * 48 * <p>A remote control client object is associated with a media button event receiver. This 49 * event receiver must have been previously registered with 50 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the 51 * RemoteControlClient can be registered through 52 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 53 * 54 * <p>Here is an example of creating a RemoteControlClient instance after registering a media 55 * button event receiver: 56 * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName()); 57 * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 58 * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver); 59 * // build the PendingIntent for the remote control client 60 * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 61 * mediaButtonIntent.setComponent(myEventReceiver); 62 * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); 63 * // create and register the remote control client 64 * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent); 65 * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre> 66 */ 67public class RemoteControlClient 68{ 69 private final static String TAG = "RemoteControlClient"; 70 private final static boolean DEBUG = false; 71 72 /** 73 * Playback state of a RemoteControlClient which is stopped. 74 * 75 * @see #setPlaybackState(int) 76 */ 77 public final static int PLAYSTATE_STOPPED = 1; 78 /** 79 * Playback state of a RemoteControlClient which is paused. 80 * 81 * @see #setPlaybackState(int) 82 */ 83 public final static int PLAYSTATE_PAUSED = 2; 84 /** 85 * Playback state of a RemoteControlClient which is playing media. 86 * 87 * @see #setPlaybackState(int) 88 */ 89 public final static int PLAYSTATE_PLAYING = 3; 90 /** 91 * Playback state of a RemoteControlClient which is fast forwarding in the media 92 * it is currently playing. 93 * 94 * @see #setPlaybackState(int) 95 */ 96 public final static int PLAYSTATE_FAST_FORWARDING = 4; 97 /** 98 * Playback state of a RemoteControlClient which is fast rewinding in the media 99 * it is currently playing. 100 * 101 * @see #setPlaybackState(int) 102 */ 103 public final static int PLAYSTATE_REWINDING = 5; 104 /** 105 * Playback state of a RemoteControlClient which is skipping to the next 106 * logical chapter (such as a song in a playlist) in the media it is currently playing. 107 * 108 * @see #setPlaybackState(int) 109 */ 110 public final static int PLAYSTATE_SKIPPING_FORWARDS = 6; 111 /** 112 * Playback state of a RemoteControlClient which is skipping back to the previous 113 * logical chapter (such as a song in a playlist) in the media it is currently playing. 114 * 115 * @see #setPlaybackState(int) 116 */ 117 public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7; 118 /** 119 * Playback state of a RemoteControlClient which is buffering data to play before it can 120 * start or resume playback. 121 * 122 * @see #setPlaybackState(int) 123 */ 124 public final static int PLAYSTATE_BUFFERING = 8; 125 /** 126 * Playback state of a RemoteControlClient which cannot perform any playback related 127 * operation because of an internal error. Examples of such situations are no network 128 * connectivity when attempting to stream data from a server, or expired user credentials 129 * when trying to play subscription-based content. 130 * 131 * @see #setPlaybackState(int) 132 */ 133 public final static int PLAYSTATE_ERROR = 9; 134 /** 135 * @hide 136 * The value of a playback state when none has been declared. 137 * Intentionally hidden as an application shouldn't set such a playback state value. 138 */ 139 public final static int PLAYSTATE_NONE = 0; 140 141 /** 142 * @hide 143 * The default playback type, "local", indicating the presentation of the media is happening on 144 * the same device (e.g. a phone, a tablet) as where it is controlled from. 145 */ 146 public final static int PLAYBACK_TYPE_LOCAL = 0; 147 /** 148 * @hide 149 * A playback type indicating the presentation of the media is happening on 150 * a different device (i.e. the remote device) than where it is controlled from. 151 */ 152 public final static int PLAYBACK_TYPE_REMOTE = 1; 153 private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL; 154 private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE; 155 /** 156 * @hide 157 * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled 158 * from this object. An example of fixed playback volume is a remote player, playing over HDMI 159 * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the 160 * source. 161 * @see #PLAYBACKINFO_VOLUME_HANDLING. 162 */ 163 public final static int PLAYBACK_VOLUME_FIXED = 0; 164 /** 165 * @hide 166 * Playback information indicating the playback volume is variable and can be controlled from 167 * this object. 168 * @see #PLAYBACKINFO_VOLUME_HANDLING. 169 */ 170 public final static int PLAYBACK_VOLUME_VARIABLE = 1; 171 /** 172 * @hide (to be un-hidden) 173 * The playback information value indicating the value of a given information type is invalid. 174 * @see #PLAYBACKINFO_VOLUME_HANDLING. 175 */ 176 public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; 177 178 /** 179 * @hide 180 * An unknown or invalid playback position value. 181 */ 182 public final static long PLAYBACK_POSITION_INVALID = -1; 183 /** 184 * @hide 185 * An invalid playback position value associated with the use of {@link #setPlaybackState(int)} 186 * used to indicate that playback position will remain unknown. 187 */ 188 public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L; 189 /** 190 * @hide 191 * The default playback speed, 1x. 192 */ 193 public final static float PLAYBACK_SPEED_1X = 1.0f; 194 195 //========================================== 196 // Public keys for playback information 197 /** 198 * @hide 199 * Playback information that defines the type of playback associated with this 200 * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}. 201 */ 202 public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1; 203 /** 204 * @hide 205 * Playback information that defines at what volume the playback associated with this 206 * RemoteControlClient is performed. This information is only used when the playback type is not 207 * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 208 */ 209 public final static int PLAYBACKINFO_VOLUME = 2; 210 /** 211 * @hide 212 * Playback information that defines the maximum volume volume value that is supported 213 * by the playback associated with this RemoteControlClient. This information is only used 214 * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 215 */ 216 public final static int PLAYBACKINFO_VOLUME_MAX = 3; 217 /** 218 * @hide 219 * Playback information that defines how volume is handled for the presentation of the media. 220 * @see #PLAYBACK_VOLUME_FIXED 221 * @see #PLAYBACK_VOLUME_VARIABLE 222 */ 223 public final static int PLAYBACKINFO_VOLUME_HANDLING = 4; 224 /** 225 * @hide 226 * Playback information that defines over what stream type the media is presented. 227 */ 228 public final static int PLAYBACKINFO_USES_STREAM = 5; 229 230 //========================================== 231 // Public flags for the supported transport control capabilities 232 /** 233 * Flag indicating a RemoteControlClient makes use of the "previous" media key. 234 * 235 * @see #setTransportControlFlags(int) 236 * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS 237 */ 238 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 239 /** 240 * Flag indicating a RemoteControlClient makes use of the "rewind" media key. 241 * 242 * @see #setTransportControlFlags(int) 243 * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND 244 */ 245 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 246 /** 247 * Flag indicating a RemoteControlClient makes use of the "play" media key. 248 * 249 * @see #setTransportControlFlags(int) 250 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY 251 */ 252 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 253 /** 254 * Flag indicating a RemoteControlClient makes use of the "play/pause" media key. 255 * 256 * @see #setTransportControlFlags(int) 257 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE 258 */ 259 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 260 /** 261 * Flag indicating a RemoteControlClient makes use of the "pause" media key. 262 * 263 * @see #setTransportControlFlags(int) 264 * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE 265 */ 266 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 267 /** 268 * Flag indicating a RemoteControlClient makes use of the "stop" media key. 269 * 270 * @see #setTransportControlFlags(int) 271 * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP 272 */ 273 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 274 /** 275 * Flag indicating a RemoteControlClient makes use of the "fast forward" media key. 276 * 277 * @see #setTransportControlFlags(int) 278 * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD 279 */ 280 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 281 /** 282 * Flag indicating a RemoteControlClient makes use of the "next" media key. 283 * 284 * @see #setTransportControlFlags(int) 285 * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT 286 */ 287 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 288 /** 289 * Flag indicating a RemoteControlClient can receive changes in the media playback position 290 * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set 291 * in order for components that display the RemoteControlClient information, to display and 292 * let the user control media playback position. 293 * @see #setTransportControlFlags(int) 294 * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener) 295 * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) 296 */ 297 public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; 298 /** 299 * Flag indicating a RemoteControlClient supports ratings. 300 * This flag must be set in order for components that display the RemoteControlClient 301 * information, to display ratings information, and, if ratings are declared editable 302 * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the 303 * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate 304 * the media, with values being received through the interface set with 305 * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}. 306 * @see #setTransportControlFlags(int) 307 */ 308 public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; 309 310 /** 311 * @hide 312 * The flags for when no media keys are declared supported. 313 * Intentionally hidden as an application shouldn't set the transport control flags 314 * to this value. 315 */ 316 public final static int FLAGS_KEY_MEDIA_NONE = 0; 317 318 /** 319 * @hide 320 * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. 321 */ 322 public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0; 323 /** 324 * @hide 325 * Flag used to signal that the transport control buttons supported by the 326 * RemoteControlClient are requested. 327 * This can for instance happen when playback is at the end of a playlist, and the "next" 328 * operation is not supported anymore. 329 */ 330 public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1; 331 /** 332 * @hide 333 * Flag used to signal that the playback state of the RemoteControlClient is requested. 334 */ 335 public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2; 336 /** 337 * @hide 338 * Flag used to signal that the album art for the RemoteControlClient is requested. 339 */ 340 public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; 341 342 private MediaSession mSession; 343 344 /** 345 * Class constructor. 346 * @param mediaButtonIntent The intent that will be sent for the media button events sent 347 * by remote controls. 348 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 349 * action, and have a component that will handle the intent (set with 350 * {@link Intent#setComponent(ComponentName)}) registered with 351 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 352 * before this new RemoteControlClient can itself be registered with 353 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 354 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 355 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 356 */ 357 public RemoteControlClient(PendingIntent mediaButtonIntent) { 358 mRcMediaIntent = mediaButtonIntent; 359 360 Looper looper; 361 if ((looper = Looper.myLooper()) != null) { 362 mEventHandler = new EventHandler(this, looper); 363 } else if ((looper = Looper.getMainLooper()) != null) { 364 mEventHandler = new EventHandler(this, looper); 365 } else { 366 mEventHandler = null; 367 Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); 368 } 369 } 370 371 /** 372 * Class constructor for a remote control client whose internal event handling 373 * happens on a user-provided Looper. 374 * @param mediaButtonIntent The intent that will be sent for the media button events sent 375 * by remote controls. 376 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 377 * action, and have a component that will handle the intent (set with 378 * {@link Intent#setComponent(ComponentName)}) registered with 379 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 380 * before this new RemoteControlClient can itself be registered with 381 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 382 * @param looper The Looper running the event loop. 383 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 384 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 385 */ 386 public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { 387 mRcMediaIntent = mediaButtonIntent; 388 389 mEventHandler = new EventHandler(this, looper); 390 } 391 392 /** 393 * @hide 394 */ 395 public void registerWithSession(MediaSessionLegacyHelper helper) { 396 helper.addRccListener(mRcMediaIntent, mTransportListener); 397 mSession = helper.getSession(mRcMediaIntent); 398 } 399 400 /** 401 * @hide 402 */ 403 public void unregisterWithSession(MediaSessionLegacyHelper helper) { 404 helper.removeRccListener(mRcMediaIntent); 405 mSession = null; 406 } 407 408 /** 409 * Get a {@link MediaSession} associated with this RCC. It will only have a 410 * session while it is registered with 411 * {@link AudioManager#registerRemoteControlClient}. The session returned 412 * should not be modified directly by the application but may be used with 413 * other APIs that require a session. 414 * 415 * @return A media session object or null. 416 */ 417 public MediaSession getMediaSession() { 418 return mSession; 419 } 420 421 /** 422 * Class used to modify metadata in a {@link RemoteControlClient} object. 423 * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, 424 * on which you set the metadata for the RemoteControlClient instance. Once all the information 425 * has been set, use {@link #apply()} to make it the new metadata that should be displayed 426 * for the associated client. Once the metadata has been "applied", you cannot reuse this 427 * instance of the MetadataEditor. 428 */ 429 public class MetadataEditor extends MediaMetadataEditor { 430 431 // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance 432 private MetadataEditor() { } 433 /** 434 * @hide 435 */ 436 public Object clone() throws CloneNotSupportedException { 437 throw new CloneNotSupportedException(); 438 } 439 440 /** 441 * The metadata key for the content artwork / album art. 442 */ 443 public final static int BITMAP_KEY_ARTWORK = 100; 444 445 /** 446 * @hide 447 * TODO(jmtrivi) have lockscreen move to the new key name and remove 448 */ 449 public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; 450 451 /** 452 * Adds textual information to be displayed. 453 * Note that none of the information added after {@link #apply()} has been called, 454 * will be displayed. 455 * @param key The identifier of a the metadata field to set. Valid values are 456 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, 457 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, 458 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 459 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, 460 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, 461 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, 462 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, 463 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, 464 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, 465 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 466 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. 467 * @param value The text for the given key, or {@code null} to signify there is no valid 468 * information for the field. 469 * @return Returns a reference to the same MetadataEditor object, so you can chain put 470 * calls together. 471 */ 472 public synchronized MetadataEditor putString(int key, String value) 473 throws IllegalArgumentException { 474 super.putString(key, value); 475 if (mMetadataBuilder != null) { 476 // MediaMetadata supports all the same fields as MetadataEditor 477 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); 478 // But just in case, don't add things we don't understand 479 if (metadataKey != null) { 480 mMetadataBuilder.putString(metadataKey, value); 481 } 482 } 483 484 return this; 485 } 486 487 /** 488 * Adds numerical information to be displayed. 489 * Note that none of the information added after {@link #apply()} has been called, 490 * will be displayed. 491 * @param key the identifier of a the metadata field to set. Valid values are 492 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, 493 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, 494 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value 495 * expressed in milliseconds), 496 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. 497 * @param value The long value for the given key 498 * @return Returns a reference to the same MetadataEditor object, so you can chain put 499 * calls together. 500 * @throws IllegalArgumentException 501 */ 502 public synchronized MetadataEditor putLong(int key, long value) 503 throws IllegalArgumentException { 504 super.putLong(key, value); 505 if (mMetadataBuilder != null) { 506 // MediaMetadata supports all the same fields as MetadataEditor 507 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); 508 // But just in case, don't add things we don't understand 509 if (metadataKey != null) { 510 mMetadataBuilder.putLong(metadataKey, value); 511 } 512 } 513 return this; 514 } 515 516 /** 517 * Sets the album / artwork picture to be displayed on the remote control. 518 * @param key the identifier of the bitmap to set. The only valid value is 519 * {@link #BITMAP_KEY_ARTWORK} 520 * @param bitmap The bitmap for the artwork, or null if there isn't any. 521 * @return Returns a reference to the same MetadataEditor object, so you can chain put 522 * calls together. 523 * @throws IllegalArgumentException 524 * @see android.graphics.Bitmap 525 */ 526 @Override 527 public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) 528 throws IllegalArgumentException { 529 super.putBitmap(key, bitmap); 530 if (mMetadataBuilder != null) { 531 // MediaMetadata supports all the same fields as MetadataEditor 532 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); 533 // But just in case, don't add things we don't understand 534 if (metadataKey != null) { 535 mMetadataBuilder.putBitmap(metadataKey, bitmap); 536 } 537 } 538 return this; 539 } 540 541 /** 542 * Clears all the metadata that has been set since the MetadataEditor instance was created 543 * (with {@link RemoteControlClient#editMetadata(boolean)}). 544 * Note that clearing the metadata doesn't reset the editable keys 545 * (use {@link MediaMetadataEditor#removeEditableKeys()} instead). 546 */ 547 @Override 548 public synchronized void clear() { 549 super.clear(); 550 } 551 552 /** 553 * Associates all the metadata that has been set since the MetadataEditor instance was 554 * created with {@link RemoteControlClient#editMetadata(boolean)}, or since 555 * {@link #clear()} was called, with the RemoteControlClient. Once "applied", 556 * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. 557 */ 558 public synchronized void apply() { 559 if (mApplied) { 560 Log.e(TAG, "Can't apply a previously applied MetadataEditor"); 561 return; 562 } 563 synchronized (mCacheLock) { 564 // Still build the old metadata so when creating a new editor 565 // you get the expected values. 566 // assign the edited data 567 mMetadata = new Bundle(mEditorMetadata); 568 // add the information about editable keys 569 mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); 570 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { 571 mOriginalArtwork.recycle(); 572 } 573 mOriginalArtwork = mEditorArtwork; 574 mEditorArtwork = null; 575 576 // USE_SESSIONS 577 if (mSession != null && mMetadataBuilder != null) { 578 mSession.setMetadata(mMetadataBuilder.build()); 579 } 580 mApplied = true; 581 } 582 } 583 } 584 585 /** 586 * Creates a {@link MetadataEditor}. 587 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that 588 * was previously applied to the RemoteControlClient, or true if it is to be created empty. 589 * @return a new MetadataEditor instance. 590 */ 591 public MetadataEditor editMetadata(boolean startEmpty) { 592 MetadataEditor editor = new MetadataEditor(); 593 if (startEmpty) { 594 editor.mEditorMetadata = new Bundle(); 595 editor.mEditorArtwork = null; 596 editor.mMetadataChanged = true; 597 editor.mArtworkChanged = true; 598 editor.mEditableKeys = 0; 599 } else { 600 editor.mEditorMetadata = new Bundle(mMetadata); 601 editor.mEditorArtwork = mOriginalArtwork; 602 editor.mMetadataChanged = false; 603 editor.mArtworkChanged = false; 604 } 605 // USE_SESSIONS 606 if (startEmpty || mMediaMetadata == null) { 607 editor.mMetadataBuilder = new MediaMetadata.Builder(); 608 } else { 609 editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata); 610 } 611 return editor; 612 } 613 614 /** 615 * Sets the current playback state. 616 * @param state The current playback state, one of the following values: 617 * {@link #PLAYSTATE_STOPPED}, 618 * {@link #PLAYSTATE_PAUSED}, 619 * {@link #PLAYSTATE_PLAYING}, 620 * {@link #PLAYSTATE_FAST_FORWARDING}, 621 * {@link #PLAYSTATE_REWINDING}, 622 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 623 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 624 * {@link #PLAYSTATE_BUFFERING}, 625 * {@link #PLAYSTATE_ERROR}. 626 */ 627 public void setPlaybackState(int state) { 628 setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X, 629 false /* legacy API, converting to method with position and speed */); 630 } 631 632 /** 633 * Sets the current playback state and the matching media position for the current playback 634 * speed. 635 * @param state The current playback state, one of the following values: 636 * {@link #PLAYSTATE_STOPPED}, 637 * {@link #PLAYSTATE_PAUSED}, 638 * {@link #PLAYSTATE_PLAYING}, 639 * {@link #PLAYSTATE_FAST_FORWARDING}, 640 * {@link #PLAYSTATE_REWINDING}, 641 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 642 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 643 * {@link #PLAYSTATE_BUFFERING}, 644 * {@link #PLAYSTATE_ERROR}. 645 * @param timeInMs a 0 or positive value for the current media position expressed in ms 646 * (same unit as for when sending the media duration, if applicable, with 647 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the 648 * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not 649 * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state 650 * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). 651 * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 652 * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 653 * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). 654 */ 655 public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { 656 setPlaybackStateInt(state, timeInMs, playbackSpeed, true); 657 } 658 659 private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, 660 boolean hasPosition) { 661 synchronized(mCacheLock) { 662 if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) 663 || (mPlaybackSpeed != playbackSpeed)) { 664 // store locally 665 mPlaybackState = state; 666 // distinguish between an application not knowing the current playback position 667 // at the moment and an application using the API where only the playback state 668 // is passed, not the playback position. 669 if (hasPosition) { 670 if (timeInMs < 0) { 671 mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 672 } else { 673 mPlaybackPositionMs = timeInMs; 674 } 675 } else { 676 mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN; 677 } 678 mPlaybackSpeed = playbackSpeed; 679 // keep track of when the state change occurred 680 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); 681 682 // USE_SESSIONS 683 if (mSession != null) { 684 int pbState = PlaybackState.getStateFromRccState(state); 685 mSessionPlaybackState.setState(pbState, hasPosition ? 686 mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN, 687 playbackSpeed); 688 mSession.setPlaybackState(mSessionPlaybackState); 689 } 690 } 691 } 692 } 693 694 // TODO investigate if we still need position drift checking 695 private void onPositionDriftCheck() { 696 if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } 697 synchronized(mCacheLock) { 698 if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) { 699 return; 700 } 701 if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) { 702 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); } 703 return; 704 } 705 long estPos = mPlaybackPositionMs + (long) 706 ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed); 707 long actPos = mPositionProvider.onGetPlaybackPosition(); 708 if (actPos >= 0) { 709 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) { 710 // drift happened, report the new position 711 if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); } 712 setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed); 713 } else { 714 if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); } 715 // no drift, schedule the next drift check 716 mEventHandler.sendMessageDelayed( 717 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 718 getCheckPeriodFromSpeed(mPlaybackSpeed)); 719 } 720 } else { 721 // invalid position (negative value), can't check for drift 722 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); 723 } 724 } 725 } 726 727 /** 728 * Sets the flags for the media transport control buttons that this client supports. 729 * @param transportControlFlags A combination of the following flags: 730 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 731 * {@link #FLAG_KEY_MEDIA_REWIND}, 732 * {@link #FLAG_KEY_MEDIA_PLAY}, 733 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 734 * {@link #FLAG_KEY_MEDIA_PAUSE}, 735 * {@link #FLAG_KEY_MEDIA_STOP}, 736 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 737 * {@link #FLAG_KEY_MEDIA_NEXT}, 738 * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, 739 * {@link #FLAG_KEY_MEDIA_RATING}. 740 */ 741 public void setTransportControlFlags(int transportControlFlags) { 742 synchronized(mCacheLock) { 743 // store locally 744 mTransportControlFlags = transportControlFlags; 745 746 // USE_SESSIONS 747 if (mSession != null) { 748 mSessionPlaybackState.setActions(PlaybackState 749 .getActionsFromRccControlFlags(transportControlFlags)); 750 mSession.setPlaybackState(mSessionPlaybackState); 751 } 752 } 753 } 754 755 /** 756 * Interface definition for a callback to be invoked when one of the metadata values has 757 * been updated. 758 * Implement this interface to receive metadata updates after registering your listener 759 * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}. 760 */ 761 public interface OnMetadataUpdateListener { 762 /** 763 * Called on the implementer to notify that the metadata field for the given key has 764 * been updated to the new value. 765 * @param key the identifier of the updated metadata field. 766 * @param newValue the Object storing the new value for the key. 767 */ 768 public abstract void onMetadataUpdate(int key, Object newValue); 769 } 770 771 /** 772 * Sets the listener to be called whenever the metadata is updated. 773 * New metadata values will be received in the same thread as the one in which 774 * RemoteControlClient was created. 775 * @param l the metadata update listener 776 */ 777 public void setMetadataUpdateListener(OnMetadataUpdateListener l) { 778 synchronized(mCacheLock) { 779 mMetadataUpdateListener = l; 780 } 781 } 782 783 784 /** 785 * Interface definition for a callback to be invoked when the media playback position is 786 * requested to be updated. 787 * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE 788 */ 789 public interface OnPlaybackPositionUpdateListener { 790 /** 791 * Called on the implementer to notify it that the playback head should be set at the given 792 * position. If the position can be changed from its current value, the implementor of 793 * the interface must also update the playback position using 794 * {@link #setPlaybackState(int, long, float)} to reflect the actual new 795 * position being used, regardless of whether it differs from the requested position. 796 * Failure to do so would cause the system to not know the new actual playback position, 797 * and user interface components would fail to show the user where playback resumed after 798 * the position was updated. 799 * @param newPositionMs the new requested position in the current media, expressed in ms. 800 */ 801 void onPlaybackPositionUpdate(long newPositionMs); 802 } 803 804 /** 805 * Interface definition for a callback to be invoked when the media playback position is 806 * queried. 807 * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE 808 */ 809 public interface OnGetPlaybackPositionListener { 810 /** 811 * Called on the implementer of the interface to query the current playback position. 812 * @return a negative value if the current playback position (or the last valid playback 813 * position) is not known, or a zero or positive value expressed in ms indicating the 814 * current position, or the last valid known position. 815 */ 816 long onGetPlaybackPosition(); 817 } 818 819 /** 820 * Sets the listener to be called whenever the media playback position is requested 821 * to be updated. 822 * Notifications will be received in the same thread as the one in which RemoteControlClient 823 * was created. 824 * @param l the position update listener to be called 825 */ 826 public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { 827 synchronized(mCacheLock) { 828 mPositionUpdateListener = l; 829 } 830 } 831 832 /** 833 * Sets the listener to be called whenever the media current playback position is needed. 834 * Queries will be received in the same thread as the one in which RemoteControlClient 835 * was created. 836 * @param l the listener to be called to retrieve the playback position 837 */ 838 public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) { 839 synchronized(mCacheLock) { 840 mPositionProvider = l; 841 if ((mPositionProvider != null) && (mEventHandler != null) 842 && playbackPositionShouldMove(mPlaybackState)) { 843 // playback position is already moving, but now we have a position provider, 844 // so schedule a drift check right now 845 mEventHandler.sendMessageDelayed( 846 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 847 0 /*check now*/); 848 } 849 } 850 } 851 852 /** 853 * @hide 854 * Flag to reflect that the application controlling this RemoteControlClient sends playback 855 * position updates. The playback position being "readable" is considered from the application's 856 * point of view. 857 */ 858 public static int MEDIA_POSITION_READABLE = 1 << 0; 859 /** 860 * @hide 861 * Flag to reflect that the application controlling this RemoteControlClient can receive 862 * playback position updates. The playback position being "writable" 863 * is considered from the application's point of view. 864 */ 865 public static int MEDIA_POSITION_WRITABLE = 1 << 1; 866 867 /** @hide */ 868 public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; 869 /** @hide */ 870 // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] 871 public final static int DEFAULT_PLAYBACK_VOLUME = 15; 872 873 /** 874 * Lock for all cached data 875 */ 876 private final Object mCacheLock = new Object(); 877 /** 878 * Cache for the playback state. 879 * Access synchronized on mCacheLock 880 */ 881 private int mPlaybackState = PLAYSTATE_NONE; 882 /** 883 * Time of last play state change 884 * Access synchronized on mCacheLock 885 */ 886 private long mPlaybackStateChangeTimeMs = 0; 887 /** 888 * Last playback position in ms reported by the user 889 */ 890 private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 891 /** 892 * Last playback speed reported by the user 893 */ 894 private float mPlaybackSpeed = PLAYBACK_SPEED_1X; 895 /** 896 * Cache for the artwork bitmap. 897 * Access synchronized on mCacheLock 898 * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be 899 * accessed to be resized, in which case a copy will be made. This would add overhead in 900 * Bundle operations. 901 */ 902 private Bitmap mOriginalArtwork; 903 /** 904 * Cache for the transport control mask. 905 * Access synchronized on mCacheLock 906 */ 907 private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE; 908 /** 909 * Cache for the metadata strings. 910 * Access synchronized on mCacheLock 911 * This is re-initialized in apply() and so cannot be final. 912 */ 913 private Bundle mMetadata = new Bundle(); 914 /** 915 * Listener registered by user of RemoteControlClient to receive requests for playback position 916 * update requests. 917 */ 918 private OnPlaybackPositionUpdateListener mPositionUpdateListener; 919 /** 920 * Provider registered by user of RemoteControlClient to provide the current playback position. 921 */ 922 private OnGetPlaybackPositionListener mPositionProvider; 923 /** 924 * Listener registered by user of RemoteControlClient to receive edit changes to metadata 925 * it exposes. 926 */ 927 private OnMetadataUpdateListener mMetadataUpdateListener; 928 /** 929 * The current remote control client generation ID across the system, as known by this object 930 */ 931 private int mCurrentClientGenId = -1; 932 933 /** 934 * The media button intent description associated with this remote control client 935 * (can / should include target component for intent handling, used when persisting media 936 * button event receiver across reboots). 937 */ 938 private final PendingIntent mRcMediaIntent; 939 940 /** 941 * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. 942 */ 943 // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead 944 private boolean mNeedsPositionSync = false; 945 946 /** 947 * Cache for the current playback state using Session APIs. 948 */ 949 private final PlaybackState mSessionPlaybackState = new PlaybackState(); 950 951 /** 952 * Cache for metadata using Session APIs. This is re-initialized in apply(). 953 */ 954 private MediaMetadata mMediaMetadata; 955 956 /** 957 * @hide 958 * Accessor to media button intent description (includes target component) 959 */ 960 public PendingIntent getRcMediaIntent() { 961 return mRcMediaIntent; 962 } 963 964 /** 965 * @hide 966 * Default value for the unique identifier 967 */ 968 public final static int RCSE_ID_UNREGISTERED = -1; 969 970 // USE_SESSIONS 971 private MediaSession.TransportControlsCallback mTransportListener 972 = new MediaSession.TransportControlsCallback() { 973 974 @Override 975 public void onSeekTo(long pos) { 976 RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos); 977 } 978 979 @Override 980 public void onSetRating(Rating rating) { 981 if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) { 982 onUpdateMetadata(mCurrentClientGenId, MetadataEditor.RATING_KEY_BY_USER, rating); 983 } 984 } 985 }; 986 987 private EventHandler mEventHandler; 988 private final static int MSG_POSITION_DRIFT_CHECK = 11; 989 990 private class EventHandler extends Handler { 991 public EventHandler(RemoteControlClient rcc, Looper looper) { 992 super(looper); 993 } 994 995 @Override 996 public void handleMessage(Message msg) { 997 switch(msg.what) { 998 case MSG_POSITION_DRIFT_CHECK: 999 onPositionDriftCheck(); 1000 break; 1001 default: 1002 Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); 1003 } 1004 } 1005 } 1006 1007 //=========================================================== 1008 // Message handlers 1009 1010 private void onSeekTo(int generationId, long timeMs) { 1011 synchronized (mCacheLock) { 1012 if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { 1013 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); 1014 } 1015 } 1016 } 1017 1018 private void onUpdateMetadata(int generationId, int key, Object value) { 1019 synchronized (mCacheLock) { 1020 if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { 1021 mMetadataUpdateListener.onMetadataUpdate(key, value); 1022 } 1023 } 1024 } 1025 1026 //=========================================================== 1027 // Internal utilities 1028 1029 /** 1030 * Returns whether, for the given playback state, the playback position is expected to 1031 * be changing. 1032 * @param playstate the playback state to evaluate 1033 * @return true during any form of playback, false if it's not playing anything while in this 1034 * playback state 1035 */ 1036 static boolean playbackPositionShouldMove(int playstate) { 1037 switch(playstate) { 1038 case PLAYSTATE_STOPPED: 1039 case PLAYSTATE_PAUSED: 1040 case PLAYSTATE_BUFFERING: 1041 case PLAYSTATE_ERROR: 1042 case PLAYSTATE_SKIPPING_FORWARDS: 1043 case PLAYSTATE_SKIPPING_BACKWARDS: 1044 return false; 1045 case PLAYSTATE_PLAYING: 1046 case PLAYSTATE_FAST_FORWARDING: 1047 case PLAYSTATE_REWINDING: 1048 default: 1049 return true; 1050 } 1051 } 1052 1053 /** 1054 * Period for playback position drift checks, 15s when playing at 1x or slower. 1055 */ 1056 private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; 1057 /** 1058 * Minimum period for playback position drift checks, never more often when every 2s, when 1059 * fast forwarding or rewinding. 1060 */ 1061 private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; 1062 /** 1063 * The value above which the difference between client-reported playback position and 1064 * estimated position is considered a drift. 1065 */ 1066 private final static long POSITION_DRIFT_MAX_MS = 500; 1067 /** 1068 * Compute the period at which the estimated playback position should be compared against the 1069 * actual playback position. Is a funciton of playback speed. 1070 * @param speed 1.0f is normal playback speed 1071 * @return the period in ms 1072 */ 1073 private static long getCheckPeriodFromSpeed(float speed) { 1074 if (Math.abs(speed) <= 1.0f) { 1075 return POSITION_REFRESH_PERIOD_PLAYING_MS; 1076 } else { 1077 return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)), 1078 POSITION_REFRESH_PERIOD_MIN_MS); 1079 } 1080 } 1081} 1082