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