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