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