RemoteControlClient.java revision f823fc4dba2df5cf5f00e13361f2db93c81f6961
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media; 18 19import android.app.PendingIntent; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.graphics.Bitmap; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.RectF; 27import android.media.MediaMetadataRetriever; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.Looper; 32import android.os.Message; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.os.SystemClock; 36import android.util.Log; 37 38import java.lang.IllegalArgumentException; 39import java.util.ArrayList; 40import 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 */ 65public 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 * @hide 298 * CANDIDATE FOR PUBLIC API 299 * Flag indicating a RemoteControlClient supports ratings. 300 * This flag must be set in order for components that display the RemoteControlClient 301 * information, to display ratings information, and, if ratings are declared editable 302 * (by calling {@link MetadataEditor#addEditableKey(int)} with the 303 * {@link MetadataEditor#LONG_KEY_RATING_BY_USER} key), it will enable the user to rate 304 * the media. 305 * @see #setTransportControlFlags(int) 306 */ 307 public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; 308 309 /** 310 * @hide 311 * The flags for when no media keys are declared supported. 312 * Intentionally hidden as an application shouldn't set the transport control flags 313 * to this value. 314 */ 315 public final static int FLAGS_KEY_MEDIA_NONE = 0; 316 317 /** 318 * @hide 319 * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. 320 */ 321 public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0; 322 /** 323 * @hide 324 * Flag used to signal that the transport control buttons supported by the 325 * RemoteControlClient are requested. 326 * This can for instance happen when playback is at the end of a playlist, and the "next" 327 * operation is not supported anymore. 328 */ 329 public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1; 330 /** 331 * @hide 332 * Flag used to signal that the playback state of the RemoteControlClient is requested. 333 */ 334 public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2; 335 /** 336 * @hide 337 * Flag used to signal that the album art for the RemoteControlClient is requested. 338 */ 339 public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; 340 341 /** 342 * Class constructor. 343 * @param mediaButtonIntent The intent that will be sent for the media button events sent 344 * by remote controls. 345 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 346 * action, and have a component that will handle the intent (set with 347 * {@link Intent#setComponent(ComponentName)}) registered with 348 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 349 * before this new RemoteControlClient can itself be registered with 350 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 351 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 352 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 353 */ 354 public RemoteControlClient(PendingIntent mediaButtonIntent) { 355 mRcMediaIntent = mediaButtonIntent; 356 357 Looper looper; 358 if ((looper = Looper.myLooper()) != null) { 359 mEventHandler = new EventHandler(this, looper); 360 } else if ((looper = Looper.getMainLooper()) != null) { 361 mEventHandler = new EventHandler(this, looper); 362 } else { 363 mEventHandler = null; 364 Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); 365 } 366 } 367 368 /** 369 * Class constructor for a remote control client whose internal event handling 370 * happens on a user-provided Looper. 371 * @param mediaButtonIntent The intent that will be sent for the media button events sent 372 * by remote controls. 373 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 374 * action, and have a component that will handle the intent (set with 375 * {@link Intent#setComponent(ComponentName)}) registered with 376 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 377 * before this new RemoteControlClient can itself be registered with 378 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 379 * @param looper The Looper running the event loop. 380 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 381 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 382 */ 383 public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { 384 mRcMediaIntent = mediaButtonIntent; 385 386 mEventHandler = new EventHandler(this, looper); 387 } 388 389 private static final int[] METADATA_KEYS_TYPE_STRING = { 390 MediaMetadataRetriever.METADATA_KEY_ALBUM, 391 MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, 392 MediaMetadataRetriever.METADATA_KEY_TITLE, 393 MediaMetadataRetriever.METADATA_KEY_ARTIST, 394 MediaMetadataRetriever.METADATA_KEY_AUTHOR, 395 MediaMetadataRetriever.METADATA_KEY_COMPILATION, 396 MediaMetadataRetriever.METADATA_KEY_COMPOSER, 397 MediaMetadataRetriever.METADATA_KEY_DATE, 398 MediaMetadataRetriever.METADATA_KEY_GENRE, 399 MediaMetadataRetriever.METADATA_KEY_TITLE, 400 MediaMetadataRetriever.METADATA_KEY_WRITER }; 401 private static final int[] METADATA_KEYS_TYPE_LONG = { 402 MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, 403 MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, 404 MediaMetadataRetriever.METADATA_KEY_DURATION, 405 MetadataEditor.LONG_KEY_RATING_TYPE, 406 MetadataEditor.LONG_KEY_RATING_BY_OTHERS, 407 MetadataEditor.LONG_KEY_RATING_BY_USER}; 408 409 /** 410 * Class used to modify metadata in a {@link RemoteControlClient} object. 411 * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, 412 * on which you set the metadata for the RemoteControlClient instance. Once all the information 413 * has been set, use {@link #apply()} to make it the new metadata that should be displayed 414 * for the associated client. Once the metadata has been "applied", you cannot reuse this 415 * instance of the MetadataEditor. 416 */ 417 public class MetadataEditor { 418 /** 419 * Mask of editable keys. 420 */ 421 private long mEditableKeys; 422 /** 423 * @hide 424 */ 425 protected boolean mMetadataChanged; 426 /** 427 * @hide 428 */ 429 protected boolean mArtworkChanged; 430 /** 431 * @hide 432 */ 433 protected Bitmap mEditorArtwork; 434 /** 435 * @hide 436 */ 437 protected Bundle mEditorMetadata; 438 private boolean mApplied = false; 439 440 // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance 441 private MetadataEditor() { } 442 /** 443 * @hide 444 */ 445 public Object clone() throws CloneNotSupportedException { 446 throw new CloneNotSupportedException(); 447 } 448 449 /** 450 * The metadata key for the content artwork / album art. 451 */ 452 public final static int BITMAP_KEY_ARTWORK = 100; 453 /** 454 * @hide 455 * CANDIDATE FOR PUBLIC API 456 * The metadata key qualifying the content rating. 457 * The value associated with this key may be: {@link #RATING_HEART}, 458 * {@link #RATING_THUMB_UP_DOWN}, or a non-null positive integer expressing a maximum 459 * number of "stars" for the rating, for which a typical value is 3 or 5. 460 */ 461 public final static int LONG_KEY_RATING_TYPE = 101; 462 /** 463 * @hide 464 * CANDIDATE FOR PUBLIC API 465 * The metadata key for the content's average rating, not the user's rating. 466 * The value associated with this key may be: an integer value between 0 and 100, 467 * or {@link #RATING_NOT_RATED} to express that no average rating is available. 468 * <p></p> 469 * Note that a rating value up to 100 is not incompatible with a rating type using up 470 * to 5 stars for instance, as the average may be an non-integer number of stars. 471 * <p></p> 472 * When the rating type is: 473 * <ul> 474 * <li>{@link #RATING_HEART}, a rating of 50 to 100 means "heart selected",</li> 475 * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 49 means "thumb down", 50 means 476 * both "thumb up" and "thumb down" selected, 51 to 100 means "thumb up"</li> 477 * <li>a non-null positive integer, the rating value is mapped to the number of stars, e.g. 478 * with a maximum of 5 stars, a rating of 21 to 40 maps to 2 stars.</li> 479 * </ul> 480 * @see #LONG_KEY_RATING_BY_USER 481 */ 482 public final static int LONG_KEY_RATING_BY_OTHERS = 102; 483 484 // editable keys 485 /** 486 * @hide 487 * Editable key mask 488 */ 489 public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF; 490 /** 491 * @hide 492 * CANDIDATE FOR PUBLIC API 493 * The metadata key for the content's rating by the user. 494 * The value associated with this key may be: an integer value between 0 and 100, 495 * or {@link #RATING_NOT_RATED} to express that the user hasn't rated this content. 496 * Rules for the interpretation of the rating value according to the rating style are 497 * the same as for {@link #LONG_KEY_RATING_BY_OTHERS} 498 */ 499 public final static int LONG_KEY_RATING_BY_USER = 0x10000001; 500 501 /** 502 * @hide 503 * CANDIDATE FOR PUBLIC API 504 * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to 505 * indicate the content referred to is a favorite (or not). 506 * @see #LONG_KEY_RATING_TYPE 507 */ 508 public final static long RATING_HEART = -1; 509 /** 510 * @hide 511 * CANDIDATE FOR PUBLIC API 512 * A rating style for "thumb up" vs "thumb down". 513 * @see #LONG_KEY_RATING_TYPE 514 */ 515 public final static long RATING_THUMB_UP_DOWN = -2; 516 /** 517 * @hide 518 * CANDIDATE FOR PUBLIC API 519 * A rating value indicating no rating is available. 520 * @see #LONG_KEY_RATING_BY_OTHERS 521 * @see #LONG_KEY_RATING_BY_USER 522 */ 523 public final static long RATING_NOT_RATED = -101; 524 525 /** 526 * @hide 527 * TODO(jmtrivi) have lockscreen and music move to the new key name 528 */ 529 public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; 530 531 /** 532 * Adds textual information to be displayed. 533 * Note that none of the information added after {@link #apply()} has been called, 534 * will be displayed. 535 * @param key The identifier of a the metadata field to set. Valid values are 536 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, 537 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, 538 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 539 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, 540 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, 541 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, 542 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, 543 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, 544 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, 545 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 546 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. 547 * @param value The text for the given key, or {@code null} to signify there is no valid 548 * information for the field. 549 * @return Returns a reference to the same MetadataEditor object, so you can chain put 550 * calls together. 551 */ 552 public synchronized MetadataEditor putString(int key, String value) 553 throws IllegalArgumentException { 554 if (mApplied) { 555 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 556 return this; 557 } 558 if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { 559 throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); 560 } 561 mEditorMetadata.putString(String.valueOf(key), value); 562 mMetadataChanged = true; 563 return this; 564 } 565 566 /** 567 * Adds numerical information to be displayed. 568 * Note that none of the information added after {@link #apply()} has been called, 569 * will be displayed. 570 * @param key the identifier of a the metadata field to set. Valid values are 571 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, 572 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, 573 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value 574 * expressed in milliseconds), 575 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. 576 * @param value The long value for the given key 577 * @return Returns a reference to the same MetadataEditor object, so you can chain put 578 * calls together. 579 * @throws IllegalArgumentException 580 */ 581 public synchronized MetadataEditor putLong(int key, long value) 582 throws IllegalArgumentException { 583 if (mApplied) { 584 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 585 return this; 586 } 587 if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { 588 throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); 589 } 590 mEditorMetadata.putLong(String.valueOf(key), value); 591 mMetadataChanged = true; 592 return this; 593 } 594 595 /** 596 * Sets the album / artwork picture to be displayed on the remote control. 597 * @param key the identifier of the bitmap to set. The only valid value is 598 * {@link #BITMAP_KEY_ARTWORK} 599 * @param bitmap The bitmap for the artwork, or null if there isn't any. 600 * @return Returns a reference to the same MetadataEditor object, so you can chain put 601 * calls together. 602 * @throws IllegalArgumentException 603 * @see android.graphics.Bitmap 604 */ 605 public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) 606 throws IllegalArgumentException { 607 if (mApplied) { 608 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 609 return this; 610 } 611 if (key != BITMAP_KEY_ARTWORK) { 612 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); 613 } 614 mEditorArtwork = bitmap; 615 mArtworkChanged = true; 616 return this; 617 } 618 619 /** 620 * Clears all the metadata that has been set since the MetadataEditor instance was 621 * created with {@link RemoteControlClient#editMetadata(boolean)}. 622 */ 623 // TODO add in javadoc that this doesn't call clearEditableKeys() 624 public synchronized void clear() { 625 if (mApplied) { 626 Log.e(TAG, "Can't clear a previously applied MetadataEditor"); 627 return; 628 } 629 mEditorMetadata.clear(); 630 mEditorArtwork = null; 631 } 632 633 /** 634 * @hide 635 * CANDIDATE FOR PUBLIC API 636 */ 637 public synchronized void addEditableKey(int key) { 638 if (mApplied) { 639 Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor"); 640 return; 641 } 642 // only one editable key at the moment, so we're not wasting memory on an array 643 // of editable keys to check the validity of the key, just hardcode the supported key. 644 if (key == MetadataEditor.LONG_KEY_RATING_BY_USER) { 645 mEditableKeys |= (MetadataEditor.KEY_EDITABLE_MASK & key); 646 mMetadataChanged = true; 647 } else { 648 Log.e(TAG, "Metadata key " + key + " cannot be edited"); 649 } 650 } 651 652 /** 653 * @hide 654 * CANDIDATE FOR PUBLIC API 655 */ 656 public synchronized void clearEditableKeys() { 657 if (mApplied) { 658 Log.e(TAG, "Can't clear editable keys of a previously applied MetadataEditor"); 659 return; 660 } 661 if (mEditableKeys != 0) { 662 mEditableKeys = 0; 663 mMetadataChanged = true; 664 } 665 } 666 667 /** 668 * Associates all the metadata that has been set since the MetadataEditor instance was 669 * created with {@link RemoteControlClient#editMetadata(boolean)}, or since 670 * {@link #clear()} was called, with the RemoteControlClient. Once "applied", 671 * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. 672 */ 673 public synchronized void apply() { 674 if (mApplied) { 675 Log.e(TAG, "Can't apply a previously applied MetadataEditor"); 676 return; 677 } 678 synchronized(mCacheLock) { 679 // assign the edited data 680 mMetadata = new Bundle(mEditorMetadata); 681 // add the information about editable keys 682 mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); 683 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { 684 mOriginalArtwork.recycle(); 685 } 686 mOriginalArtwork = mEditorArtwork; 687 mEditorArtwork = null; 688 if (mMetadataChanged & mArtworkChanged) { 689 // send to remote control display if conditions are met 690 sendMetadataWithArtwork_syncCacheLock(); 691 } else if (mMetadataChanged) { 692 // send to remote control display if conditions are met 693 sendMetadata_syncCacheLock(); 694 } else if (mArtworkChanged) { 695 // send to remote control display if conditions are met 696 sendArtwork_syncCacheLock(); 697 } 698 mApplied = true; 699 } 700 } 701 } 702 703 /** 704 * Creates a {@link MetadataEditor}. 705 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that 706 * was previously applied to the RemoteControlClient, or true if it is to be created empty. 707 * @return a new MetadataEditor instance. 708 */ 709 public MetadataEditor editMetadata(boolean startEmpty) { 710 MetadataEditor editor = new MetadataEditor(); 711 if (startEmpty) { 712 editor.mEditorMetadata = new Bundle(); 713 editor.mEditorArtwork = null; 714 editor.mMetadataChanged = true; 715 editor.mArtworkChanged = true; 716 editor.mEditableKeys = 0; 717 } else { 718 editor.mEditorMetadata = new Bundle(mMetadata); 719 editor.mEditorArtwork = mOriginalArtwork; 720 editor.mMetadataChanged = false; 721 editor.mArtworkChanged = false; 722 } 723 return editor; 724 } 725 726 /** 727 * Sets the current playback state. 728 * @param state The current playback state, one of the following values: 729 * {@link #PLAYSTATE_STOPPED}, 730 * {@link #PLAYSTATE_PAUSED}, 731 * {@link #PLAYSTATE_PLAYING}, 732 * {@link #PLAYSTATE_FAST_FORWARDING}, 733 * {@link #PLAYSTATE_REWINDING}, 734 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 735 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 736 * {@link #PLAYSTATE_BUFFERING}, 737 * {@link #PLAYSTATE_ERROR}. 738 */ 739 public void setPlaybackState(int state) { 740 setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X, 741 false /* legacy API, converting to method with position and speed */); 742 } 743 744 /** 745 * Sets the current playback state and the matching media position for the current playback 746 * speed. 747 * @param state The current playback state, one of the following values: 748 * {@link #PLAYSTATE_STOPPED}, 749 * {@link #PLAYSTATE_PAUSED}, 750 * {@link #PLAYSTATE_PLAYING}, 751 * {@link #PLAYSTATE_FAST_FORWARDING}, 752 * {@link #PLAYSTATE_REWINDING}, 753 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 754 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 755 * {@link #PLAYSTATE_BUFFERING}, 756 * {@link #PLAYSTATE_ERROR}. 757 * @param timeInMs a 0 or positive value for the current media position expressed in ms 758 * (same unit as for when sending the media duration, if applicable, with 759 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the 760 * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not 761 * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state 762 * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). 763 * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 764 * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 765 * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). 766 */ 767 public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { 768 setPlaybackStateInt(state, timeInMs, playbackSpeed, true); 769 } 770 771 private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, 772 boolean hasPosition) { 773 synchronized(mCacheLock) { 774 if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) 775 || (mPlaybackSpeed != playbackSpeed)) { 776 // store locally 777 mPlaybackState = state; 778 // distinguish between an application not knowing the current playback position 779 // at the moment and an application using the API where only the playback state 780 // is passed, not the playback position. 781 if (hasPosition) { 782 if (timeInMs < 0) { 783 mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 784 } else { 785 mPlaybackPositionMs = timeInMs; 786 } 787 } else { 788 mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN; 789 } 790 mPlaybackSpeed = playbackSpeed; 791 // keep track of when the state change occurred 792 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); 793 794 // send to remote control display if conditions are met 795 sendPlaybackState_syncCacheLock(); 796 // update AudioService 797 sendAudioServiceNewPlaybackState_syncCacheLock(); 798 799 // handle automatic playback position refreshes 800 initiateCheckForDrift_syncCacheLock(); 801 } 802 } 803 } 804 805 private void initiateCheckForDrift_syncCacheLock() { 806 if (mEventHandler == null) { 807 return; 808 } 809 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); 810 if (!mNeedsPositionSync) { 811 return; 812 } 813 if (mPlaybackPositionMs < 0) { 814 // the current playback state has no known playback position, it's no use 815 // trying to see if there is any drift at this point 816 // (this also bypasses this mechanism for older apps that use the old 817 // setPlaybackState(int) API) 818 return; 819 } 820 if (playbackPositionShouldMove(mPlaybackState)) { 821 // playback position moving, schedule next position drift check 822 mEventHandler.sendMessageDelayed( 823 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 824 getCheckPeriodFromSpeed(mPlaybackSpeed)); 825 } 826 } 827 828 private void onPositionDriftCheck() { 829 if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } 830 synchronized(mCacheLock) { 831 if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) { 832 return; 833 } 834 if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) { 835 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); } 836 return; 837 } 838 long estPos = mPlaybackPositionMs + (long) 839 ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed); 840 long actPos = mPositionProvider.onGetPlaybackPosition(); 841 if (actPos >= 0) { 842 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) { 843 // drift happened, report the new position 844 if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); } 845 setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed); 846 } else { 847 if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); } 848 // no drift, schedule the next drift check 849 mEventHandler.sendMessageDelayed( 850 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 851 getCheckPeriodFromSpeed(mPlaybackSpeed)); 852 } 853 } else { 854 // invalid position (negative value), can't check for drift 855 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); 856 } 857 } 858 } 859 860 /** 861 * Sets the flags for the media transport control buttons that this client supports. 862 * @param transportControlFlags A combination of the following flags: 863 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 864 * {@link #FLAG_KEY_MEDIA_REWIND}, 865 * {@link #FLAG_KEY_MEDIA_PLAY}, 866 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 867 * {@link #FLAG_KEY_MEDIA_PAUSE}, 868 * {@link #FLAG_KEY_MEDIA_STOP}, 869 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 870 * {@link #FLAG_KEY_MEDIA_NEXT}, 871 * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE} 872 */ 873 public void setTransportControlFlags(int transportControlFlags) { 874 synchronized(mCacheLock) { 875 // store locally 876 mTransportControlFlags = transportControlFlags; 877 878 // send to remote control display if conditions are met 879 sendTransportControlInfo_syncCacheLock(); 880 } 881 } 882 883 /** 884 * @hide 885 * CANDIDATE FOR PUBLIC API 886 * TODO ADD DESCRIPTION 887 */ 888 public interface OnMetadataUpdateListener { 889 /** 890 * TODO ADD DESCRIPTION 891 * @param key 892 * @param newValue 893 */ 894 void onMetadataUpdateLong(int key, long newValue); 895 /** 896 * TODO ADD DESCRIPTION 897 * @param key 898 * @param newValue 899 */ 900 void onMetadataUpdateString(int key, String newValue); 901 /** 902 * TODO ADD DESCRIPTION 903 * @param key 904 * @param newValue 905 */ 906 void onMetadataUpdateBitmap(int key, Bitmap newValue); 907 } 908 909 /** 910 * @hide 911 * CANDIDATE FOR PUBLIC API 912 * TODO ADD DESCRIPTION 913 * @param l 914 */ 915 public void setMetadataUpdateListener(OnMetadataUpdateListener l) { 916 synchronized(mCacheLock) { 917 mMetadataUpdateListener = l; 918 } 919 } 920 921 922 /** 923 * Interface definition for a callback to be invoked when the media playback position is 924 * requested to be updated. 925 * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE 926 */ 927 public interface OnPlaybackPositionUpdateListener { 928 /** 929 * Called on the implementer to notify it that the playback head should be set at the given 930 * position. If the position can be changed from its current value, the implementor of 931 * the interface must also update the playback position using 932 * {@link #setPlaybackState(int, long, float)} to reflect the actual new 933 * position being used, regardless of whether it differs from the requested position. 934 * Failure to do so would cause the system to not know the new actual playback position, 935 * and user interface components would fail to show the user where playback resumed after 936 * the position was updated. 937 * @param newPositionMs the new requested position in the current media, expressed in ms. 938 */ 939 void onPlaybackPositionUpdate(long newPositionMs); 940 } 941 942 /** 943 * Interface definition for a callback to be invoked when the media playback position is 944 * queried. 945 * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE 946 */ 947 public interface OnGetPlaybackPositionListener { 948 /** 949 * Called on the implementer of the interface to query the current playback position. 950 * @return a negative value if the current playback position (or the last valid playback 951 * position) is not known, or a zero or positive value expressed in ms indicating the 952 * current position, or the last valid known position. 953 */ 954 long onGetPlaybackPosition(); 955 } 956 957 /** 958 * Sets the listener to be called whenever the media playback position is requested 959 * to be updated. 960 * Notifications will be received in the same thread as the one in which RemoteControlClient 961 * was created. 962 * @param l the position update listener to be called 963 */ 964 public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { 965 synchronized(mCacheLock) { 966 int oldCapa = mPlaybackPositionCapabilities; 967 if (l != null) { 968 mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; 969 } else { 970 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; 971 } 972 mPositionUpdateListener = l; 973 if (oldCapa != mPlaybackPositionCapabilities) { 974 // tell RCDs that this RCC's playback position capabilities have changed 975 sendTransportControlInfo_syncCacheLock(); 976 } 977 } 978 } 979 980 /** 981 * Sets the listener to be called whenever the media current playback position is needed. 982 * Queries will be received in the same thread as the one in which RemoteControlClient 983 * was created. 984 * @param l the listener to be called to retrieve the playback position 985 */ 986 public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) { 987 synchronized(mCacheLock) { 988 int oldCapa = mPlaybackPositionCapabilities; 989 if (l != null) { 990 mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; 991 } else { 992 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; 993 } 994 mPositionProvider = l; 995 if (oldCapa != mPlaybackPositionCapabilities) { 996 // tell RCDs that this RCC's playback position capabilities have changed 997 sendTransportControlInfo_syncCacheLock(); 998 } 999 if ((mPositionProvider != null) && (mEventHandler != null) 1000 && playbackPositionShouldMove(mPlaybackState)) { 1001 // playback position is already moving, but now we have a position provider, 1002 // so schedule a drift check right now 1003 mEventHandler.sendMessageDelayed( 1004 mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), 1005 0 /*check now*/); 1006 } 1007 } 1008 } 1009 1010 /** 1011 * @hide 1012 * Flag to reflect that the application controlling this RemoteControlClient sends playback 1013 * position updates. The playback position being "readable" is considered from the application's 1014 * point of view. 1015 */ 1016 public static int MEDIA_POSITION_READABLE = 1 << 0; 1017 /** 1018 * @hide 1019 * Flag to reflect that the application controlling this RemoteControlClient can receive 1020 * playback position updates. The playback position being "writable" 1021 * is considered from the application's point of view. 1022 */ 1023 public static int MEDIA_POSITION_WRITABLE = 1 << 1; 1024 1025 private int mPlaybackPositionCapabilities = 0; 1026 1027 /** @hide */ 1028 public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; 1029 /** @hide */ 1030 // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] 1031 public final static int DEFAULT_PLAYBACK_VOLUME = 15; 1032 1033 private int mPlaybackType = PLAYBACK_TYPE_LOCAL; 1034 private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME; 1035 private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME; 1036 private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING; 1037 private int mPlaybackStream = AudioManager.STREAM_MUSIC; 1038 1039 /** 1040 * @hide 1041 * Set information describing information related to the playback of media so the system 1042 * can implement additional behavior to handle non-local playback usecases. 1043 * @param what a key to specify the type of information to set. Valid keys are 1044 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 1045 * {@link #PLAYBACKINFO_USES_STREAM}, 1046 * {@link #PLAYBACKINFO_VOLUME}, 1047 * {@link #PLAYBACKINFO_VOLUME_MAX}, 1048 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 1049 * @param value the value for the supplied information to set. 1050 */ 1051 public void setPlaybackInformation(int what, int value) { 1052 synchronized(mCacheLock) { 1053 switch (what) { 1054 case PLAYBACKINFO_PLAYBACK_TYPE: 1055 if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) { 1056 if (mPlaybackType != value) { 1057 mPlaybackType = value; 1058 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 1059 } 1060 } else { 1061 Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE"); 1062 } 1063 break; 1064 case PLAYBACKINFO_VOLUME: 1065 if ((value > -1) && (value <= mPlaybackVolumeMax)) { 1066 if (mPlaybackVolume != value) { 1067 mPlaybackVolume = value; 1068 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 1069 } 1070 } else { 1071 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME"); 1072 } 1073 break; 1074 case PLAYBACKINFO_VOLUME_MAX: 1075 if (value > 0) { 1076 if (mPlaybackVolumeMax != value) { 1077 mPlaybackVolumeMax = value; 1078 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 1079 } 1080 } else { 1081 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX"); 1082 } 1083 break; 1084 case PLAYBACKINFO_USES_STREAM: 1085 if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) { 1086 mPlaybackStream = value; 1087 } else { 1088 Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM"); 1089 } 1090 break; 1091 case PLAYBACKINFO_VOLUME_HANDLING: 1092 if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) { 1093 if (mPlaybackVolumeHandling != value) { 1094 mPlaybackVolumeHandling = value; 1095 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 1096 } 1097 } else { 1098 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING"); 1099 } 1100 break; 1101 default: 1102 // not throwing an exception or returning an error if more keys are to be 1103 // supported in the future 1104 Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what); 1105 break; 1106 } 1107 } 1108 } 1109 1110 /** 1111 * @hide 1112 * Return playback information represented as an integer value. 1113 * @param what a key to specify the type of information to retrieve. Valid keys are 1114 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 1115 * {@link #PLAYBACKINFO_USES_STREAM}, 1116 * {@link #PLAYBACKINFO_VOLUME}, 1117 * {@link #PLAYBACKINFO_VOLUME_MAX}, 1118 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 1119 * @return the current value for the given information type, or 1120 * {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or 1121 * the value is unknown. 1122 */ 1123 public int getIntPlaybackInformation(int what) { 1124 synchronized(mCacheLock) { 1125 switch (what) { 1126 case PLAYBACKINFO_PLAYBACK_TYPE: 1127 return mPlaybackType; 1128 case PLAYBACKINFO_VOLUME: 1129 return mPlaybackVolume; 1130 case PLAYBACKINFO_VOLUME_MAX: 1131 return mPlaybackVolumeMax; 1132 case PLAYBACKINFO_USES_STREAM: 1133 return mPlaybackStream; 1134 case PLAYBACKINFO_VOLUME_HANDLING: 1135 return mPlaybackVolumeHandling; 1136 default: 1137 Log.e(TAG, "getIntPlaybackInformation() unknown key " + what); 1138 return PLAYBACKINFO_INVALID_VALUE; 1139 } 1140 } 1141 } 1142 1143 /** 1144 * Lock for all cached data 1145 */ 1146 private final Object mCacheLock = new Object(); 1147 /** 1148 * Cache for the playback state. 1149 * Access synchronized on mCacheLock 1150 */ 1151 private int mPlaybackState = PLAYSTATE_NONE; 1152 /** 1153 * Time of last play state change 1154 * Access synchronized on mCacheLock 1155 */ 1156 private long mPlaybackStateChangeTimeMs = 0; 1157 /** 1158 * Last playback position in ms reported by the user 1159 */ 1160 private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 1161 /** 1162 * Last playback speed reported by the user 1163 */ 1164 private float mPlaybackSpeed = PLAYBACK_SPEED_1X; 1165 /** 1166 * Cache for the artwork bitmap. 1167 * Access synchronized on mCacheLock 1168 * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be 1169 * accessed to be resized, in which case a copy will be made. This would add overhead in 1170 * Bundle operations. 1171 */ 1172 private Bitmap mOriginalArtwork; 1173 /** 1174 * Cache for the transport control mask. 1175 * Access synchronized on mCacheLock 1176 */ 1177 private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE; 1178 /** 1179 * Cache for the metadata strings. 1180 * Access synchronized on mCacheLock 1181 * This is re-initialized in apply() and so cannot be final. 1182 */ 1183 private Bundle mMetadata = new Bundle(); 1184 /** 1185 * Listener registered by user of RemoteControlClient to receive requests for playback position 1186 * update requests. 1187 */ 1188 private OnPlaybackPositionUpdateListener mPositionUpdateListener; 1189 /** 1190 * Provider registered by user of RemoteControlClient to provide the current playback position. 1191 */ 1192 private OnGetPlaybackPositionListener mPositionProvider; 1193 /** 1194 * Listener registered by user of RemoteControlClient to receive edit changes to metadata 1195 * it exposes. 1196 */ 1197 private OnMetadataUpdateListener mMetadataUpdateListener; 1198 /** 1199 * The current remote control client generation ID across the system, as known by this object 1200 */ 1201 private int mCurrentClientGenId = -1; 1202 /** 1203 * The remote control client generation ID, the last time it was told it was the current RC. 1204 * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control 1205 * client is the "focused" one, and that whenever this client's info is updated, it needs to 1206 * send it to the known IRemoteControlDisplay interfaces. 1207 */ 1208 private int mInternalClientGenId = -2; 1209 1210 /** 1211 * The media button intent description associated with this remote control client 1212 * (can / should include target component for intent handling, used when persisting media 1213 * button event receiver across reboots). 1214 */ 1215 private final PendingIntent mRcMediaIntent; 1216 1217 /** 1218 * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. 1219 */ 1220 // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead 1221 private boolean mNeedsPositionSync = false; 1222 1223 /** 1224 * A class to encapsulate all the information about a remote control display. 1225 * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay 1226 */ 1227 private class DisplayInfoForClient { 1228 /** may never be null */ 1229 private IRemoteControlDisplay mRcDisplay; 1230 private int mArtworkExpectedWidth; 1231 private int mArtworkExpectedHeight; 1232 private boolean mWantsPositionSync = false; 1233 1234 DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { 1235 mRcDisplay = rcd; 1236 mArtworkExpectedWidth = w; 1237 mArtworkExpectedHeight = h; 1238 } 1239 } 1240 1241 /** 1242 * The list of remote control displays to which this client will send information. 1243 * Accessed and modified synchronized on mCacheLock 1244 */ 1245 private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1); 1246 1247 /** 1248 * @hide 1249 * Accessor to media button intent description (includes target component) 1250 */ 1251 public PendingIntent getRcMediaIntent() { 1252 return mRcMediaIntent; 1253 } 1254 /** 1255 * @hide 1256 * Accessor to IRemoteControlClient 1257 */ 1258 public IRemoteControlClient getIRemoteControlClient() { 1259 return mIRCC; 1260 } 1261 1262 /** 1263 * The IRemoteControlClient implementation 1264 */ 1265 private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { 1266 1267 public void onInformationRequested(int generationId, int infoFlags) { 1268 // only post messages, we can't block here 1269 if (mEventHandler != null) { 1270 // signal new client 1271 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); 1272 mEventHandler.sendMessage( 1273 mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, 1274 /*arg1*/ generationId, /*arg2, ignored*/ 0)); 1275 // send the information 1276 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); 1277 mEventHandler.removeMessages(MSG_REQUEST_METADATA); 1278 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); 1279 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); 1280 mEventHandler.sendMessage( 1281 mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); 1282 mEventHandler.sendMessage( 1283 mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); 1284 mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); 1285 mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); 1286 } 1287 } 1288 1289 public void setCurrentClientGenerationId(int clientGeneration) { 1290 // only post messages, we can't block here 1291 if (mEventHandler != null) { 1292 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN); 1293 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1294 MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/)); 1295 } 1296 } 1297 1298 public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { 1299 // only post messages, we can't block here 1300 if ((mEventHandler != null) && (rcd != null)) { 1301 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1302 MSG_PLUG_DISPLAY, w, h, rcd)); 1303 } 1304 } 1305 1306 public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) { 1307 // only post messages, we can't block here 1308 if ((mEventHandler != null) && (rcd != null)) { 1309 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1310 MSG_UNPLUG_DISPLAY, rcd)); 1311 } 1312 } 1313 1314 public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) { 1315 // only post messages, we can't block here 1316 if ((mEventHandler != null) && (rcd != null)) { 1317 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1318 MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); 1319 } 1320 } 1321 1322 public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) { 1323 // only post messages, we can't block here 1324 if ((mEventHandler != null) && (rcd != null)) { 1325 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1326 MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd)); 1327 } 1328 } 1329 1330 public void seekTo(int generationId, long timeMs) { 1331 // only post messages, we can't block here 1332 if (mEventHandler != null) { 1333 mEventHandler.removeMessages(MSG_SEEK_TO); 1334 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1335 MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */, 1336 new Long(timeMs))); 1337 } 1338 } 1339 1340 public void updateMetadata(int generationId, int key, long value) { 1341 // only post messages, we can't block here 1342 if (mEventHandler != null) { 1343 mEventHandler.sendMessage(mEventHandler.obtainMessage( 1344 MSG_UPDATE_METADATA_LONG, generationId /* arg1 */, key /* arg2*/, 1345 new Long(value))); 1346 } 1347 } 1348 }; 1349 1350 /** 1351 * @hide 1352 * Default value for the unique identifier 1353 */ 1354 public final static int RCSE_ID_UNREGISTERED = -1; 1355 /** 1356 * Unique identifier of the RemoteControlStackEntry in AudioService with which 1357 * this RemoteControlClient is associated. 1358 */ 1359 private int mRcseId = RCSE_ID_UNREGISTERED; 1360 /** 1361 * @hide 1362 * To be only used by AudioManager after it has received the unique id from 1363 * IAudioService.registerRemoteControlClient() 1364 * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which 1365 * this RemoteControlClient is associated. 1366 */ 1367 public void setRcseId(int id) { 1368 mRcseId = id; 1369 } 1370 1371 /** 1372 * @hide 1373 */ 1374 public int getRcseId() { 1375 return mRcseId; 1376 } 1377 1378 private EventHandler mEventHandler; 1379 private final static int MSG_REQUEST_PLAYBACK_STATE = 1; 1380 private final static int MSG_REQUEST_METADATA = 2; 1381 private final static int MSG_REQUEST_TRANSPORTCONTROL = 3; 1382 private final static int MSG_REQUEST_ARTWORK = 4; 1383 private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5; 1384 private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6; 1385 private final static int MSG_PLUG_DISPLAY = 7; 1386 private final static int MSG_UNPLUG_DISPLAY = 8; 1387 private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; 1388 private final static int MSG_SEEK_TO = 10; 1389 private final static int MSG_POSITION_DRIFT_CHECK = 11; 1390 private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; 1391 private final static int MSG_UPDATE_METADATA_LONG = 13; 1392 1393 private class EventHandler extends Handler { 1394 public EventHandler(RemoteControlClient rcc, Looper looper) { 1395 super(looper); 1396 } 1397 1398 @Override 1399 public void handleMessage(Message msg) { 1400 switch(msg.what) { 1401 case MSG_REQUEST_PLAYBACK_STATE: 1402 synchronized (mCacheLock) { 1403 sendPlaybackState_syncCacheLock(); 1404 } 1405 break; 1406 case MSG_REQUEST_METADATA: 1407 synchronized (mCacheLock) { 1408 sendMetadata_syncCacheLock(); 1409 } 1410 break; 1411 case MSG_REQUEST_TRANSPORTCONTROL: 1412 synchronized (mCacheLock) { 1413 sendTransportControlInfo_syncCacheLock(); 1414 } 1415 break; 1416 case MSG_REQUEST_ARTWORK: 1417 synchronized (mCacheLock) { 1418 sendArtwork_syncCacheLock(); 1419 } 1420 break; 1421 case MSG_NEW_INTERNAL_CLIENT_GEN: 1422 onNewInternalClientGen(msg.arg1); 1423 break; 1424 case MSG_NEW_CURRENT_CLIENT_GEN: 1425 onNewCurrentClientGen(msg.arg1); 1426 break; 1427 case MSG_PLUG_DISPLAY: 1428 onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1429 break; 1430 case MSG_UNPLUG_DISPLAY: 1431 onUnplugDisplay((IRemoteControlDisplay)msg.obj); 1432 break; 1433 case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: 1434 onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1435 break; 1436 case MSG_SEEK_TO: 1437 onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); 1438 break; 1439 case MSG_POSITION_DRIFT_CHECK: 1440 onPositionDriftCheck(); 1441 break; 1442 case MSG_DISPLAY_WANTS_POS_SYNC: 1443 onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); 1444 break; 1445 case MSG_UPDATE_METADATA_LONG: 1446 onUpdateMetadata(msg.arg1, msg.arg2, ((Long)msg.obj).longValue()); 1447 break; 1448 default: 1449 Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); 1450 } 1451 } 1452 } 1453 1454 //=========================================================== 1455 // Communication with the IRemoteControlDisplay (the displays known to the system) 1456 1457 private void sendPlaybackState_syncCacheLock() { 1458 if (mCurrentClientGenId == mInternalClientGenId) { 1459 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1460 while (displayIterator.hasNext()) { 1461 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1462 try { 1463 di.mRcDisplay.setPlaybackState(mInternalClientGenId, 1464 mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, 1465 mPlaybackSpeed); 1466 } catch (RemoteException e) { 1467 Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); 1468 displayIterator.remove(); 1469 } 1470 } 1471 } 1472 } 1473 1474 private void sendMetadata_syncCacheLock() { 1475 if (mCurrentClientGenId == mInternalClientGenId) { 1476 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1477 while (displayIterator.hasNext()) { 1478 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1479 try { 1480 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1481 } catch (RemoteException e) { 1482 Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); 1483 displayIterator.remove(); 1484 } 1485 } 1486 } 1487 } 1488 1489 private void sendTransportControlInfo_syncCacheLock() { 1490 if (mCurrentClientGenId == mInternalClientGenId) { 1491 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1492 while (displayIterator.hasNext()) { 1493 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1494 try { 1495 di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, 1496 mTransportControlFlags, mPlaybackPositionCapabilities); 1497 } catch (RemoteException e) { 1498 Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, 1499 e); 1500 displayIterator.remove(); 1501 } 1502 } 1503 } 1504 } 1505 1506 private void sendArtwork_syncCacheLock() { 1507 // FIXME modify to cache all requested sizes? 1508 if (mCurrentClientGenId == mInternalClientGenId) { 1509 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1510 while (displayIterator.hasNext()) { 1511 if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { 1512 displayIterator.remove(); 1513 } 1514 } 1515 } 1516 } 1517 1518 /** 1519 * Send artwork to an IRemoteControlDisplay. 1520 * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its 1521 * dimension requirements. 1522 * @return false if there was an error communicating with the IRemoteControlDisplay. 1523 */ 1524 private boolean sendArtworkToDisplay(DisplayInfoForClient di) { 1525 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1526 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1527 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1528 try { 1529 di.mRcDisplay.setArtwork(mInternalClientGenId, artwork); 1530 } catch (RemoteException e) { 1531 Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e); 1532 return false; 1533 } 1534 } 1535 return true; 1536 } 1537 1538 private void sendMetadataWithArtwork_syncCacheLock() { 1539 // FIXME modify to cache all requested sizes? 1540 if (mCurrentClientGenId == mInternalClientGenId) { 1541 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1542 while (displayIterator.hasNext()) { 1543 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1544 try { 1545 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1546 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1547 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1548 di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); 1549 } else { 1550 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1551 } 1552 } catch (RemoteException e) { 1553 Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); 1554 displayIterator.remove(); 1555 } 1556 } 1557 } 1558 } 1559 1560 //=========================================================== 1561 // Communication with AudioService 1562 1563 private static IAudioService sService; 1564 1565 private static IAudioService getService() 1566 { 1567 if (sService != null) { 1568 return sService; 1569 } 1570 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 1571 sService = IAudioService.Stub.asInterface(b); 1572 return sService; 1573 } 1574 1575 private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) { 1576 if (mRcseId == RCSE_ID_UNREGISTERED) { 1577 return; 1578 } 1579 //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value); 1580 IAudioService service = getService(); 1581 try { 1582 service.setPlaybackInfoForRcc(mRcseId, what, value); 1583 } catch (RemoteException e) { 1584 Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); 1585 } 1586 } 1587 1588 private void sendAudioServiceNewPlaybackState_syncCacheLock() { 1589 if (mRcseId == RCSE_ID_UNREGISTERED) { 1590 return; 1591 } 1592 IAudioService service = getService(); 1593 try { 1594 service.setPlaybackStateForRcc(mRcseId, 1595 mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); 1596 } catch (RemoteException e) { 1597 Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); 1598 } 1599 } 1600 1601 //=========================================================== 1602 // Message handlers 1603 1604 private void onNewInternalClientGen(int clientGeneration) { 1605 synchronized (mCacheLock) { 1606 // this remote control client is told it is the "focused" one: 1607 // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true 1608 mInternalClientGenId = clientGeneration; 1609 } 1610 } 1611 1612 private void onNewCurrentClientGen(int clientGeneration) { 1613 synchronized (mCacheLock) { 1614 mCurrentClientGenId = clientGeneration; 1615 } 1616 } 1617 1618 /** pre-condition rcd != null */ 1619 private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) { 1620 synchronized(mCacheLock) { 1621 // do we have this display already? 1622 boolean displayKnown = false; 1623 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1624 while (displayIterator.hasNext() && !displayKnown) { 1625 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1626 displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder()); 1627 if (displayKnown) { 1628 // this display was known but the change in artwork size will cause the 1629 // artwork to be refreshed 1630 if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { 1631 di.mArtworkExpectedWidth = w; 1632 di.mArtworkExpectedHeight = h; 1633 if (!sendArtworkToDisplay(di)) { 1634 displayIterator.remove(); 1635 } 1636 } 1637 } 1638 } 1639 if (!displayKnown) { 1640 mRcDisplays.add(new DisplayInfoForClient(rcd, w, h)); 1641 } 1642 } 1643 } 1644 1645 /** pre-condition rcd != null */ 1646 private void onUnplugDisplay(IRemoteControlDisplay rcd) { 1647 synchronized(mCacheLock) { 1648 Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1649 while (displayIterator.hasNext()) { 1650 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1651 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1652 displayIterator.remove(); 1653 break; 1654 } 1655 } 1656 // list of RCDs has changed, reevaluate whether position check is still needed 1657 boolean oldNeedsPositionSync = mNeedsPositionSync; 1658 boolean newNeedsPositionSync = false; 1659 displayIterator = mRcDisplays.iterator(); 1660 while (displayIterator.hasNext()) { 1661 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1662 if (di.mWantsPositionSync) { 1663 newNeedsPositionSync = true; 1664 break; 1665 } 1666 } 1667 mNeedsPositionSync = newNeedsPositionSync; 1668 if (oldNeedsPositionSync != mNeedsPositionSync) { 1669 // update needed? 1670 initiateCheckForDrift_syncCacheLock(); 1671 } 1672 } 1673 } 1674 1675 /** pre-condition rcd != null */ 1676 private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) { 1677 synchronized(mCacheLock) { 1678 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1679 while (displayIterator.hasNext()) { 1680 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1681 if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) && 1682 ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { 1683 di.mArtworkExpectedWidth = w; 1684 di.mArtworkExpectedHeight = h; 1685 if (!sendArtworkToDisplay(di)) { 1686 displayIterator.remove(); 1687 } 1688 break; 1689 } 1690 } 1691 } 1692 } 1693 1694 /** pre-condition rcd != null */ 1695 private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) { 1696 synchronized(mCacheLock) { 1697 boolean oldNeedsPositionSync = mNeedsPositionSync; 1698 boolean newNeedsPositionSync = false; 1699 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1700 // go through the list of RCDs and for each entry, check both whether this is the RCD 1701 // that gets upated, and whether the list has one entry that wants position sync 1702 while (displayIterator.hasNext()) { 1703 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1704 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1705 di.mWantsPositionSync = wantsSync; 1706 } 1707 if (di.mWantsPositionSync) { 1708 newNeedsPositionSync = true; 1709 } 1710 } 1711 mNeedsPositionSync = newNeedsPositionSync; 1712 if (oldNeedsPositionSync != mNeedsPositionSync) { 1713 // update needed? 1714 initiateCheckForDrift_syncCacheLock(); 1715 } 1716 } 1717 } 1718 1719 private void onSeekTo(int generationId, long timeMs) { 1720 synchronized (mCacheLock) { 1721 if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { 1722 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); 1723 } 1724 } 1725 } 1726 1727 private void onUpdateMetadata(int generationId, int key, long value) { 1728 synchronized (mCacheLock) { 1729 if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { 1730 mMetadataUpdateListener.onMetadataUpdateLong(key, value); 1731 } 1732 } 1733 } 1734 1735 //=========================================================== 1736 // Internal utilities 1737 1738 /** 1739 * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap. 1740 * If the bitmap fits, then do nothing and return the original. 1741 * 1742 * @param bitmap 1743 * @param maxWidth 1744 * @param maxHeight 1745 * @return 1746 */ 1747 1748 private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) { 1749 if (bitmap != null) { 1750 final int width = bitmap.getWidth(); 1751 final int height = bitmap.getHeight(); 1752 if (width > maxWidth || height > maxHeight) { 1753 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); 1754 int newWidth = Math.round(scale * width); 1755 int newHeight = Math.round(scale * height); 1756 Bitmap.Config newConfig = bitmap.getConfig(); 1757 if (newConfig == null) { 1758 newConfig = Bitmap.Config.ARGB_8888; 1759 } 1760 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig); 1761 Canvas canvas = new Canvas(outBitmap); 1762 Paint paint = new Paint(); 1763 paint.setAntiAlias(true); 1764 paint.setFilterBitmap(true); 1765 canvas.drawBitmap(bitmap, null, 1766 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 1767 bitmap = outBitmap; 1768 } 1769 } 1770 return bitmap; 1771 } 1772 1773 /** 1774 * Fast routine to go through an array of allowed keys and return whether the key is part 1775 * of that array 1776 * @param key the key value 1777 * @param validKeys the array of valid keys for a given type 1778 * @return true if the key is part of the array, false otherwise 1779 */ 1780 private static boolean validTypeForKey(int key, int[] validKeys) { 1781 try { 1782 for (int i = 0 ; ; i++) { 1783 if (key == validKeys[i]) { 1784 return true; 1785 } 1786 } 1787 } catch (ArrayIndexOutOfBoundsException e) { 1788 return false; 1789 } 1790 } 1791 1792 /** 1793 * Returns whether, for the given playback state, the playback position is expected to 1794 * be changing. 1795 * @param playstate the playback state to evaluate 1796 * @return true during any form of playback, false if it's not playing anything while in this 1797 * playback state 1798 */ 1799 private static boolean playbackPositionShouldMove(int playstate) { 1800 switch(playstate) { 1801 case PLAYSTATE_STOPPED: 1802 case PLAYSTATE_PAUSED: 1803 case PLAYSTATE_BUFFERING: 1804 case PLAYSTATE_ERROR: 1805 case PLAYSTATE_SKIPPING_FORWARDS: 1806 case PLAYSTATE_SKIPPING_BACKWARDS: 1807 return false; 1808 case PLAYSTATE_PLAYING: 1809 case PLAYSTATE_FAST_FORWARDING: 1810 case PLAYSTATE_REWINDING: 1811 default: 1812 return true; 1813 } 1814 } 1815 1816 /** 1817 * Period for playback position drift checks, 15s when playing at 1x or slower. 1818 */ 1819 private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; 1820 /** 1821 * Minimum period for playback position drift checks, never more often when every 2s, when 1822 * fast forwarding or rewinding. 1823 */ 1824 private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; 1825 /** 1826 * The value above which the difference between client-reported playback position and 1827 * estimated position is considered a drift. 1828 */ 1829 private final static long POSITION_DRIFT_MAX_MS = 500; 1830 /** 1831 * Compute the period at which the estimated playback position should be compared against the 1832 * actual playback position. Is a funciton of playback speed. 1833 * @param speed 1.0f is normal playback speed 1834 * @return the period in ms 1835 */ 1836 private static long getCheckPeriodFromSpeed(float speed) { 1837 if (Math.abs(speed) <= 1.0f) { 1838 return POSITION_REFRESH_PERIOD_PLAYING_MS; 1839 } else { 1840 return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)), 1841 POSITION_REFRESH_PERIOD_MIN_MS); 1842 } 1843 } 1844} 1845