RemoteControlClient.java revision bc43b4c2f24fd03c0d0546895c97918c1736d9fb
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 69 /** 70 * Playback state of a RemoteControlClient which is stopped. 71 * 72 * @see #setPlaybackState(int) 73 */ 74 public final static int PLAYSTATE_STOPPED = 1; 75 /** 76 * Playback state of a RemoteControlClient which is paused. 77 * 78 * @see #setPlaybackState(int) 79 */ 80 public final static int PLAYSTATE_PAUSED = 2; 81 /** 82 * Playback state of a RemoteControlClient which is playing media. 83 * 84 * @see #setPlaybackState(int) 85 */ 86 public final static int PLAYSTATE_PLAYING = 3; 87 /** 88 * Playback state of a RemoteControlClient which is fast forwarding in the media 89 * it is currently playing. 90 * 91 * @see #setPlaybackState(int) 92 */ 93 public final static int PLAYSTATE_FAST_FORWARDING = 4; 94 /** 95 * Playback state of a RemoteControlClient which is fast rewinding in the media 96 * it is currently playing. 97 * 98 * @see #setPlaybackState(int) 99 */ 100 public final static int PLAYSTATE_REWINDING = 5; 101 /** 102 * Playback state of a RemoteControlClient which is skipping to the next 103 * logical chapter (such as a song in a playlist) in the media it is currently playing. 104 * 105 * @see #setPlaybackState(int) 106 */ 107 public final static int PLAYSTATE_SKIPPING_FORWARDS = 6; 108 /** 109 * Playback state of a RemoteControlClient which is skipping back to the previous 110 * logical chapter (such as a song in a playlist) in the media it is currently playing. 111 * 112 * @see #setPlaybackState(int) 113 */ 114 public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7; 115 /** 116 * Playback state of a RemoteControlClient which is buffering data to play before it can 117 * start or resume playback. 118 * 119 * @see #setPlaybackState(int) 120 */ 121 public final static int PLAYSTATE_BUFFERING = 8; 122 /** 123 * Playback state of a RemoteControlClient which cannot perform any playback related 124 * operation because of an internal error. Examples of such situations are no network 125 * connectivity when attempting to stream data from a server, or expired user credentials 126 * when trying to play subscription-based content. 127 * 128 * @see #setPlaybackState(int) 129 */ 130 public final static int PLAYSTATE_ERROR = 9; 131 /** 132 * @hide 133 * The value of a playback state when none has been declared. 134 * Intentionally hidden as an application shouldn't set such a playback state value. 135 */ 136 public final static int PLAYSTATE_NONE = 0; 137 138 /** 139 * @hide 140 * The default playback type, "local", indicating the presentation of the media is happening on 141 * the same device (e.g. a phone, a tablet) as where it is controlled from. 142 */ 143 public final static int PLAYBACK_TYPE_LOCAL = 0; 144 /** 145 * @hide 146 * A playback type indicating the presentation of the media is happening on 147 * a different device (i.e. the remote device) than where it is controlled from. 148 */ 149 public final static int PLAYBACK_TYPE_REMOTE = 1; 150 private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL; 151 private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE; 152 /** 153 * @hide 154 * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled 155 * from this object. An example of fixed playback volume is a remote player, playing over HDMI 156 * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the 157 * source. 158 * @see #PLAYBACKINFO_VOLUME_HANDLING. 159 */ 160 public final static int PLAYBACK_VOLUME_FIXED = 0; 161 /** 162 * @hide 163 * Playback information indicating the playback volume is variable and can be controlled from 164 * this object. 165 * @see #PLAYBACKINFO_VOLUME_HANDLING. 166 */ 167 public final static int PLAYBACK_VOLUME_VARIABLE = 1; 168 /** 169 * @hide (to be un-hidden) 170 * The playback information value indicating the value of a given information type is invalid. 171 * @see #PLAYBACKINFO_VOLUME_HANDLING. 172 */ 173 public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; 174 175 /** 176 * @hide 177 * An unknown or invalid playback position value. 178 */ 179 public final static long PLAYBACK_POSITION_INVALID = -1; 180 /** 181 * @hide 182 * The default playback speed, 1x. 183 */ 184 public final static float PLAYBACK_SPEED_1X = 1.0f; 185 186 //========================================== 187 // Public keys for playback information 188 /** 189 * @hide 190 * Playback information that defines the type of playback associated with this 191 * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}. 192 */ 193 public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1; 194 /** 195 * @hide 196 * Playback information that defines at what volume the playback associated with this 197 * RemoteControlClient is performed. This information is only used when the playback type is not 198 * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 199 */ 200 public final static int PLAYBACKINFO_VOLUME = 2; 201 /** 202 * @hide 203 * Playback information that defines the maximum volume volume value that is supported 204 * by the playback associated with this RemoteControlClient. This information is only used 205 * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). 206 */ 207 public final static int PLAYBACKINFO_VOLUME_MAX = 3; 208 /** 209 * @hide 210 * Playback information that defines how volume is handled for the presentation of the media. 211 * @see #PLAYBACK_VOLUME_FIXED 212 * @see #PLAYBACK_VOLUME_VARIABLE 213 */ 214 public final static int PLAYBACKINFO_VOLUME_HANDLING = 4; 215 /** 216 * @hide 217 * Playback information that defines over what stream type the media is presented. 218 */ 219 public final static int PLAYBACKINFO_USES_STREAM = 5; 220 221 //========================================== 222 // Public flags for the supported transport control capabililities 223 /** 224 * Flag indicating a RemoteControlClient makes use of the "previous" media key. 225 * 226 * @see #setTransportControlFlags(int) 227 * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS 228 */ 229 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 230 /** 231 * Flag indicating a RemoteControlClient makes use of the "rewind" media key. 232 * 233 * @see #setTransportControlFlags(int) 234 * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND 235 */ 236 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 237 /** 238 * Flag indicating a RemoteControlClient makes use of the "play" media key. 239 * 240 * @see #setTransportControlFlags(int) 241 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY 242 */ 243 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 244 /** 245 * Flag indicating a RemoteControlClient makes use of the "play/pause" media key. 246 * 247 * @see #setTransportControlFlags(int) 248 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE 249 */ 250 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 251 /** 252 * Flag indicating a RemoteControlClient makes use of the "pause" media key. 253 * 254 * @see #setTransportControlFlags(int) 255 * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE 256 */ 257 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 258 /** 259 * Flag indicating a RemoteControlClient makes use of the "stop" media key. 260 * 261 * @see #setTransportControlFlags(int) 262 * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP 263 */ 264 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 265 /** 266 * Flag indicating a RemoteControlClient makes use of the "fast forward" media key. 267 * 268 * @see #setTransportControlFlags(int) 269 * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD 270 */ 271 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 272 /** 273 * Flag indicating a RemoteControlClient makes use of the "next" media key. 274 * 275 * @see #setTransportControlFlags(int) 276 * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT 277 */ 278 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 279 /** 280 * @hide 281 * (to be un-hidden and added in javadoc of setTransportControlFlags(int)) 282 * Flag indicating a RemoteControlClient can receive changes in the media playback position 283 * through the {@link #OnPlaybackPositionUpdateListener} interface. 284 * 285 * @see #setTransportControlFlags(int) 286 */ 287 public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; 288 289 /** 290 * @hide 291 * The flags for when no media keys are declared supported. 292 * Intentionally hidden as an application shouldn't set the transport control flags 293 * to this value. 294 */ 295 public final static int FLAGS_KEY_MEDIA_NONE = 0; 296 297 /** 298 * @hide 299 * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. 300 */ 301 public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0; 302 /** 303 * @hide 304 * Flag used to signal that the transport control buttons supported by the 305 * RemoteControlClient are requested. 306 * This can for instance happen when playback is at the end of a playlist, and the "next" 307 * operation is not supported anymore. 308 */ 309 public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1; 310 /** 311 * @hide 312 * Flag used to signal that the playback state of the RemoteControlClient is requested. 313 */ 314 public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2; 315 /** 316 * @hide 317 * Flag used to signal that the album art for the RemoteControlClient is requested. 318 */ 319 public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; 320 321 /** 322 * Class constructor. 323 * @param mediaButtonIntent The intent that will be sent for the media button events sent 324 * by remote controls. 325 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 326 * action, and have a component that will handle the intent (set with 327 * {@link Intent#setComponent(ComponentName)}) registered with 328 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 329 * before this new RemoteControlClient can itself be registered with 330 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 331 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 332 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 333 */ 334 public RemoteControlClient(PendingIntent mediaButtonIntent) { 335 mRcMediaIntent = mediaButtonIntent; 336 337 Looper looper; 338 if ((looper = Looper.myLooper()) != null) { 339 mEventHandler = new EventHandler(this, looper); 340 } else if ((looper = Looper.getMainLooper()) != null) { 341 mEventHandler = new EventHandler(this, looper); 342 } else { 343 mEventHandler = null; 344 Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); 345 } 346 } 347 348 /** 349 * Class constructor for a remote control client whose internal event handling 350 * happens on a user-provided Looper. 351 * @param mediaButtonIntent The intent that will be sent for the media button events sent 352 * by remote controls. 353 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 354 * action, and have a component that will handle the intent (set with 355 * {@link Intent#setComponent(ComponentName)}) registered with 356 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 357 * before this new RemoteControlClient can itself be registered with 358 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 359 * @param looper The Looper running the event loop. 360 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 361 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 362 */ 363 public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { 364 mRcMediaIntent = mediaButtonIntent; 365 366 mEventHandler = new EventHandler(this, looper); 367 } 368 369 private static final int[] METADATA_KEYS_TYPE_STRING = { 370 MediaMetadataRetriever.METADATA_KEY_ALBUM, 371 MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, 372 MediaMetadataRetriever.METADATA_KEY_TITLE, 373 MediaMetadataRetriever.METADATA_KEY_ARTIST, 374 MediaMetadataRetriever.METADATA_KEY_AUTHOR, 375 MediaMetadataRetriever.METADATA_KEY_COMPILATION, 376 MediaMetadataRetriever.METADATA_KEY_COMPOSER, 377 MediaMetadataRetriever.METADATA_KEY_DATE, 378 MediaMetadataRetriever.METADATA_KEY_GENRE, 379 MediaMetadataRetriever.METADATA_KEY_TITLE, 380 MediaMetadataRetriever.METADATA_KEY_WRITER }; 381 private static final int[] METADATA_KEYS_TYPE_LONG = { 382 MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, 383 MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, 384 MediaMetadataRetriever.METADATA_KEY_DURATION }; 385 386 /** 387 * Class used to modify metadata in a {@link RemoteControlClient} object. 388 * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, 389 * on which you set the metadata for the RemoteControlClient instance. Once all the information 390 * has been set, use {@link #apply()} to make it the new metadata that should be displayed 391 * for the associated client. Once the metadata has been "applied", you cannot reuse this 392 * instance of the MetadataEditor. 393 */ 394 public class MetadataEditor { 395 /** 396 * @hide 397 */ 398 protected boolean mMetadataChanged; 399 /** 400 * @hide 401 */ 402 protected boolean mArtworkChanged; 403 /** 404 * @hide 405 */ 406 protected Bitmap mEditorArtwork; 407 /** 408 * @hide 409 */ 410 protected Bundle mEditorMetadata; 411 private boolean mApplied = false; 412 413 // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance 414 private MetadataEditor() { } 415 /** 416 * @hide 417 */ 418 public Object clone() throws CloneNotSupportedException { 419 throw new CloneNotSupportedException(); 420 } 421 422 /** 423 * The metadata key for the content artwork / album art. 424 */ 425 public final static int BITMAP_KEY_ARTWORK = 100; 426 /** 427 * @hide 428 * TODO(jmtrivi) have lockscreen and music move to the new key name 429 */ 430 public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; 431 432 /** 433 * Adds textual information to be displayed. 434 * Note that none of the information added after {@link #apply()} has been called, 435 * will be displayed. 436 * @param key The identifier of a the metadata field to set. Valid values are 437 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, 438 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, 439 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 440 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, 441 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, 442 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, 443 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, 444 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, 445 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, 446 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 447 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. 448 * @param value The text for the given key, or {@code null} to signify there is no valid 449 * information for the field. 450 * @return Returns a reference to the same MetadataEditor object, so you can chain put 451 * calls together. 452 */ 453 public synchronized MetadataEditor putString(int key, String value) 454 throws IllegalArgumentException { 455 if (mApplied) { 456 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 457 return this; 458 } 459 if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { 460 throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); 461 } 462 mEditorMetadata.putString(String.valueOf(key), value); 463 mMetadataChanged = true; 464 return this; 465 } 466 467 /** 468 * Adds numerical information to be displayed. 469 * Note that none of the information added after {@link #apply()} has been called, 470 * will be displayed. 471 * @param key the identifier of a the metadata field to set. Valid values are 472 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, 473 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, 474 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value 475 * expressed in milliseconds), 476 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. 477 * @param value The long value for the given key 478 * @return Returns a reference to the same MetadataEditor object, so you can chain put 479 * calls together. 480 * @throws IllegalArgumentException 481 */ 482 public synchronized MetadataEditor putLong(int key, long value) 483 throws IllegalArgumentException { 484 if (mApplied) { 485 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 486 return this; 487 } 488 if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { 489 throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); 490 } 491 mEditorMetadata.putLong(String.valueOf(key), value); 492 mMetadataChanged = true; 493 return this; 494 } 495 496 /** 497 * Sets the album / artwork picture to be displayed on the remote control. 498 * @param key the identifier of the bitmap to set. The only valid value is 499 * {@link #BITMAP_KEY_ARTWORK} 500 * @param bitmap The bitmap for the artwork, or null if there isn't any. 501 * @return Returns a reference to the same MetadataEditor object, so you can chain put 502 * calls together. 503 * @throws IllegalArgumentException 504 * @see android.graphics.Bitmap 505 */ 506 public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) 507 throws IllegalArgumentException { 508 if (mApplied) { 509 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 510 return this; 511 } 512 if (key != BITMAP_KEY_ARTWORK) { 513 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); 514 } 515 mEditorArtwork = bitmap; 516 mArtworkChanged = true; 517 return this; 518 } 519 520 /** 521 * Clears all the metadata that has been set since the MetadataEditor instance was 522 * created with {@link RemoteControlClient#editMetadata(boolean)}. 523 */ 524 public synchronized void clear() { 525 if (mApplied) { 526 Log.e(TAG, "Can't clear a previously applied MetadataEditor"); 527 return; 528 } 529 mEditorMetadata.clear(); 530 mEditorArtwork = null; 531 } 532 533 /** 534 * Associates all the metadata that has been set since the MetadataEditor instance was 535 * created with {@link RemoteControlClient#editMetadata(boolean)}, or since 536 * {@link #clear()} was called, with the RemoteControlClient. Once "applied", 537 * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. 538 */ 539 public synchronized void apply() { 540 if (mApplied) { 541 Log.e(TAG, "Can't apply a previously applied MetadataEditor"); 542 return; 543 } 544 synchronized(mCacheLock) { 545 // assign the edited data 546 mMetadata = new Bundle(mEditorMetadata); 547 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { 548 mOriginalArtwork.recycle(); 549 } 550 mOriginalArtwork = mEditorArtwork; 551 mEditorArtwork = null; 552 if (mMetadataChanged & mArtworkChanged) { 553 // send to remote control display if conditions are met 554 sendMetadataWithArtwork_syncCacheLock(); 555 } else if (mMetadataChanged) { 556 // send to remote control display if conditions are met 557 sendMetadata_syncCacheLock(); 558 } else if (mArtworkChanged) { 559 // send to remote control display if conditions are met 560 sendArtwork_syncCacheLock(); 561 } 562 mApplied = true; 563 } 564 } 565 } 566 567 /** 568 * Creates a {@link MetadataEditor}. 569 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that 570 * was previously applied to the RemoteControlClient, or true if it is to be created empty. 571 * @return a new MetadataEditor instance. 572 */ 573 public MetadataEditor editMetadata(boolean startEmpty) { 574 MetadataEditor editor = new MetadataEditor(); 575 if (startEmpty) { 576 editor.mEditorMetadata = new Bundle(); 577 editor.mEditorArtwork = null; 578 editor.mMetadataChanged = true; 579 editor.mArtworkChanged = true; 580 } else { 581 editor.mEditorMetadata = new Bundle(mMetadata); 582 editor.mEditorArtwork = mOriginalArtwork; 583 editor.mMetadataChanged = false; 584 editor.mArtworkChanged = false; 585 } 586 return editor; 587 } 588 589 /** 590 * Sets the current playback state. 591 * @param state The current playback state, one of the following values: 592 * {@link #PLAYSTATE_STOPPED}, 593 * {@link #PLAYSTATE_PAUSED}, 594 * {@link #PLAYSTATE_PLAYING}, 595 * {@link #PLAYSTATE_FAST_FORWARDING}, 596 * {@link #PLAYSTATE_REWINDING}, 597 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 598 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 599 * {@link #PLAYSTATE_BUFFERING}, 600 * {@link #PLAYSTATE_ERROR}. 601 */ 602 public void setPlaybackState(int state) { 603 setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X); 604 } 605 606 /** 607 * @hide 608 * (to be un-hidden) 609 * Sets the current playback state and the matching media position for the current playback 610 * speed. 611 * @param state The current playback state, one of the following values: 612 * {@link #PLAYSTATE_STOPPED}, 613 * {@link #PLAYSTATE_PAUSED}, 614 * {@link #PLAYSTATE_PLAYING}, 615 * {@link #PLAYSTATE_FAST_FORWARDING}, 616 * {@link #PLAYSTATE_REWINDING}, 617 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 618 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 619 * {@link #PLAYSTATE_BUFFERING}, 620 * {@link #PLAYSTATE_ERROR}. 621 * @param timeInMs a 0 or positive value for the current media position expressed in ms 622 * (same unit as for when sending the media duration, if applicable, with 623 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the 624 * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not 625 * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state 626 * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). 627 * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 628 * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 629 * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). 630 */ 631 public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { 632 synchronized(mCacheLock) { 633 if (timeInMs != PLAYBACK_POSITION_INVALID) { 634 mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; 635 } else { 636 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; 637 } 638 if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) 639 || (mPlaybackSpeed != playbackSpeed)) { 640 // store locally 641 mPlaybackState = state; 642 mPlaybackPositionMs = timeInMs; 643 mPlaybackSpeed = playbackSpeed; 644 // keep track of when the state change occurred 645 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); 646 647 // send to remote control display if conditions are met 648 sendPlaybackState_syncCacheLock(); 649 // update AudioService 650 sendAudioServiceNewPlaybackState_syncCacheLock(); 651 } 652 } 653 } 654 655 /** 656 * Sets the flags for the media transport control buttons that this client supports. 657 * @param transportControlFlags A combination of the following flags: 658 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 659 * {@link #FLAG_KEY_MEDIA_REWIND}, 660 * {@link #FLAG_KEY_MEDIA_PLAY}, 661 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 662 * {@link #FLAG_KEY_MEDIA_PAUSE}, 663 * {@link #FLAG_KEY_MEDIA_STOP}, 664 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 665 * {@link #FLAG_KEY_MEDIA_NEXT} 666 */ 667 public void setTransportControlFlags(int transportControlFlags) { 668 synchronized(mCacheLock) { 669 // store locally 670 mTransportControlFlags = transportControlFlags; 671 672 // send to remote control display if conditions are met 673 sendTransportControlFlags_syncCacheLock(); 674 } 675 } 676 677 /** 678 * @hide 679 * (to be un-hidden) 680 * Interface definition for a callback to be invoked when the media playback position is 681 * requested to be updated. 682 */ 683 public interface OnPlaybackPositionUpdateListener { 684 /** 685 * Called on the listener to notify it that the playback head should be set at the given 686 * position. If the position can be changed from its current value, the implementor of 687 * the interface should also update the playback position using 688 * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new 689 * position being used, regardless of whether it differs from the requested position. 690 * @param newPositionMs the new requested position in the current media, expressed in ms. 691 */ 692 void onPlaybackPositionUpdate(long newPositionMs); 693 } 694 695 /** 696 * @hide 697 * (to be un-hidden) 698 * Sets the listener RemoteControlClient calls whenever the media playback position is requested 699 * to be updated. 700 * Notifications will be received in the same thread as the one in which RemoteControlClient 701 * was created. 702 * @param l 703 */ 704 public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { 705 synchronized(mCacheLock) { 706 if ((mPositionUpdateListener == null) && (l != null)) { 707 mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; 708 // tell RCDs and AudioService this RCC accepts position updates 709 // TODO implement 710 } else if ((mPositionUpdateListener != null) && (l == null)) { 711 mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; 712 // tell RCDs and AudioService this RCC doesn't handle position updates 713 // TODO implement 714 } 715 mPositionUpdateListener = l; 716 } 717 } 718 719 /** 720 * @hide 721 * Flag to reflect that the application controlling this RemoteControlClient sends playback 722 * position updates. The playback position being "readable" is considered from the application's 723 * point of view. 724 */ 725 public static int MEDIA_POSITION_READABLE = 1 << 0; 726 /** 727 * @hide 728 * Flag to reflect that the application controlling this RemoteControlClient can receive 729 * playback position updates. The playback position being "writable" 730 * is considered from the application's point of view. 731 */ 732 public static int MEDIA_POSITION_WRITABLE = 1 << 1; 733 734 private int mPlaybackPositionCapabilities = 0; 735 736 /** @hide */ 737 public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; 738 /** @hide */ 739 // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] 740 public final static int DEFAULT_PLAYBACK_VOLUME = 15; 741 742 private int mPlaybackType = PLAYBACK_TYPE_LOCAL; 743 private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME; 744 private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME; 745 private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING; 746 private int mPlaybackStream = AudioManager.STREAM_MUSIC; 747 748 /** 749 * @hide 750 * Set information describing information related to the playback of media so the system 751 * can implement additional behavior to handle non-local playback usecases. 752 * @param what a key to specify the type of information to set. Valid keys are 753 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 754 * {@link #PLAYBACKINFO_USES_STREAM}, 755 * {@link #PLAYBACKINFO_VOLUME}, 756 * {@link #PLAYBACKINFO_VOLUME_MAX}, 757 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 758 * @param value the value for the supplied information to set. 759 */ 760 public void setPlaybackInformation(int what, int value) { 761 synchronized(mCacheLock) { 762 switch (what) { 763 case PLAYBACKINFO_PLAYBACK_TYPE: 764 if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) { 765 if (mPlaybackType != value) { 766 mPlaybackType = value; 767 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 768 } 769 } else { 770 Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE"); 771 } 772 break; 773 case PLAYBACKINFO_VOLUME: 774 if ((value > -1) && (value <= mPlaybackVolumeMax)) { 775 if (mPlaybackVolume != value) { 776 mPlaybackVolume = value; 777 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 778 } 779 } else { 780 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME"); 781 } 782 break; 783 case PLAYBACKINFO_VOLUME_MAX: 784 if (value > 0) { 785 if (mPlaybackVolumeMax != value) { 786 mPlaybackVolumeMax = value; 787 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 788 } 789 } else { 790 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX"); 791 } 792 break; 793 case PLAYBACKINFO_USES_STREAM: 794 if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) { 795 mPlaybackStream = value; 796 } else { 797 Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM"); 798 } 799 break; 800 case PLAYBACKINFO_VOLUME_HANDLING: 801 if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) { 802 if (mPlaybackVolumeHandling != value) { 803 mPlaybackVolumeHandling = value; 804 sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value); 805 } 806 } else { 807 Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING"); 808 } 809 break; 810 default: 811 // not throwing an exception or returning an error if more keys are to be 812 // supported in the future 813 Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what); 814 break; 815 } 816 } 817 } 818 819 /** 820 * @hide 821 * Return playback information represented as an integer value. 822 * @param what a key to specify the type of information to retrieve. Valid keys are 823 * {@link #PLAYBACKINFO_PLAYBACK_TYPE}, 824 * {@link #PLAYBACKINFO_USES_STREAM}, 825 * {@link #PLAYBACKINFO_VOLUME}, 826 * {@link #PLAYBACKINFO_VOLUME_MAX}, 827 * and {@link #PLAYBACKINFO_VOLUME_HANDLING}. 828 * @return the current value for the given information type, or 829 * {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or 830 * the value is unknown. 831 */ 832 public int getIntPlaybackInformation(int what) { 833 synchronized(mCacheLock) { 834 switch (what) { 835 case PLAYBACKINFO_PLAYBACK_TYPE: 836 return mPlaybackType; 837 case PLAYBACKINFO_VOLUME: 838 return mPlaybackVolume; 839 case PLAYBACKINFO_VOLUME_MAX: 840 return mPlaybackVolumeMax; 841 case PLAYBACKINFO_USES_STREAM: 842 return mPlaybackStream; 843 case PLAYBACKINFO_VOLUME_HANDLING: 844 return mPlaybackVolumeHandling; 845 default: 846 Log.e(TAG, "getIntPlaybackInformation() unknown key " + what); 847 return PLAYBACKINFO_INVALID_VALUE; 848 } 849 } 850 } 851 852 /** 853 * Lock for all cached data 854 */ 855 private final Object mCacheLock = new Object(); 856 /** 857 * Cache for the playback state. 858 * Access synchronized on mCacheLock 859 */ 860 private int mPlaybackState = PLAYSTATE_NONE; 861 /** 862 * Time of last play state change 863 * Access synchronized on mCacheLock 864 */ 865 private long mPlaybackStateChangeTimeMs = 0; 866 /** 867 * Last playback position in ms reported by the user 868 */ 869 private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; 870 /** 871 * Last playback speed reported by the user 872 */ 873 private float mPlaybackSpeed = PLAYBACK_SPEED_1X; 874 /** 875 * Cache for the artwork bitmap. 876 * Access synchronized on mCacheLock 877 * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be 878 * accessed to be resized, in which case a copy will be made. This would add overhead in 879 * Bundle operations. 880 */ 881 private Bitmap mOriginalArtwork; 882 /** 883 * Cache for the transport control mask. 884 * Access synchronized on mCacheLock 885 */ 886 private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE; 887 /** 888 * Cache for the metadata strings. 889 * Access synchronized on mCacheLock 890 * This is re-initialized in apply() and so cannot be final. 891 */ 892 private Bundle mMetadata = new Bundle(); 893 /** 894 * Listener registered by user of RemoteControlClient to receive requests for playback position 895 * update requests. 896 */ 897 private OnPlaybackPositionUpdateListener mPositionUpdateListener; 898 /** 899 * The current remote control client generation ID across the system, as known by this object 900 */ 901 private int mCurrentClientGenId = -1; 902 /** 903 * The remote control client generation ID, the last time it was told it was the current RC. 904 * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control 905 * client is the "focused" one, and that whenever this client's info is updated, it needs to 906 * send it to the known IRemoteControlDisplay interfaces. 907 */ 908 private int mInternalClientGenId = -2; 909 910 /** 911 * The media button intent description associated with this remote control client 912 * (can / should include target component for intent handling, used when persisting media 913 * button event receiver across reboots). 914 */ 915 private final PendingIntent mRcMediaIntent; 916 917 /** 918 * A class to encapsulate all the information about a remote control display. 919 * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay 920 */ 921 private class DisplayInfoForClient { 922 /** may never be null */ 923 private IRemoteControlDisplay mRcDisplay; 924 private int mArtworkExpectedWidth; 925 private int mArtworkExpectedHeight; 926 927 DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { 928 mRcDisplay = rcd; 929 mArtworkExpectedWidth = w; 930 mArtworkExpectedHeight = h; 931 } 932 } 933 934 /** 935 * The list of remote control displays to which this client will send information. 936 * Accessed and modified synchronized on mCacheLock 937 */ 938 private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1); 939 940 /** 941 * @hide 942 * Accessor to media button intent description (includes target component) 943 */ 944 public PendingIntent getRcMediaIntent() { 945 return mRcMediaIntent; 946 } 947 /** 948 * @hide 949 * Accessor to IRemoteControlClient 950 */ 951 public IRemoteControlClient getIRemoteControlClient() { 952 return mIRCC; 953 } 954 955 /** 956 * The IRemoteControlClient implementation 957 */ 958 private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { 959 960 public void onInformationRequested(int clientGeneration, int infoFlags) { 961 // only post messages, we can't block here 962 if (mEventHandler != null) { 963 // signal new client 964 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); 965 mEventHandler.dispatchMessage( 966 mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, 967 /*arg1*/ clientGeneration, /*arg2, ignored*/ 0)); 968 // send the information 969 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); 970 mEventHandler.removeMessages(MSG_REQUEST_METADATA); 971 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); 972 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); 973 mEventHandler.dispatchMessage( 974 mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); 975 mEventHandler.dispatchMessage( 976 mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); 977 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); 978 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); 979 } 980 } 981 982 public void setCurrentClientGenerationId(int clientGeneration) { 983 // only post messages, we can't block here 984 if (mEventHandler != null) { 985 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN); 986 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 987 MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/)); 988 } 989 } 990 991 public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { 992 // only post messages, we can't block here 993 if ((mEventHandler != null) && (rcd != null)) { 994 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 995 MSG_PLUG_DISPLAY, w, h, rcd)); 996 } 997 } 998 999 public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) { 1000 // only post messages, we can't block here 1001 if ((mEventHandler != null) && (rcd != null)) { 1002 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 1003 MSG_UNPLUG_DISPLAY, rcd)); 1004 } 1005 } 1006 1007 public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) { 1008 // only post messages, we can't block here 1009 if ((mEventHandler != null) && (rcd != null)) { 1010 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 1011 MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); 1012 } 1013 } 1014 }; 1015 1016 /** 1017 * @hide 1018 * Default value for the unique identifier 1019 */ 1020 public final static int RCSE_ID_UNREGISTERED = -1; 1021 /** 1022 * Unique identifier of the RemoteControlStackEntry in AudioService with which 1023 * this RemoteControlClient is associated. 1024 */ 1025 private int mRcseId = RCSE_ID_UNREGISTERED; 1026 /** 1027 * @hide 1028 * To be only used by AudioManager after it has received the unique id from 1029 * IAudioService.registerRemoteControlClient() 1030 * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which 1031 * this RemoteControlClient is associated. 1032 */ 1033 public void setRcseId(int id) { 1034 mRcseId = id; 1035 } 1036 1037 /** 1038 * @hide 1039 */ 1040 public int getRcseId() { 1041 return mRcseId; 1042 } 1043 1044 private EventHandler mEventHandler; 1045 private final static int MSG_REQUEST_PLAYBACK_STATE = 1; 1046 private final static int MSG_REQUEST_METADATA = 2; 1047 private final static int MSG_REQUEST_TRANSPORTCONTROL = 3; 1048 private final static int MSG_REQUEST_ARTWORK = 4; 1049 private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5; 1050 private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6; 1051 private final static int MSG_PLUG_DISPLAY = 7; 1052 private final static int MSG_UNPLUG_DISPLAY = 8; 1053 private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; 1054 1055 private class EventHandler extends Handler { 1056 public EventHandler(RemoteControlClient rcc, Looper looper) { 1057 super(looper); 1058 } 1059 1060 @Override 1061 public void handleMessage(Message msg) { 1062 switch(msg.what) { 1063 case MSG_REQUEST_PLAYBACK_STATE: 1064 synchronized (mCacheLock) { 1065 sendPlaybackState_syncCacheLock(); 1066 } 1067 break; 1068 case MSG_REQUEST_METADATA: 1069 synchronized (mCacheLock) { 1070 sendMetadata_syncCacheLock(); 1071 } 1072 break; 1073 case MSG_REQUEST_TRANSPORTCONTROL: 1074 synchronized (mCacheLock) { 1075 sendTransportControlFlags_syncCacheLock(); 1076 } 1077 break; 1078 case MSG_REQUEST_ARTWORK: 1079 synchronized (mCacheLock) { 1080 sendArtwork_syncCacheLock(); 1081 } 1082 break; 1083 case MSG_NEW_INTERNAL_CLIENT_GEN: 1084 onNewInternalClientGen(msg.arg1); 1085 break; 1086 case MSG_NEW_CURRENT_CLIENT_GEN: 1087 onNewCurrentClientGen(msg.arg1); 1088 break; 1089 case MSG_PLUG_DISPLAY: 1090 onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1091 break; 1092 case MSG_UNPLUG_DISPLAY: 1093 onUnplugDisplay((IRemoteControlDisplay)msg.obj); 1094 break; 1095 case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: 1096 onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); 1097 break; 1098 default: 1099 Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); 1100 } 1101 } 1102 } 1103 1104 //=========================================================== 1105 // Communication with the IRemoteControlDisplay (the displays known to the system) 1106 1107 private void sendPlaybackState_syncCacheLock() { 1108 if (mCurrentClientGenId == mInternalClientGenId) { 1109 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1110 while (displayIterator.hasNext()) { 1111 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1112 try { 1113 di.mRcDisplay.setPlaybackState(mInternalClientGenId, 1114 mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, 1115 mPlaybackSpeed); 1116 } catch (RemoteException e) { 1117 Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); 1118 displayIterator.remove(); 1119 } 1120 } 1121 } 1122 } 1123 1124 private void sendMetadata_syncCacheLock() { 1125 if (mCurrentClientGenId == mInternalClientGenId) { 1126 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1127 while (displayIterator.hasNext()) { 1128 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1129 try { 1130 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1131 } catch (RemoteException e) { 1132 Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); 1133 displayIterator.remove(); 1134 } 1135 } 1136 } 1137 } 1138 1139 private void sendTransportControlFlags_syncCacheLock() { 1140 if (mCurrentClientGenId == mInternalClientGenId) { 1141 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1142 while (displayIterator.hasNext()) { 1143 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1144 try { 1145 di.mRcDisplay.setTransportControlFlags(mInternalClientGenId, 1146 mTransportControlFlags); 1147 } catch (RemoteException e) { 1148 Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, 1149 e); 1150 displayIterator.remove(); 1151 } 1152 } 1153 } 1154 } 1155 1156 private void sendArtwork_syncCacheLock() { 1157 // FIXME modify to cache all requested sizes? 1158 if (mCurrentClientGenId == mInternalClientGenId) { 1159 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1160 while (displayIterator.hasNext()) { 1161 if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { 1162 displayIterator.remove(); 1163 } 1164 } 1165 } 1166 } 1167 1168 /** 1169 * Send artwork to an IRemoteControlDisplay. 1170 * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its 1171 * dimension requirements. 1172 * @return false if there was an error communicating with the IRemoteControlDisplay. 1173 */ 1174 private boolean sendArtworkToDisplay(DisplayInfoForClient di) { 1175 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1176 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1177 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1178 try { 1179 di.mRcDisplay.setArtwork(mInternalClientGenId, artwork); 1180 } catch (RemoteException e) { 1181 Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e); 1182 return false; 1183 } 1184 } 1185 return true; 1186 } 1187 1188 private void sendMetadataWithArtwork_syncCacheLock() { 1189 // FIXME modify to cache all requested sizes? 1190 if (mCurrentClientGenId == mInternalClientGenId) { 1191 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1192 while (displayIterator.hasNext()) { 1193 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1194 try { 1195 if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { 1196 Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, 1197 di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); 1198 di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); 1199 } else { 1200 di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 1201 } 1202 } catch (RemoteException e) { 1203 Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); 1204 displayIterator.remove(); 1205 } 1206 } 1207 } 1208 } 1209 1210 //=========================================================== 1211 // Communication with AudioService 1212 1213 private static IAudioService sService; 1214 1215 private static IAudioService getService() 1216 { 1217 if (sService != null) { 1218 return sService; 1219 } 1220 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 1221 sService = IAudioService.Stub.asInterface(b); 1222 return sService; 1223 } 1224 1225 private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) { 1226 if (mRcseId == RCSE_ID_UNREGISTERED) { 1227 return; 1228 } 1229 //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value); 1230 IAudioService service = getService(); 1231 try { 1232 service.setPlaybackInfoForRcc(mRcseId, what, value); 1233 } catch (RemoteException e) { 1234 Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); 1235 } 1236 } 1237 1238 private void sendAudioServiceNewPlaybackState_syncCacheLock() { 1239 if (mRcseId == RCSE_ID_UNREGISTERED) { 1240 return; 1241 } 1242 IAudioService service = getService(); 1243 try { 1244 service.setPlaybackStateForRcc(mRcseId, 1245 mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); 1246 } catch (RemoteException e) { 1247 Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); 1248 } 1249 } 1250 1251 //=========================================================== 1252 // Message handlers 1253 1254 private void onNewInternalClientGen(int clientGeneration) { 1255 synchronized (mCacheLock) { 1256 // this remote control client is told it is the "focused" one: 1257 // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true 1258 mInternalClientGenId = clientGeneration; 1259 } 1260 } 1261 1262 private void onNewCurrentClientGen(int clientGeneration) { 1263 synchronized (mCacheLock) { 1264 mCurrentClientGenId = clientGeneration; 1265 } 1266 } 1267 1268 /** pre-condition rcd != null */ 1269 private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) { 1270 synchronized(mCacheLock) { 1271 // do we have this display already? 1272 boolean displayKnown = false; 1273 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1274 while (displayIterator.hasNext() && !displayKnown) { 1275 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1276 displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder()); 1277 if (displayKnown) { 1278 // this display was known but the change in artwork size will cause the 1279 // artwork to be refreshed 1280 if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { 1281 di.mArtworkExpectedWidth = w; 1282 di.mArtworkExpectedHeight = h; 1283 if (!sendArtworkToDisplay(di)) { 1284 displayIterator.remove(); 1285 } 1286 } 1287 } 1288 } 1289 if (!displayKnown) { 1290 mRcDisplays.add(new DisplayInfoForClient(rcd, w, h)); 1291 } 1292 } 1293 } 1294 1295 /** pre-condition rcd != null */ 1296 private void onUnplugDisplay(IRemoteControlDisplay rcd) { 1297 synchronized(mCacheLock) { 1298 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1299 while (displayIterator.hasNext()) { 1300 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1301 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1302 displayIterator.remove(); 1303 return; 1304 } 1305 } 1306 } 1307 } 1308 1309 /** pre-condition rcd != null */ 1310 private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) { 1311 synchronized(mCacheLock) { 1312 final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); 1313 while (displayIterator.hasNext()) { 1314 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); 1315 if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) && 1316 ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { 1317 di.mArtworkExpectedWidth = w; 1318 di.mArtworkExpectedHeight = h; 1319 if (!sendArtworkToDisplay(di)) { 1320 displayIterator.remove(); 1321 } 1322 break; 1323 } 1324 } 1325 } 1326 } 1327 1328 //=========================================================== 1329 // Internal utilities 1330 1331 /** 1332 * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap. 1333 * If the bitmap fits, then do nothing and return the original. 1334 * 1335 * @param bitmap 1336 * @param maxWidth 1337 * @param maxHeight 1338 * @return 1339 */ 1340 1341 private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) { 1342 if (bitmap != null) { 1343 final int width = bitmap.getWidth(); 1344 final int height = bitmap.getHeight(); 1345 if (width > maxWidth || height > maxHeight) { 1346 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); 1347 int newWidth = Math.round(scale * width); 1348 int newHeight = Math.round(scale * height); 1349 Bitmap.Config newConfig = bitmap.getConfig(); 1350 if (newConfig == null) { 1351 newConfig = Bitmap.Config.ARGB_8888; 1352 } 1353 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig); 1354 Canvas canvas = new Canvas(outBitmap); 1355 Paint paint = new Paint(); 1356 paint.setAntiAlias(true); 1357 paint.setFilterBitmap(true); 1358 canvas.drawBitmap(bitmap, null, 1359 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 1360 bitmap = outBitmap; 1361 } 1362 } 1363 return bitmap; 1364 } 1365 1366 /** 1367 * Fast routine to go through an array of allowed keys and return whether the key is part 1368 * of that array 1369 * @param key the key value 1370 * @param validKeys the array of valid keys for a given type 1371 * @return true if the key is part of the array, false otherwise 1372 */ 1373 private static boolean validTypeForKey(int key, int[] validKeys) { 1374 try { 1375 for (int i = 0 ; ; i++) { 1376 if (key == validKeys[i]) { 1377 return true; 1378 } 1379 } 1380 } catch (ArrayIndexOutOfBoundsException e) { 1381 return false; 1382 } 1383 } 1384} 1385