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