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