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.Context; 22 import android.content.Intent; 23 import android.graphics.Bitmap; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.graphics.RectF; 27 import android.media.MediaMetadataRetriever; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.SystemClock; 36 import android.util.Log; 37 38 import java.lang.IllegalArgumentException; 39 import java.util.ArrayList; 40 import java.util.Iterator; 41 42 /** 43 * RemoteControlClient enables exposing information meant to be consumed by remote controls 44 * capable of displaying metadata, artwork and media transport control buttons. 45 * 46 * <p>A remote control client object is associated with a media button event receiver. This 47 * event receiver must have been previously registered with 48 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the 49 * RemoteControlClient can be registered through 50 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 51 * 52 * <p>Here is an example of creating a RemoteControlClient instance after registering a media 53 * button event receiver: 54 * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName()); 55 * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 56 * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver); 57 * // build the PendingIntent for the remote control client 58 * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 59 * mediaButtonIntent.setComponent(myEventReceiver); 60 * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); 61 * // create and register the remote control client 62 * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent); 63 * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre> 64 */ 65 public class RemoteControlClient 66 { 67 private final static String TAG = "RemoteControlClient"; 68 private final static boolean DEBUG = false; 69 70 /** 71 * Playback state of a RemoteControlClient which is stopped. 72 * 73 * @see #setPlaybackState(int) 74 */ 75 public final static int PLAYSTATE_STOPPED = 1; 76 /** 77 * Playback state of a RemoteControlClient which is paused. 78 * 79 * @see #setPlaybackState(int) 80 */ 81 public final static int PLAYSTATE_PAUSED = 2; 82 /** 83 * Playback state of a RemoteControlClient which is playing media. 84 * 85 * @see #setPlaybackState(int) 86 */ 87 public final static int PLAYSTATE_PLAYING = 3; 88 /** 89 * Playback state of a RemoteControlClient which is fast forwarding in the media 90 * it is currently playing. 91 * 92 * @see #setPlaybackState(int) 93 */ 94 public final static int PLAYSTATE_FAST_FORWARDING = 4; 95 /** 96 * Playback state of a RemoteControlClient which is fast rewinding in the media 97 * it is currently playing. 98 * 99 * @see #setPlaybackState(int) 100 */ 101 public final static int PLAYSTATE_REWINDING = 5; 102 /** 103 * Playback state of a RemoteControlClient which is skipping to the next 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_FORWARDS = 6; 109 /** 110 * Playback state of a RemoteControlClient which is skipping back to the previous 111 * logical chapter (such as a song in a playlist) in the media it is currently playing. 112 * 113 * @see #setPlaybackState(int) 114 */ 115 public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7; 116 /** 117 * Playback state of a RemoteControlClient which is buffering data to play before it can 118 * start or resume playback. 119 * 120 * @see #setPlaybackState(int) 121 */ 122 public final static int PLAYSTATE_BUFFERING = 8; 123 /** 124 * Playback state of a RemoteControlClient which cannot perform any playback related 125 * operation because of an internal error. Examples of such situations are no network 126 * connectivity when attempting to stream data from a server, or expired user credentials 127 * when trying to play subscription-based content. 128 * 129 * @see #setPlaybackState(int) 130 */ 131 public final static int PLAYSTATE_ERROR = 9; 132 /** 133 * @hide 134 * The value of a playback state when none has been declared. 135 * Intentionally hidden as an application shouldn't set such a playback state value. 136 */ 137 public final static int PLAYSTATE_NONE = 0; 138 139 /** 140 * @hide 141 * The default playback type, "local", indicating the presentation of the media is happening on 142 * the same device (e.g. a phone, a tablet) as where it is controlled from. 143 */ 144 public final static int PLAYBACK_TYPE_LOCAL = 0; 145 /** 146 * @hide 147 * A playback type indicating the presentation of the media is happening on 148 * a different device (i.e. the remote device) than where it is controlled from. 149 */ 150 public final static int PLAYBACK_TYPE_REMOTE = 1; 151 private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL; 152 private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE; 153 /** 154 * @hide 155 * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled 156 * from this object. An example of fixed playback volume is a remote player, playing over HDMI 157 * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the 158 * source. 159 * @see #PLAYBACKINFO_VOLUME_HANDLING. 160 */ 161 public final static int PLAYBACK_VOLUME_FIXED = 0; 162 /** 163 * @hide 164 * Playback information indicating the playback volume is variable and can be controlled from 165 * this object. 166 * @see #PLAYBACKINFO_VOLUME_HANDLING. 167 */ 168 public final static int PLAYBACK_VOLUME_VARIABLE = 1; 169 /** 170 * @hide (to be un-hidden) 171 * The playback information value indicating the value of a given information type is invalid. 172 * @see #PLAYBACKINFO_VOLUME_HANDLING. 173 */ 174 public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; 175 176 /** 177 * @hide 178 * An unknown or invalid playback position value. 179 */ 180 public final static long PLAYBACK_POSITION_INVALID = -1; 181 /** 182 * @hide 183 * An invalid playback position value associated with the use of {@link #setPlaybackState(int)} 184 * used to indicate that playback position will remain unknown. 185 */ 186 public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L; 187 /** 188 * @hide 189 * The default playback speed, 1x. 190 */ 191 public final static float PLAYBACK_SPEED_1X = 1.0f; 192 193 //========================================== 194 // Public keys for playback information 195 /** 196 * @hide 197 * Playback information that defines the type of playback associated with this 198 * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}. 199 */ 200 public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1; 201 /** 202 * @hide 203 * Playback information that defines at what volume the playback associated with this 204 * RemoteControlClient is performed. This information is only used when the playback type is not 205 * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 206 */ 207 public final static int PLAYBACKINFO_VOLUME = 2; 208 /** 209 * @hide 210 * Playback information that defines the maximum volume volume value that is supported 211 * by the playback associated with this RemoteControlClient. This information is only used 212 * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 213 */ 214 public final static int PLAYBACKINFO_VOLUME_MAX = 3; 215 /** 216 * @hide 217 * Playback information that defines how volume is handled for the presentation of the media. 218 * @see #PLAYBACK_VOLUME_FIXED 219 * @see #PLAYBACK_VOLUME_VARIABLE 220 */ 221 public final static int PLAYBACKINFO_VOLUME_HANDLING = 4; 222 /** 223 * @hide 224 * Playback information that defines over what stream type the media is presented. 225 */ 226 public final static int PLAYBACKINFO_USES_STREAM = 5; 227 228 //========================================== 229 // Public flags for the supported transport control capabilities 230 /** 231 * Flag indicating a RemoteControlClient makes use of the "previous" media key. 232 * 233 * @see #setTransportControlFlags(int) 234 * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS 235 */ 236 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 237 /** 238 * Flag indicating a RemoteControlClient makes use of the "rewind" media key. 239 * 240 * @see #setTransportControlFlags(int) 241 * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND 242 */ 243 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 244 /** 245 * Flag indicating a RemoteControlClient makes use of the "play" media key. 246 * 247 * @see #setTransportControlFlags(int) 248 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY 249 */ 250 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 251 /** 252 * Flag indicating a RemoteControlClient makes use of the "play/pause" media key. 253 * 254 * @see #setTransportControlFlags(int) 255 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE 256 */ 257 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 258 /** 259 * Flag indicating a RemoteControlClient makes use of the "pause" media key. 260 * 261 * @see #setTransportControlFlags(int) 262 * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE 263 */ 264 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 265 /** 266 * Flag indicating a RemoteControlClient makes use of the "stop" media key. 267 * 268 * @see #setTransportControlFlags(int) 269 * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP 270 */ 271 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 272 /** 273 * Flag indicating a RemoteControlClient makes use of the "fast forward" media key. 274 * 275 * @see #setTransportControlFlags(int) 276 * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD 277 */ 278 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 279 /** 280 * Flag indicating a RemoteControlClient makes use of the "next" media key. 281 * 282 * @see #setTransportControlFlags(int) 283 * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT 284 */ 285 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 286 /** 287 * Flag indicating a RemoteControlClient can receive changes in the media playback position 288 * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set 289 * in order for components that display the RemoteControlClient information, to display and 290 * let the user control media playback position. 291 * @see #setTransportControlFlags(int) 292 * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener) 293 * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) 294 */ 295 public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; 296 297 /** 298 * @hide 299 * The flags for when no media keys are declared supported. 300 * Intentionally hidden as an application shouldn't set the transport control flags 301 * to this value. 302 */ 303 public final static int FLAGS_KEY_MEDIA_NONE = 0; 304 305 /** 306 * @hide 307 * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. 308 */ 309 public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0; 310 /** 311 * @hide 312 * Flag used to signal that the transport control buttons supported by the 313 * RemoteControlClient are requested. 314 * This can for instance happen when playback is at the end of a playlist, and the "next" 315 * operation is not supported anymore. 316 */ 317 public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1; 318 /** 319 * @hide 320 * Flag used to signal that the playback state of the RemoteControlClient is requested. 321 */ 322 public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2; 323 /** 324 * @hide 325 * Flag used to signal that the album art for the RemoteControlClient is requested. 326 */ 327 public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; 328 329 /** 330 * Class constructor. 331 * @param mediaButtonIntent The intent that will be sent for the media button events sent 332 * by remote controls. 333 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 334 * action, and have a component that will handle the intent (set with 335 * {@link Intent#setComponent(ComponentName)}) registered with 336 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 337 * before this new RemoteControlClient can itself be registered with 338 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 339 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 340 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 341 */ 342 public RemoteControlClient(PendingIntent mediaButtonIntent) { 343 mRcMediaIntent = mediaButtonIntent; 344 345 Looper looper; 346 if ((looper = Looper.myLooper()) != null) { 347 mEventHandler = new EventHandler(this, looper); 348 } else if ((looper = Looper.getMainLooper()) != null) { 349 mEventHandler = new EventHandler(this, looper); 350 } else { 351 mEventHandler = null; 352 Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); 353 } 354 } 355 356 /** 357 * Class constructor for a remote control client whose internal event handling 358 * happens on a user-provided Looper. 359 * @param mediaButtonIntent The intent that will be sent for the media button events sent 360 * by remote controls. 361 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 362 * action, and have a component that will handle the intent (set with 363 * {@link Intent#setComponent(ComponentName)}) registered with 364 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 365 * before this new RemoteControlClient can itself be registered with 366 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 367 * @param looper The Looper running the event loop. 368 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 369 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 370 */ 371 public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { 372 mRcMediaIntent = mediaButtonIntent; 373 374 mEventHandler = new EventHandler(this, looper); 375 } 376 377 private static final int[] METADATA_KEYS_TYPE_STRING = { 378 MediaMetadataRetriever.METADATA_KEY_ALBUM, 379 MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, 380 MediaMetadataRetriever.METADATA_KEY_TITLE, 381 MediaMetadataRetriever.METADATA_KEY_ARTIST, 382 MediaMetadataRetriever.METADATA_KEY_AUTHOR, 383 MediaMetadataRetriever.METADATA_KEY_COMPILATION, 384 MediaMetadataRetriever.METADATA_KEY_COMPOSER, 385 MediaMetadataRetriever.METADATA_KEY_DATE, 386 MediaMetadataRetriever.METADATA_KEY_GENRE, 387 MediaMetadataRetriever.METADATA_KEY_TITLE, 388 MediaMetadataRetriever.METADATA_KEY_WRITER }; 389 private static final int[] METADATA_KEYS_TYPE_LONG = { 390 MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, 391 MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, 392 MediaMetadataRetriever.METADATA_KEY_DURATION }; 393 394 /** 395 * Class used to modify metadata in a {@link RemoteControlClient} object. 396 * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, 397 * on which you set the metadata for the RemoteControlClient instance. Once all the information 398 * has been set, use {@link #apply()} to make it the new metadata that should be displayed 399 * for the associated client. Once the metadata has been "applied", you cannot reuse this 400 * instance of the MetadataEditor. 401 */ 402 public class MetadataEditor { 403 /** 404 * @hide 405 */ 406 protected boolean mMetadataChanged; 407 /** 408 * @hide 409 */ 410 protected boolean mArtworkChanged; 411 /** 412 * @hide 413 */ 414 protected Bitmap mEditorArtwork; 415 /** 416 * @hide 417 */ 418 protected Bundle mEditorMetadata; 419 private boolean mApplied = false; 420 421 // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance 422 private MetadataEditor() { } 423 /** 424 * @hide 425 */ 426 public Object clone() throws CloneNotSupportedException { 427 throw new CloneNotSupportedException(); 428 } 429 430 /** 431 * The metadata key for the content artwork / album art. 432 */ 433 public final static int BITMAP_KEY_ARTWORK = 100; 434 /** 435 * @hide 436 * TODO(jmtrivi) have lockscreen and music move to the new key name 437 */ 438 public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; 439 440 /** 441 * Adds textual information to be displayed. 442 * Note that none of the information added after {@link #apply()} has been called, 443 * will be displayed. 444 * @param key The identifier of a the metadata field to set. Valid values are 445 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, 446 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, 447 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 448 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, 449 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, 450 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, 451 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, 452 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, 453 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, 454 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 455 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. 456 * @param value The text for the given key, or {@code null} to signify there is no valid 457 * information for the field. 458 * @return Returns a reference to the same MetadataEditor object, so you can chain put 459 * calls together. 460 */ 461 public synchronized MetadataEditor putString(int key, String value) 462 throws IllegalArgumentException { 463 if (mApplied) { 464 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 465 return this; 466 } 467 if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { 468 throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); 469 } 470 mEditorMetadata.putString(String.valueOf(key), value); 471 mMetadataChanged = true; 472 return this; 473 } 474 475 /** 476 * Adds numerical information to be displayed. 477 * Note that none of the information added after {@link #apply()} has been called, 478 * will be displayed. 479 * @param key the identifier of a the metadata field to set. Valid values are 480 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, 481 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, 482 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value 483 * expressed in milliseconds), 484 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. 485 * @param value The long value for the given key 486 * @return Returns a reference to the same MetadataEditor object, so you can chain put 487 * calls together. 488 * @throws IllegalArgumentException 489 */ 490 public synchronized MetadataEditor putLong(int key, long value) 491 throws IllegalArgumentException { 492 if (mApplied) { 493 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 494 return this; 495 } 496 if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { 497 throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); 498 } 499 mEditorMetadata.putLong(String.valueOf(key), value); 500 mMetadataChanged = true; 501 return this; 502 } 503 504 /** 505 * Sets the album / artwork picture to be displayed on the remote control. 506 * @param key the identifier of the bitmap to set. The only valid value is 507 * {@link #BITMAP_KEY_ARTWORK} 508 * @param bitmap The bitmap for the artwork, or null if there isn't any. 509 * @return Returns a reference to the same MetadataEditor object, so you can chain put 510 * calls together. 511 * @throws IllegalArgumentException 512 * @see android.graphics.Bitmap 513 */ 514 public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) 515 throws IllegalArgumentException { 516 if (mApplied) { 517 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 518 return this; 519 } 520 if (key != BITMAP_KEY_ARTWORK) { 521 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); 522 } 523 mEditorArtwork = bitmap; 524 mArtworkChanged = true; 525 return this; 526 } 527 528 /** 529 * Clears all the metadata that has been set since the MetadataEditor instance was 530 * created with {@link RemoteControlClient#editMetadata(boolean)}. 531 */ 532 public synchronized void clear() { 533 if (mApplied) { 534 Log.e(TAG, "Can't clear a previously applied MetadataEditor"); 535 return; 536 } 537 mEditorMetadata.clear(); 538 mEditorArtwork = null; 539 } 540 541 /** 542 * Associates all the metadata that has been set since the MetadataEditor instance was 543 * created with {@link RemoteControlClient#editMetadata(boolean)}, or since 544 * {@link #clear()} was called, with the RemoteControlClient. Once "applied", 545 * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. 546 */ 547 public synchronized void apply() { 548 if (mApplied) { 549 Log.e(TAG, "Can't apply a previously applied MetadataEditor"); 550 return; 551 } 552 synchronized(mCacheLock) { 553 // assign the edited data 554 mMetadata = new Bundle(mEditorMetadata); 555 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { 556 mOriginalArtwork.recycle(); 557 } 558 mOriginalArtwork = mEditorArtwork; 559 mEditorArtwork = null; 560 if (mMetadataChanged & mArtworkChanged) { 561 // send to remote control display if conditions are met 562 sendMetadataWithArtwork_syncCacheLock(); 563 } else if (mMetadataChanged) { 564 // send to remote control display if conditions are met 565 sendMetadata_syncCacheLock(); 566 } else if (mArtworkChanged) { 567 // send to remote control display if conditions are met 568 sendArtwork_syncCacheLock(); 569 } 570 mApplied = true; 571 } 572 } 573 } 574 575 /** 576 * Creates a {@link MetadataEditor}. 577 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that 578 * was previously applied to the RemoteControlClient, or true if it is to be created empty. 579 * @return a new MetadataEditor instance. 580 */ 581 public MetadataEditor editMetadata(boolean startEmpty) { 582 MetadataEditor editor = new MetadataEditor(); 583 if (startEmpty) { 584 editor.mEditorMetadata = new Bundle(); 585 editor.mEditorArtwork = null; 586 editor.mMetadataChanged = true; 587 editor.mArtworkChanged = true; 588 } else { 589 editor.mEditorMetadata = new Bundle(mMetadata); 590 editor.mEditorArtwork = mOriginalArtwork; 591 editor.mMetadataChanged = false; 592 editor.mArtworkChanged = false; 593 } 594 return editor; 595 } 596 597 /** 598 * Sets the current playback state. 599 * @param state The current playback state, one of the following values: 600 * {@link #PLAYSTATE_STOPPED}, 601 * {@link #PLAYSTATE_PAUSED}, 602 * {@link #PLAYSTATE_PLAYING}, 603 * {@link #PLAYSTATE_FAST_FORWARDING}, 604 * {@link #PLAYSTATE_REWINDING}, 605 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 606 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 607 * {@link #PLAYSTATE_BUFFERING}, 608 * {@link #PLAYSTATE_ERROR}. 609 */ 610 public void setPlaybackState(int state) { 611 setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X, 612 false /* legacy API, converting to method with position and speed */); 613 } 614 615 /** 616 * Sets the current playback state and the matching media position for the current playback 617 * speed. 618 * @param state The current playback state, one of the following values: 619 * {@link #PLAYSTATE_STOPPED}, 620 * {@link #PLAYSTATE_PAUSED}, 621 * {@link #PLAYSTATE_PLAYING}, 622 * {@link #PLAYSTATE_FAST_FORWARDING}, 623 * {@link #PLAYSTATE_REWINDING}, 624 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 625 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 626 * {@link #PLAYSTATE_BUFFERING}, 627 * {@link #PLAYSTATE_ERROR}. 628 * @param timeInMs a 0 or positive value for the current media position expressed in ms 629 * (same unit as for when sending the media duration, if applicable, with 630 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the 631 * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not 632 * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state 633 * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). 634 * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 635 * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 636 * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). 637 */ 638 public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { 639 setPlaybackStateInt(state, timeInMs, playbackSpeed, true); 640 } 641 642 private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, 643 boolean hasPosition) { 644 synchronized(mCacheLock) { 645 if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) 646 || (mPlaybackSpeed != playbackSpeed)) { 647 // store locally 648 mPlaybackState = state; 649 // distinguish between an application not knowing the current playback position 650 // at the moment and an application using the API where only the playback state 651 // is passed, not the playback position. 652 if (hasPosition) { 653 if (timeInMs < 0) { 654 mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 655 } else { 656 mPlaybackPositionMs = timeInMs; 657 } 658 } else { 659 mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN; 660 } 661 mPlaybackSpeed = playbackSpeed; 662 // keep track of when the state change occurred 663 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); 664 665 // send to remote control display if conditions are met 666 sendPlaybackState_syncCacheLock(); 667 // update AudioService 668 sendAudioServiceNewPlaybackState_syncCacheLock(); 669 670 // handle automatic playback position refreshes 671 initiateCheckForDrift_syncCacheLock(); 672 } 673 } 674 } 675 676 private void initiateCheckForDrift_syncCacheLock() { 677 if (mEventHandler == null) { 678 return; 679 } 680 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); 681 if (!mNeedsPositionSync) { 682 return; 683 } 684 if (mPlaybackPositionMs < 0) { 685 // the current playback state has no known playback position, it's no use 686 // trying to see if there is any drift at this point 687 // (this also bypasses this mechanism for older apps that use the old 688 // setPlaybackState(int) API) 689 return; 690 } 691 if (playbackPositionShouldMove(mPlaybackState)) { 692 // playback position moving, schedule next position drift check 693 mEventHandler.sendMessageDelayed( 694 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 695 getCheckPeriodFromSpeed(mPlaybackSpeed)); 696 } 697 } 698 699 private void onPositionDriftCheck() { 700 if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } 701 synchronized(mCacheLock) { 702 if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) { 703 return; 704 } 705 if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) { 706 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); } 707 return; 708 } 709 long estPos = mPlaybackPositionMs + (long) 710 ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed); 711 long actPos = mPositionProvider.onGetPlaybackPosition(); 712 if (actPos >= 0) { 713 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) { 714 // drift happened, report the new position 715 if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); } 716 setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed); 717 } else { 718 if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); } 719 // no drift, schedule the next drift check 720 mEventHandler.sendMessageDelayed( 721 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 722 getCheckPeriodFromSpeed(mPlaybackSpeed)); 723 } 724 } else { 725 // invalid position (negative value), can't check for drift 726 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); 727 } 728 } 729 } 730 731 /** 732 * Sets the flags for the media transport control buttons that this client supports. 733 * @param transportControlFlags A combination of the following flags: 734 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 735 * {@link #FLAG_KEY_MEDIA_REWIND}, 736 * {@link #FLAG_KEY_MEDIA_PLAY}, 737 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 738 * {@link #FLAG_KEY_MEDIA_PAUSE}, 739 * {@link #FLAG_KEY_MEDIA_STOP}, 740 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 741 * {@link #FLAG_KEY_MEDIA_NEXT}, 742 * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE} 743 */ 744 public void setTransportControlFlags(int transportControlFlags) { 745 synchronized(mCacheLock) { 746 // store locally 747 mTransportControlFlags = transportControlFlags; 748 749 // send to remote control display if conditions are met 750 sendTransportControlInfo_syncCacheLock(); 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 */ 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 */ 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 */ 796 public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { 797 synchronized(mCacheLock) { 798 int oldCapa = mPlaybackPositionCapabilities; 799 if (l != null) { 800 mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; 801 } else { 802 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; 803 } 804 mPositionUpdateListener = l; 805 if (oldCapa != mPlaybackPositionCapabilities) { 806 // tell RCDs that this RCC's playback position capabilities have changed 807 sendTransportControlInfo_syncCacheLock(); 808 } 809 } 810 } 811 812 /** 813 * Sets the listener to be called whenever the media current playback position is needed. 814 * Queries will be received in the same thread as the one in which RemoteControlClient 815 * was created. 816 * @param l the listener to be called to retrieve the playback position 817 */ 818 public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) { 819 synchronized(mCacheLock) { 820 int oldCapa = mPlaybackPositionCapabilities; 821 if (l != null) { 822 mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; 823 } else { 824 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; 825 } 826 mPositionProvider = l; 827 if (oldCapa != mPlaybackPositionCapabilities) { 828 // tell RCDs that this RCC's playback position capabilities have changed 829 sendTransportControlInfo_syncCacheLock(); 830 } 831 if ((mPositionProvider != null) && (mEventHandler != null) 832 && playbackPositionShouldMove(mPlaybackState)) { 833 // playback position is already moving, but now we have a position provider, 834 // so schedule a drift check right now 835 mEventHandler.sendMessageDelayed( 836 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 837 0 /*check now*/); 838 } 839 } 840 } 841 842 /** 843 * @hide 844 * Flag to reflect that the application controlling this RemoteControlClient sends playback 845 * position updates. The playback position being "readable" is considered from the application's 846 * point of view. 847 */ 848 public static int MEDIA_POSITION_READABLE = 1 << 0; 849 /** 850 * @hide 851 * Flag to reflect that the application controlling this RemoteControlClient can receive 852 * playback position updates. The playback position being "writable" 853 * is considered from the application's point of view. 854 */ 855 public static int MEDIA_POSITION_WRITABLE = 1 << 1; 856 857 private int mPlaybackPositionCapabilities = 0; 858 859 /** @hide */ 860 public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; 861 /** @hide */ 862 // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] 863 public final static int DEFAULT_PLAYBACK_VOLUME = 15; 864 865 private int mPlaybackType = PLAYBACK_TYPE_LOCAL; 866 private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME; 867 private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME; 868 private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING; 869 private int mPlaybackStream = AudioManager.STREAM_MUSIC; 870 871 /** 872 * @hide 873 * Set information describing information related to the playback of media so the system 874 * can implement additional behavior to handle non-local playback usecases. 875 * @param what a key to specify the type of information to set. Valid keys are 876 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 877 * {@link #PLAYBACKINFO_USES_STREAM}, 878 * {@link #PLAYBACKINFO_VOLUME}, 879 * {@link #PLAYBACKINFO_VOLUME_MAX}, 880 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 881 * @param value the value for the supplied information to set. 882 */ 883 public void setPlaybackInformation(int what, int value) { 884 synchronized(mCacheLock) { 885 switch (what) { 886 case PLAYBACKINFO_PLAYBACK_TYPE: 887 if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) { 888 if (mPlaybackType != value) { 889 mPlaybackType = value; 890 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 891 } 892 } else { 893 Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE"); 894 } 895 break; 896 case PLAYBACKINFO_VOLUME: 897 if ((value > -1) && (value <= mPlaybackVolumeMax)) { 898 if (mPlaybackVolume != value) { 899 mPlaybackVolume = value; 900 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 901 } 902 } else { 903 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME"); 904 } 905 break; 906 case PLAYBACKINFO_VOLUME_MAX: 907 if (value > 0) { 908 if (mPlaybackVolumeMax != value) { 909 mPlaybackVolumeMax = value; 910 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 911 } 912 } else { 913 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX"); 914 } 915 break; 916 case PLAYBACKINFO_USES_STREAM: 917 if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) { 918 mPlaybackStream = value; 919 } else { 920 Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM"); 921 } 922 break; 923 case PLAYBACKINFO_VOLUME_HANDLING: 924 if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) { 925 if (mPlaybackVolumeHandling != value) { 926 mPlaybackVolumeHandling = value; 927 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 928 } 929 } else { 930 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING"); 931 } 932 break; 933 default: 934 // not throwing an exception or returning an error if more keys are to be 935 // supported in the future 936 Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what); 937 break; 938 } 939 } 940 } 941 942 /** 943 * @hide 944 * Return playback information represented as an integer value. 945 * @param what a key to specify the type of information to retrieve. Valid keys are 946 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 947 * {@link #PLAYBACKINFO_USES_STREAM}, 948 * {@link #PLAYBACKINFO_VOLUME}, 949 * {@link #PLAYBACKINFO_VOLUME_MAX}, 950 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 951 * @return the current value for the given information type, or 952 * {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or 953 * the value is unknown. 954 */ 955 public int getIntPlaybackInformation(int what) { 956 synchronized(mCacheLock) { 957 switch (what) { 958 case PLAYBACKINFO_PLAYBACK_TYPE: 959 return mPlaybackType; 960 case PLAYBACKINFO_VOLUME: 961 return mPlaybackVolume; 962 case PLAYBACKINFO_VOLUME_MAX: 963 return mPlaybackVolumeMax; 964 case PLAYBACKINFO_USES_STREAM: 965 return mPlaybackStream; 966 case PLAYBACKINFO_VOLUME_HANDLING: 967 return mPlaybackVolumeHandling; 968 default: 969 Log.e(TAG, "getIntPlaybackInformation() unknown key " + what); 970 return PLAYBACKINFO_INVALID_VALUE; 971 } 972 } 973 } 974 975 /** 976 * Lock for all cached data 977 */ 978 private final Object mCacheLock = new Object(); 979 /** 980 * Cache for the playback state. 981 * Access synchronized on mCacheLock 982 */ 983 private int mPlaybackState = PLAYSTATE_NONE; 984 /** 985 * Time of last play state change 986 * Access synchronized on mCacheLock 987 */ 988 private long mPlaybackStateChangeTimeMs = 0; 989 /** 990 * Last playback position in ms reported by the user 991 */ 992 private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 993 /** 994 * Last playback speed reported by the user 995 */ 996 private float mPlaybackSpeed = PLAYBACK_SPEED_1X; 997 /** 998 * Cache for the artwork bitmap. 999 * Access synchronized on mCacheLock 1000 * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be 1001 * accessed to be resized, in which case a copy will be made. This would add overhead in 1002 * Bundle operations. 1003 */ 1004 private Bitmap mOriginalArtwork; 1005 /** 1006 * Cache for the transport control mask. 1007 * Access synchronized on mCacheLock 1008 */ 1009 private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE; 1010 /** 1011 * Cache for the metadata strings. 1012 * Access synchronized on mCacheLock 1013 * This is re-initialized in apply() and so cannot be final. 1014 */ 1015 private Bundle mMetadata = new Bundle(); 1016 /** 1017 * Listener registered by user of RemoteControlClient to receive requests for playback position 1018 * update requests. 1019 */ 1020 private OnPlaybackPositionUpdateListener mPositionUpdateListener; 1021 /** 1022 * Provider registered by user of RemoteControlClient to provide the current playback position. 1023 */ 1024 private OnGetPlaybackPositionListener mPositionProvider; 1025 /** 1026 * The current remote control client generation ID across the system, as known by this object 1027 */ 1028 private int mCurrentClientGenId = -1; 1029 /** 1030 * The remote control client generation ID, the last time it was told it was the current RC. 1031 * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control 1032 * client is the "focused" one, and that whenever this client's info is updated, it needs to 1033 * send it to the known IRemoteControlDisplay interfaces. 1034 */ 1035 private int mInternalClientGenId = -2; 1036 1037 /** 1038 * The media button intent description associated with this remote control client 1039 * (can / should include target component for intent handling, used when persisting media 1040 * button event receiver across reboots). 1041 */ 1042 private final PendingIntent mRcMediaIntent; 1043 1044 /** 1045 * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. 1046 */ 1047 // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead 1048 private boolean mNeedsPositionSync = false; 1049 1050 /** 1051 * A class to encapsulate all the information about a remote control display. 1052 * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay 1053 */ 1054 private class DisplayInfoForClient { 1055 /** may never be null */ 1056 private IRemoteControlDisplay mRcDisplay; 1057 private int mArtworkExpectedWidth; 1058 private int mArtworkExpectedHeight; 1059 private boolean mWantsPositionSync = false; 1060 1061 DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { 1062 mRcDisplay = rcd; 1063 mArtworkExpectedWidth = w; 1064 mArtworkExpectedHeight = h; 1065 } 1066 } 1067 1068 /** 1069 * The list of remote control displays to which this client will send information. 1070 * Accessed and modified synchronized on mCacheLock 1071 */ 1072 private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1); 1073 1074 /** 1075 * @hide 1076 * Accessor to media button intent description (includes target component) 1077 */ 1078 public PendingIntent getRcMediaIntent() { 1079 return mRcMediaIntent; 1080 } 1081 /** 1082 * @hide 1083 * Accessor to IRemoteControlClient 1084 */ 1085 public IRemoteControlClient getIRemoteControlClient() { 1086 return mIRCC; 1087 } 1088 1089 /** 1090 * The IRemoteControlClient implementation 1091 */ 1092 private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { 1093 1094 public void onInformationRequested(int generationId, int infoFlags) { 1095 // only post messages, we can't block here 1096 if (mEventHandler != null) { 1097 // signal new client 1098 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); 1099 mEventHandler.sendMessage( 1100 mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, 1101 /*arg1*/ generationId, /*arg2, ignored*/ 0)); 1102 // send the information 1103 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); 1104 mEventHandler.removeMessages(MSG_REQUEST_METADATA); 1105 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); 1106 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); 1107 mEventHandler.sendMessage( 1108 mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); 1109 mEventHandler.sendMessage( 1110 mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); 1111 mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); 1112 mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); 1113 } 1114 } 1115 1116 public void setCurrentClientGenerationId(int clientGeneration) { 1117 // only post messages, we can't block here 1118 if (mEventHandler != null) { 1119 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN); 1120 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1121 MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/)); 1122 } 1123 } 1124 1125 public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { 1126 // only post messages, we can't block here 1127 if ((mEventHandler != null) && (rcd != null)) { 1128 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1129 MSG_PLUG_DISPLAY, w, h, rcd)); 1130 } 1131 } 1132 1133 public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) { 1134 // only post messages, we can't block here 1135 if ((mEventHandler != null) && (rcd != null)) { 1136 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1137 MSG_UNPLUG_DISPLAY, rcd)); 1138 } 1139 } 1140 1141 public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) { 1142 // only post messages, we can't block here 1143 if ((mEventHandler != null) && (rcd != null)) { 1144 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1145 MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); 1146 } 1147 } 1148 1149 public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) { 1150 // only post messages, we can't block here 1151 if ((mEventHandler != null) && (rcd != null)) { 1152 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1153 MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd)); 1154 } 1155 } 1156 1157 public void seekTo(int generationId, long timeMs) { 1158 // only post messages, we can't block here 1159 if (mEventHandler != null) { 1160 mEventHandler.removeMessages(MSG_SEEK_TO); 1161 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1162 MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */, 1163 new Long(timeMs))); 1164 } 1165 } 1166 }; 1167 1168 /** 1169 * @hide 1170 * Default value for the unique identifier 1171 */ 1172 public final static int RCSE_ID_UNREGISTERED = -1; 1173 /** 1174 * Unique identifier of the RemoteControlStackEntry in AudioService with which 1175 * this RemoteControlClient is associated. 1176 */ 1177 private int mRcseId = RCSE_ID_UNREGISTERED; 1178 /** 1179 * @hide 1180 * To be only used by AudioManager after it has received the unique id from 1181 * IAudioService.registerRemoteControlClient() 1182 * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which 1183 * this RemoteControlClient is associated. 1184 */ 1185 public void setRcseId(int id) { 1186 mRcseId = id; 1187 } 1188 1189 /** 1190 * @hide 1191 */ 1192 public int getRcseId() { 1193 return mRcseId; 1194 } 1195 1196 private EventHandler mEventHandler; 1197 private final static int MSG_REQUEST_PLAYBACK_STATE = 1; 1198 private final static int MSG_REQUEST_METADATA = 2; 1199 private final static int MSG_REQUEST_TRANSPORTCONTROL = 3; 1200 private final static int MSG_REQUEST_ARTWORK = 4; 1201 private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5; 1202 private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6; 1203 private final static int MSG_PLUG_DISPLAY = 7; 1204 private final static int MSG_UNPLUG_DISPLAY = 8; 1205 private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; 1206 private final static int MSG_SEEK_TO = 10; 1207 private final static int MSG_POSITION_DRIFT_CHECK = 11; 1208 private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; 1209 1210 private class EventHandler extends Handler { 1211 public EventHandler(RemoteControlClient rcc, Looper looper) { 1212 super(looper); 1213 } 1214 1215 @Override 1216 public void handleMessage(Message msg) { 1217 switch(msg.what) { 1218 case MSG_REQUEST_PLAYBACK_STATE: 1219 synchronized (mCacheLock) { 1220 sendPlaybackState_syncCacheLock(); 1221 } 1222 break; 1223 case MSG_REQUEST_METADATA: 1224 synchronized (mCacheLock) { 1225 sendMetadata_syncCacheLock(); 1226 } 1227 break; 1228 case MSG_REQUEST_TRANSPORTCONTROL: 1229 synchronized (mCacheLock) { 1230 sendTransportControlInfo_syncCacheLock(); 1231 } 1232 break; 1233 case MSG_REQUEST_ARTWORK: 1234 synchronized (mCacheLock) { 1235 sendArtwork_syncCacheLock(); 1236 } 1237 break; 1238 case MSG_NEW_INTERNAL_CLIENT_GEN: 1239 onNewInternalClientGen(msg.arg1); 1240 break; 1241 case MSG_NEW_CURRENT_CLIENT_GEN: 1242 onNewCurrentClientGen(msg.arg1); 1243 break; 1244 case MSG_PLUG_DISPLAY: 1245 onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1246 break; 1247 case MSG_UNPLUG_DISPLAY: 1248 onUnplugDisplay((IRemoteControlDisplay)msg.obj); 1249 break; 1250 case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: 1251 onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1252 break; 1253 case MSG_SEEK_TO: 1254 onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); 1255 break; 1256 case MSG_POSITION_DRIFT_CHECK: 1257 onPositionDriftCheck(); 1258 break; 1259 case MSG_DISPLAY_WANTS_POS_SYNC: 1260 onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); 1261 break; 1262 default: 1263 Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); 1264 } 1265 } 1266 } 1267 1268 //=========================================================== 1269 // Communication with the IRemoteControlDisplay (the displays known to the system) 1270 1271 private void sendPlaybackState_syncCacheLock() { 1272 if (mCurrentClientGenId == mInternalClientGenId) { 1273 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1274 while (displayIterator.hasNext()) { 1275 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1276 try { 1277 di.mRcDisplay.setPlaybackState(mInternalClientGenId, 1278 mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, 1279 mPlaybackSpeed); 1280 } catch (RemoteException e) { 1281 Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); 1282 displayIterator.remove(); 1283 } 1284 } 1285 } 1286 } 1287 1288 private void sendMetadata_syncCacheLock() { 1289 if (mCurrentClientGenId == mInternalClientGenId) { 1290 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1291 while (displayIterator.hasNext()) { 1292 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1293 try { 1294 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1295 } catch (RemoteException e) { 1296 Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); 1297 displayIterator.remove(); 1298 } 1299 } 1300 } 1301 } 1302 1303 private void sendTransportControlInfo_syncCacheLock() { 1304 if (mCurrentClientGenId == mInternalClientGenId) { 1305 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1306 while (displayIterator.hasNext()) { 1307 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1308 try { 1309 di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, 1310 mTransportControlFlags, mPlaybackPositionCapabilities); 1311 } catch (RemoteException e) { 1312 Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, 1313 e); 1314 displayIterator.remove(); 1315 } 1316 } 1317 } 1318 } 1319 1320 private void sendArtwork_syncCacheLock() { 1321 // FIXME modify to cache all requested sizes? 1322 if (mCurrentClientGenId == mInternalClientGenId) { 1323 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1324 while (displayIterator.hasNext()) { 1325 if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { 1326 displayIterator.remove(); 1327 } 1328 } 1329 } 1330 } 1331 1332 /** 1333 * Send artwork to an IRemoteControlDisplay. 1334 * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its 1335 * dimension requirements. 1336 * @return false if there was an error communicating with the IRemoteControlDisplay. 1337 */ 1338 private boolean sendArtworkToDisplay(DisplayInfoForClient di) { 1339 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1340 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1341 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1342 try { 1343 di.mRcDisplay.setArtwork(mInternalClientGenId, artwork); 1344 } catch (RemoteException e) { 1345 Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e); 1346 return false; 1347 } 1348 } 1349 return true; 1350 } 1351 1352 private void sendMetadataWithArtwork_syncCacheLock() { 1353 // FIXME modify to cache all requested sizes? 1354 if (mCurrentClientGenId == mInternalClientGenId) { 1355 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1356 while (displayIterator.hasNext()) { 1357 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1358 try { 1359 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1360 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1361 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1362 di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); 1363 } else { 1364 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1365 } 1366 } catch (RemoteException e) { 1367 Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); 1368 displayIterator.remove(); 1369 } 1370 } 1371 } 1372 } 1373 1374 //=========================================================== 1375 // Communication with AudioService 1376 1377 private static IAudioService sService; 1378 1379 private static IAudioService getService() 1380 { 1381 if (sService != null) { 1382 return sService; 1383 } 1384 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 1385 sService = IAudioService.Stub.asInterface(b); 1386 return sService; 1387 } 1388 1389 private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) { 1390 if (mRcseId == RCSE_ID_UNREGISTERED) { 1391 return; 1392 } 1393 //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value); 1394 IAudioService service = getService(); 1395 try { 1396 service.setPlaybackInfoForRcc(mRcseId, what, value); 1397 } catch (RemoteException e) { 1398 Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); 1399 } 1400 } 1401 1402 private void sendAudioServiceNewPlaybackState_syncCacheLock() { 1403 if (mRcseId == RCSE_ID_UNREGISTERED) { 1404 return; 1405 } 1406 IAudioService service = getService(); 1407 try { 1408 service.setPlaybackStateForRcc(mRcseId, 1409 mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); 1410 } catch (RemoteException e) { 1411 Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); 1412 } 1413 } 1414 1415 //=========================================================== 1416 // Message handlers 1417 1418 private void onNewInternalClientGen(int clientGeneration) { 1419 synchronized (mCacheLock) { 1420 // this remote control client is told it is the "focused" one: 1421 // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true 1422 mInternalClientGenId = clientGeneration; 1423 } 1424 } 1425 1426 private void onNewCurrentClientGen(int clientGeneration) { 1427 synchronized (mCacheLock) { 1428 mCurrentClientGenId = clientGeneration; 1429 } 1430 } 1431 1432 /** pre-condition rcd != null */ 1433 private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) { 1434 synchronized(mCacheLock) { 1435 // do we have this display already? 1436 boolean displayKnown = false; 1437 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1438 while (displayIterator.hasNext() && !displayKnown) { 1439 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1440 displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder()); 1441 if (displayKnown) { 1442 // this display was known but the change in artwork size will cause the 1443 // artwork to be refreshed 1444 if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { 1445 di.mArtworkExpectedWidth = w; 1446 di.mArtworkExpectedHeight = h; 1447 if (!sendArtworkToDisplay(di)) { 1448 displayIterator.remove(); 1449 } 1450 } 1451 } 1452 } 1453 if (!displayKnown) { 1454 mRcDisplays.add(new DisplayInfoForClient(rcd, w, h)); 1455 } 1456 } 1457 } 1458 1459 /** pre-condition rcd != null */ 1460 private void onUnplugDisplay(IRemoteControlDisplay rcd) { 1461 synchronized(mCacheLock) { 1462 Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1463 while (displayIterator.hasNext()) { 1464 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1465 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1466 displayIterator.remove(); 1467 break; 1468 } 1469 } 1470 // list of RCDs has changed, reevaluate whether position check is still needed 1471 boolean oldNeedsPositionSync = mNeedsPositionSync; 1472 boolean newNeedsPositionSync = false; 1473 displayIterator = mRcDisplays.iterator(); 1474 while (displayIterator.hasNext()) { 1475 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1476 if (di.mWantsPositionSync) { 1477 newNeedsPositionSync = true; 1478 break; 1479 } 1480 } 1481 mNeedsPositionSync = newNeedsPositionSync; 1482 if (oldNeedsPositionSync != mNeedsPositionSync) { 1483 // update needed? 1484 initiateCheckForDrift_syncCacheLock(); 1485 } 1486 } 1487 } 1488 1489 /** pre-condition rcd != null */ 1490 private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) { 1491 synchronized(mCacheLock) { 1492 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1493 while (displayIterator.hasNext()) { 1494 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1495 if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) && 1496 ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { 1497 di.mArtworkExpectedWidth = w; 1498 di.mArtworkExpectedHeight = h; 1499 if (!sendArtworkToDisplay(di)) { 1500 displayIterator.remove(); 1501 } 1502 break; 1503 } 1504 } 1505 } 1506 } 1507 1508 /** pre-condition rcd != null */ 1509 private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) { 1510 synchronized(mCacheLock) { 1511 boolean oldNeedsPositionSync = mNeedsPositionSync; 1512 boolean newNeedsPositionSync = false; 1513 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1514 // go through the list of RCDs and for each entry, check both whether this is the RCD 1515 // that gets upated, and whether the list has one entry that wants position sync 1516 while (displayIterator.hasNext()) { 1517 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1518 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1519 di.mWantsPositionSync = wantsSync; 1520 } 1521 if (di.mWantsPositionSync) { 1522 newNeedsPositionSync = true; 1523 } 1524 } 1525 mNeedsPositionSync = newNeedsPositionSync; 1526 if (oldNeedsPositionSync != mNeedsPositionSync) { 1527 // update needed? 1528 initiateCheckForDrift_syncCacheLock(); 1529 } 1530 } 1531 } 1532 1533 private void onSeekTo(int generationId, long timeMs) { 1534 synchronized (mCacheLock) { 1535 if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { 1536 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); 1537 } 1538 } 1539 } 1540 1541 //=========================================================== 1542 // Internal utilities 1543 1544 /** 1545 * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap. 1546 * If the bitmap fits, then do nothing and return the original. 1547 * 1548 * @param bitmap 1549 * @param maxWidth 1550 * @param maxHeight 1551 * @return 1552 */ 1553 1554 private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) { 1555 if (bitmap != null) { 1556 final int width = bitmap.getWidth(); 1557 final int height = bitmap.getHeight(); 1558 if (width > maxWidth || height > maxHeight) { 1559 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); 1560 int newWidth = Math.round(scale * width); 1561 int newHeight = Math.round(scale * height); 1562 Bitmap.Config newConfig = bitmap.getConfig(); 1563 if (newConfig == null) { 1564 newConfig = Bitmap.Config.ARGB_8888; 1565 } 1566 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig); 1567 Canvas canvas = new Canvas(outBitmap); 1568 Paint paint = new Paint(); 1569 paint.setAntiAlias(true); 1570 paint.setFilterBitmap(true); 1571 canvas.drawBitmap(bitmap, null, 1572 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 1573 bitmap = outBitmap; 1574 } 1575 } 1576 return bitmap; 1577 } 1578 1579 /** 1580 * Fast routine to go through an array of allowed keys and return whether the key is part 1581 * of that array 1582 * @param key the key value 1583 * @param validKeys the array of valid keys for a given type 1584 * @return true if the key is part of the array, false otherwise 1585 */ 1586 private static boolean validTypeForKey(int key, int[] validKeys) { 1587 try { 1588 for (int i = 0 ; ; i++) { 1589 if (key == validKeys[i]) { 1590 return true; 1591 } 1592 } 1593 } catch (ArrayIndexOutOfBoundsException e) { 1594 return false; 1595 } 1596 } 1597 1598 /** 1599 * Returns whether, for the given playback state, the playback position is expected to 1600 * be changing. 1601 * @param playstate the playback state to evaluate 1602 * @return true during any form of playback, false if it's not playing anything while in this 1603 * playback state 1604 */ 1605 private static boolean playbackPositionShouldMove(int playstate) { 1606 switch(playstate) { 1607 case PLAYSTATE_STOPPED: 1608 case PLAYSTATE_PAUSED: 1609 case PLAYSTATE_BUFFERING: 1610 case PLAYSTATE_ERROR: 1611 case PLAYSTATE_SKIPPING_FORWARDS: 1612 case PLAYSTATE_SKIPPING_BACKWARDS: 1613 return false; 1614 case PLAYSTATE_PLAYING: 1615 case PLAYSTATE_FAST_FORWARDING: 1616 case PLAYSTATE_REWINDING: 1617 default: 1618 return true; 1619 } 1620 } 1621 1622 /** 1623 * Period for playback position drift checks, 15s when playing at 1x or slower. 1624 */ 1625 private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; 1626 /** 1627 * Minimum period for playback position drift checks, never more often when every 2s, when 1628 * fast forwarding or rewinding. 1629 */ 1630 private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; 1631 /** 1632 * The value above which the difference between client-reported playback position and 1633 * estimated position is considered a drift. 1634 */ 1635 private final static long POSITION_DRIFT_MAX_MS = 500; 1636 /** 1637 * Compute the period at which the estimated playback position should be compared against the 1638 * actual playback position. Is a funciton of playback speed. 1639 * @param speed 1.0f is normal playback speed 1640 * @return the period in ms 1641 */ 1642 private static long getCheckPeriodFromSpeed(float speed) { 1643 if (Math.abs(speed) <= 1.0f) { 1644 return POSITION_REFRESH_PERIOD_PLAYING_MS; 1645 } else { 1646 return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)), 1647 POSITION_REFRESH_PERIOD_MIN_MS); 1648 } 1649 } 1650 } 1651