RemoteControlClient.java revision 05c66cc5cc6357be19fdef0fc131731368c38646
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.Intent; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.Paint; 25import android.graphics.RectF; 26import android.media.MediaMetadataRetriever; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.Message; 31import android.os.RemoteException; 32import android.os.SystemClock; 33import android.util.Log; 34 35import java.lang.IllegalArgumentException; 36 37/** 38 * RemoteControlClient enables exposing information meant to be consumed by remote controls 39 * capable of displaying metadata, artwork and media transport control buttons. 40 * 41 * <p>A remote control client object is associated with a media button event receiver. This 42 * event receiver must have been previously registered with 43 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the 44 * RemoteControlClient can be registered through 45 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 46 * 47 * <p>Here is an example of creating a RemoteControlClient instance after registering a media 48 * button event receiver: 49 * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName()); 50 * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 51 * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver); 52 * // build the PendingIntent for the remote control client 53 * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 54 * mediaButtonIntent.setComponent(myEventReceiver); 55 * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); 56 * // create and register the remote control client 57 * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent); 58 * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre> 59 */ 60public class RemoteControlClient 61{ 62 private final static String TAG = "RemoteControlClient"; 63 64 /** 65 * Playback state of a RemoteControlClient which is stopped. 66 * 67 * @see #setPlaybackState(int) 68 */ 69 public final static int PLAYSTATE_STOPPED = 1; 70 /** 71 * Playback state of a RemoteControlClient which is paused. 72 * 73 * @see #setPlaybackState(int) 74 */ 75 public final static int PLAYSTATE_PAUSED = 2; 76 /** 77 * Playback state of a RemoteControlClient which is playing media. 78 * 79 * @see #setPlaybackState(int) 80 */ 81 public final static int PLAYSTATE_PLAYING = 3; 82 /** 83 * Playback state of a RemoteControlClient which is fast forwarding in the media 84 * it is currently playing. 85 * 86 * @see #setPlaybackState(int) 87 */ 88 public final static int PLAYSTATE_FAST_FORWARDING = 4; 89 /** 90 * Playback state of a RemoteControlClient which is fast rewinding in the media 91 * it is currently playing. 92 * 93 * @see #setPlaybackState(int) 94 */ 95 public final static int PLAYSTATE_REWINDING = 5; 96 /** 97 * Playback state of a RemoteControlClient which is skipping to the next 98 * logical chapter (such as a song in a playlist) in the media it is currently playing. 99 * 100 * @see #setPlaybackState(int) 101 */ 102 public final static int PLAYSTATE_SKIPPING_FORWARDS = 6; 103 /** 104 * Playback state of a RemoteControlClient which is skipping back to the previous 105 * logical chapter (such as a song in a playlist) in the media it is currently playing. 106 * 107 * @see #setPlaybackState(int) 108 */ 109 public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7; 110 /** 111 * Playback state of a RemoteControlClient which is buffering data to play before it can 112 * start or resume playback. 113 * 114 * @see #setPlaybackState(int) 115 */ 116 public final static int PLAYSTATE_BUFFERING = 8; 117 /** 118 * Playback state of a RemoteControlClient which cannot perform any playback related 119 * operation because of an internal error. Examples of such situations are no network 120 * connectivity when attempting to stream data from a server, or expired user credentials 121 * when trying to play subscription-based content. 122 * 123 * @see #setPlaybackState(int) 124 */ 125 public final static int PLAYSTATE_ERROR = 9; 126 /** 127 * @hide 128 * The value of a playback state when none has been declared. 129 * Intentionally hidden as an application shouldn't set such a playback state value. 130 */ 131 public final static int PLAYSTATE_NONE = 0; 132 133 /** 134 * Flag indicating a RemoteControlClient makes use of the "previous" media key. 135 * 136 * @see #setTransportControlFlags(int) 137 * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS 138 */ 139 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 140 /** 141 * Flag indicating a RemoteControlClient makes use of the "rewind" media key. 142 * 143 * @see #setTransportControlFlags(int) 144 * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND 145 */ 146 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 147 /** 148 * Flag indicating a RemoteControlClient makes use of the "play" media key. 149 * 150 * @see #setTransportControlFlags(int) 151 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY 152 */ 153 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 154 /** 155 * Flag indicating a RemoteControlClient makes use of the "play/pause" media key. 156 * 157 * @see #setTransportControlFlags(int) 158 * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE 159 */ 160 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 161 /** 162 * Flag indicating a RemoteControlClient makes use of the "pause" media key. 163 * 164 * @see #setTransportControlFlags(int) 165 * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE 166 */ 167 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 168 /** 169 * Flag indicating a RemoteControlClient makes use of the "stop" media key. 170 * 171 * @see #setTransportControlFlags(int) 172 * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP 173 */ 174 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 175 /** 176 * Flag indicating a RemoteControlClient makes use of the "fast forward" media key. 177 * 178 * @see #setTransportControlFlags(int) 179 * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD 180 */ 181 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 182 /** 183 * Flag indicating a RemoteControlClient makes use of the "next" media key. 184 * 185 * @see #setTransportControlFlags(int) 186 * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT 187 */ 188 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 189 190 /** 191 * @hide 192 * The flags for when no media keys are declared supported. 193 * Intentionally hidden as an application shouldn't set the transport control flags 194 * to this value. 195 */ 196 public final static int FLAGS_KEY_MEDIA_NONE = 0; 197 198 /** 199 * @hide 200 * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. 201 */ 202 public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0; 203 /** 204 * @hide 205 * Flag used to signal that the transport control buttons supported by the 206 * RemoteControlClient are requested. 207 * This can for instance happen when playback is at the end of a playlist, and the "next" 208 * operation is not supported anymore. 209 */ 210 public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1; 211 /** 212 * @hide 213 * Flag used to signal that the playback state of the RemoteControlClient is requested. 214 */ 215 public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2; 216 /** 217 * @hide 218 * Flag used to signal that the album art for the RemoteControlClient is requested. 219 */ 220 public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; 221 222 /** 223 * Class constructor. 224 * @param mediaButtonIntent The intent that will be sent for the media button events sent 225 * by remote controls. 226 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 227 * action, and have a component that will handle the intent (set with 228 * {@link Intent#setComponent(ComponentName)}) registered with 229 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 230 * before this new RemoteControlClient can itself be registered with 231 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 232 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 233 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 234 */ 235 public RemoteControlClient(PendingIntent mediaButtonIntent) { 236 mRcMediaIntent = mediaButtonIntent; 237 238 Looper looper; 239 if ((looper = Looper.myLooper()) != null) { 240 mEventHandler = new EventHandler(this, looper); 241 } else if ((looper = Looper.getMainLooper()) != null) { 242 mEventHandler = new EventHandler(this, looper); 243 } else { 244 mEventHandler = null; 245 Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); 246 } 247 } 248 249 /** 250 * Class constructor for a remote control client whose internal event handling 251 * happens on a user-provided Looper. 252 * @param mediaButtonIntent The intent that will be sent for the media button events sent 253 * by remote controls. 254 * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} 255 * action, and have a component that will handle the intent (set with 256 * {@link Intent#setComponent(ComponentName)}) registered with 257 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} 258 * before this new RemoteControlClient can itself be registered with 259 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. 260 * @param looper The Looper running the event loop. 261 * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) 262 * @see AudioManager#registerRemoteControlClient(RemoteControlClient) 263 */ 264 public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { 265 mRcMediaIntent = mediaButtonIntent; 266 267 mEventHandler = new EventHandler(this, looper); 268 } 269 270 private static final int[] METADATA_KEYS_TYPE_STRING = { 271 MediaMetadataRetriever.METADATA_KEY_ALBUM, 272 MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, 273 MediaMetadataRetriever.METADATA_KEY_TITLE, 274 MediaMetadataRetriever.METADATA_KEY_ARTIST, 275 MediaMetadataRetriever.METADATA_KEY_AUTHOR, 276 MediaMetadataRetriever.METADATA_KEY_COMPILATION, 277 MediaMetadataRetriever.METADATA_KEY_COMPOSER, 278 MediaMetadataRetriever.METADATA_KEY_DATE, 279 MediaMetadataRetriever.METADATA_KEY_GENRE, 280 MediaMetadataRetriever.METADATA_KEY_TITLE, 281 MediaMetadataRetriever.METADATA_KEY_WRITER }; 282 private static final int[] METADATA_KEYS_TYPE_LONG = { 283 MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, 284 MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, 285 MediaMetadataRetriever.METADATA_KEY_DURATION }; 286 287 /** 288 * Class used to modify metadata in a {@link RemoteControlClient} object. 289 * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, 290 * on which you set the metadata for the RemoteControlClient instance. Once all the information 291 * has been set, use {@link #apply()} to make it the new metadata that should be displayed 292 * for the associated client. Once the metadata has been "applied", you cannot reuse this 293 * instance of the MetadataEditor. 294 */ 295 public class MetadataEditor { 296 /** 297 * @hide 298 */ 299 protected boolean mMetadataChanged; 300 /** 301 * @hide 302 */ 303 protected boolean mArtworkChanged; 304 /** 305 * @hide 306 */ 307 protected Bitmap mEditorArtwork; 308 /** 309 * @hide 310 */ 311 protected Bundle mEditorMetadata; 312 private boolean mApplied = false; 313 314 // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance 315 private MetadataEditor() { } 316 /** 317 * @hide 318 */ 319 public Object clone() throws CloneNotSupportedException { 320 throw new CloneNotSupportedException(); 321 } 322 323 /** 324 * The metadata key for the content artwork / album art. 325 */ 326 public final static int BITMAP_KEY_ARTWORK = 100; 327 /** 328 * @hide 329 * TODO(jmtrivi) have lockscreen and music move to the new key name 330 */ 331 public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; 332 333 /** 334 * Adds textual information to be displayed. 335 * Note that none of the information added after {@link #apply()} has been called, 336 * will be displayed. 337 * @param key The identifier of a the metadata field to set. Valid values are 338 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, 339 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, 340 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 341 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, 342 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, 343 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, 344 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, 345 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, 346 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, 347 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, 348 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. 349 * @param value The text for the given key, or {@code null} to signify there is no valid 350 * information for the field. 351 * @return Returns a reference to the same MetadataEditor object, so you can chain put 352 * calls together. 353 */ 354 public synchronized MetadataEditor putString(int key, String value) 355 throws IllegalArgumentException { 356 if (mApplied) { 357 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 358 return this; 359 } 360 if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { 361 throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); 362 } 363 mEditorMetadata.putString(String.valueOf(key), value); 364 mMetadataChanged = true; 365 return this; 366 } 367 368 /** 369 * Adds numerical information to be displayed. 370 * Note that none of the information added after {@link #apply()} has been called, 371 * will be displayed. 372 * @param key the identifier of a the metadata field to set. Valid values are 373 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, 374 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, 375 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value 376 * expressed in milliseconds), 377 * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. 378 * @param value The long value for the given key 379 * @return Returns a reference to the same MetadataEditor object, so you can chain put 380 * calls together. 381 * @throws IllegalArgumentException 382 */ 383 public synchronized MetadataEditor putLong(int key, long value) 384 throws IllegalArgumentException { 385 if (mApplied) { 386 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 387 return this; 388 } 389 if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { 390 throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); 391 } 392 mEditorMetadata.putLong(String.valueOf(key), value); 393 mMetadataChanged = true; 394 return this; 395 } 396 397 /** 398 * Sets the album / artwork picture to be displayed on the remote control. 399 * @param key the identifier of the bitmap to set. The only valid value is 400 * {@link #BITMAP_KEY_ARTWORK} 401 * @param bitmap The bitmap for the artwork, or null if there isn't any. 402 * @return Returns a reference to the same MetadataEditor object, so you can chain put 403 * calls together. 404 * @throws IllegalArgumentException 405 * @see android.graphics.Bitmap 406 */ 407 public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) 408 throws IllegalArgumentException { 409 if (mApplied) { 410 Log.e(TAG, "Can't edit a previously applied MetadataEditor"); 411 return this; 412 } 413 if (key != BITMAP_KEY_ARTWORK) { 414 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); 415 } 416 if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) { 417 mEditorArtwork = scaleBitmapIfTooBig(bitmap, 418 mArtworkExpectedWidth, mArtworkExpectedHeight); 419 } else { 420 // no valid resize dimensions, store as is 421 mEditorArtwork = bitmap; 422 } 423 mArtworkChanged = true; 424 return this; 425 } 426 427 /** 428 * Clears all the metadata that has been set since the MetadataEditor instance was 429 * created with {@link RemoteControlClient#editMetadata(boolean)}. 430 */ 431 public synchronized void clear() { 432 if (mApplied) { 433 Log.e(TAG, "Can't clear a previously applied MetadataEditor"); 434 return; 435 } 436 mEditorMetadata.clear(); 437 mEditorArtwork = null; 438 } 439 440 /** 441 * Associates all the metadata that has been set since the MetadataEditor instance was 442 * created with {@link RemoteControlClient#editMetadata(boolean)}, or since 443 * {@link #clear()} was called, with the RemoteControlClient. Once "applied", 444 * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. 445 */ 446 public synchronized void apply() { 447 if (mApplied) { 448 Log.e(TAG, "Can't apply a previously applied MetadataEditor"); 449 return; 450 } 451 synchronized(mCacheLock) { 452 // assign the edited data 453 mMetadata = new Bundle(mEditorMetadata); 454 if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) { 455 mArtwork.recycle(); 456 } 457 mArtwork = mEditorArtwork; 458 mEditorArtwork = null; 459 if (mMetadataChanged & mArtworkChanged) { 460 // send to remote control display if conditions are met 461 sendMetadataWithArtwork_syncCacheLock(); 462 } else if (mMetadataChanged) { 463 // send to remote control display if conditions are met 464 sendMetadata_syncCacheLock(); 465 } else if (mArtworkChanged) { 466 // send to remote control display if conditions are met 467 sendArtwork_syncCacheLock(); 468 } 469 mApplied = true; 470 } 471 } 472 } 473 474 /** 475 * Creates a {@link MetadataEditor}. 476 * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that 477 * was previously applied to the RemoteControlClient, or true if it is to be created empty. 478 * @return a new MetadataEditor instance. 479 */ 480 public MetadataEditor editMetadata(boolean startEmpty) { 481 MetadataEditor editor = new MetadataEditor(); 482 if (startEmpty) { 483 editor.mEditorMetadata = new Bundle(); 484 editor.mEditorArtwork = null; 485 editor.mMetadataChanged = true; 486 editor.mArtworkChanged = true; 487 } else { 488 editor.mEditorMetadata = new Bundle(mMetadata); 489 editor.mEditorArtwork = mArtwork; 490 editor.mMetadataChanged = false; 491 editor.mArtworkChanged = false; 492 } 493 return editor; 494 } 495 496 /** 497 * Sets the current playback state. 498 * @param state The current playback state, one of the following values: 499 * {@link #PLAYSTATE_STOPPED}, 500 * {@link #PLAYSTATE_PAUSED}, 501 * {@link #PLAYSTATE_PLAYING}, 502 * {@link #PLAYSTATE_FAST_FORWARDING}, 503 * {@link #PLAYSTATE_REWINDING}, 504 * {@link #PLAYSTATE_SKIPPING_FORWARDS}, 505 * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, 506 * {@link #PLAYSTATE_BUFFERING}, 507 * {@link #PLAYSTATE_ERROR}. 508 */ 509 public void setPlaybackState(int state) { 510 synchronized(mCacheLock) { 511 if (mPlaybackState != state) { 512 // store locally 513 mPlaybackState = state; 514 // keep track of when the state change occurred 515 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); 516 517 // send to remote control display if conditions are met 518 sendPlaybackState_syncCacheLock(); 519 } 520 } 521 } 522 523 /** 524 * Sets the flags for the media transport control buttons that this client supports. 525 * @param transportControlFlags A combination of the following flags: 526 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 527 * {@link #FLAG_KEY_MEDIA_REWIND}, 528 * {@link #FLAG_KEY_MEDIA_PLAY}, 529 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 530 * {@link #FLAG_KEY_MEDIA_PAUSE}, 531 * {@link #FLAG_KEY_MEDIA_STOP}, 532 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 533 * {@link #FLAG_KEY_MEDIA_NEXT} 534 */ 535 public void setTransportControlFlags(int transportControlFlags) { 536 synchronized(mCacheLock) { 537 // store locally 538 mTransportControlFlags = transportControlFlags; 539 540 // send to remote control display if conditions are met 541 sendTransportControlFlags_syncCacheLock(); 542 } 543 } 544 545 /** 546 * Lock for all cached data 547 */ 548 private final Object mCacheLock = new Object(); 549 /** 550 * Cache for the playback state. 551 * Access synchronized on mCacheLock 552 */ 553 private int mPlaybackState = PLAYSTATE_NONE; 554 /** 555 * Time of last play state change 556 * Access synchronized on mCacheLock 557 */ 558 private long mPlaybackStateChangeTimeMs = 0; 559 /** 560 * Cache for the artwork bitmap. 561 * Access synchronized on mCacheLock 562 * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be 563 * accessed to be resized, in which case a copy will be made. This would add overhead in 564 * Bundle operations. 565 */ 566 private Bitmap mArtwork; 567 private final int ARTWORK_DEFAULT_SIZE = 256; 568 private final int ARTWORK_INVALID_SIZE = -1; 569 private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE; 570 private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE; 571 /** 572 * Cache for the transport control mask. 573 * Access synchronized on mCacheLock 574 */ 575 private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE; 576 /** 577 * Cache for the metadata strings. 578 * Access synchronized on mCacheLock 579 * This is re-initialized in apply() and so cannot be final. 580 */ 581 private Bundle mMetadata = new Bundle(); 582 583 /** 584 * The current remote control client generation ID across the system 585 */ 586 private int mCurrentClientGenId = -1; 587 /** 588 * The remote control client generation ID, the last time it was told it was the current RC. 589 * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control 590 * client is the "focused" one, and that whenever this client's info is updated, it needs to 591 * send it to the known IRemoteControlDisplay interfaces. 592 */ 593 private int mInternalClientGenId = -2; 594 595 /** 596 * The media button intent description associated with this remote control client 597 * (can / should include target component for intent handling) 598 */ 599 private final PendingIntent mRcMediaIntent; 600 601 /** 602 * The remote control display to which this client will send information. 603 * NOTE: Only one IRemoteControlDisplay supported in this implementation 604 */ 605 private IRemoteControlDisplay mRcDisplay; 606 607 /** 608 * @hide 609 * Accessor to media button intent description (includes target component) 610 */ 611 public PendingIntent getRcMediaIntent() { 612 return mRcMediaIntent; 613 } 614 /** 615 * @hide 616 * Accessor to IRemoteControlClient 617 */ 618 public IRemoteControlClient getIRemoteControlClient() { 619 return mIRCC; 620 } 621 622 /** 623 * The IRemoteControlClient implementation 624 */ 625 private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { 626 627 public void onInformationRequested(int clientGeneration, int infoFlags, 628 int artWidth, int artHeight) { 629 // only post messages, we can't block here 630 if (mEventHandler != null) { 631 // signal new client 632 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); 633 mEventHandler.dispatchMessage( 634 mEventHandler.obtainMessage( 635 MSG_NEW_INTERNAL_CLIENT_GEN, 636 artWidth, artHeight, 637 new Integer(clientGeneration))); 638 // send the information 639 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); 640 mEventHandler.removeMessages(MSG_REQUEST_METADATA); 641 mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); 642 mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); 643 mEventHandler.dispatchMessage( 644 mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); 645 mEventHandler.dispatchMessage( 646 mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); 647 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); 648 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); 649 } 650 } 651 652 public void setCurrentClientGenerationId(int clientGeneration) { 653 // only post messages, we can't block here 654 if (mEventHandler != null) { 655 mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN); 656 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 657 MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/)); 658 } 659 } 660 661 public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) { 662 // only post messages, we can't block here 663 if (mEventHandler != null) { 664 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 665 MSG_PLUG_DISPLAY, rcd)); 666 } 667 } 668 669 public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) { 670 // only post messages, we can't block here 671 if (mEventHandler != null) { 672 mEventHandler.dispatchMessage(mEventHandler.obtainMessage( 673 MSG_UNPLUG_DISPLAY, rcd)); 674 } 675 } 676 }; 677 678 private EventHandler mEventHandler; 679 private final static int MSG_REQUEST_PLAYBACK_STATE = 1; 680 private final static int MSG_REQUEST_METADATA = 2; 681 private final static int MSG_REQUEST_TRANSPORTCONTROL = 3; 682 private final static int MSG_REQUEST_ARTWORK = 4; 683 private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5; 684 private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6; 685 private final static int MSG_PLUG_DISPLAY = 7; 686 private final static int MSG_UNPLUG_DISPLAY = 8; 687 688 private class EventHandler extends Handler { 689 public EventHandler(RemoteControlClient rcc, Looper looper) { 690 super(looper); 691 } 692 693 @Override 694 public void handleMessage(Message msg) { 695 switch(msg.what) { 696 case MSG_REQUEST_PLAYBACK_STATE: 697 synchronized (mCacheLock) { 698 sendPlaybackState_syncCacheLock(); 699 } 700 break; 701 case MSG_REQUEST_METADATA: 702 synchronized (mCacheLock) { 703 sendMetadata_syncCacheLock(); 704 } 705 break; 706 case MSG_REQUEST_TRANSPORTCONTROL: 707 synchronized (mCacheLock) { 708 sendTransportControlFlags_syncCacheLock(); 709 } 710 break; 711 case MSG_REQUEST_ARTWORK: 712 synchronized (mCacheLock) { 713 sendArtwork_syncCacheLock(); 714 } 715 break; 716 case MSG_NEW_INTERNAL_CLIENT_GEN: 717 onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2); 718 break; 719 case MSG_NEW_CURRENT_CLIENT_GEN: 720 onNewCurrentClientGen(msg.arg1); 721 break; 722 case MSG_PLUG_DISPLAY: 723 onPlugDisplay((IRemoteControlDisplay)msg.obj); 724 break; 725 case MSG_UNPLUG_DISPLAY: 726 onUnplugDisplay((IRemoteControlDisplay)msg.obj); 727 break; 728 default: 729 Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); 730 } 731 } 732 } 733 734 private void detachFromDisplay_syncCacheLock() { 735 mRcDisplay = null; 736 mArtworkExpectedWidth = ARTWORK_INVALID_SIZE; 737 mArtworkExpectedHeight = ARTWORK_INVALID_SIZE; 738 } 739 740 private void sendPlaybackState_syncCacheLock() { 741 if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { 742 try { 743 mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState, 744 mPlaybackStateChangeTimeMs); 745 } catch (RemoteException e) { 746 Log.e(TAG, "Error in setPlaybackState(), dead display "+e); 747 detachFromDisplay_syncCacheLock(); 748 } 749 } 750 } 751 752 private void sendMetadata_syncCacheLock() { 753 if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { 754 try { 755 mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); 756 } catch (RemoteException e) { 757 Log.e(TAG, "Error in sendPlaybackState(), dead display "+e); 758 detachFromDisplay_syncCacheLock(); 759 } 760 } 761 } 762 763 private void sendTransportControlFlags_syncCacheLock() { 764 if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { 765 try { 766 mRcDisplay.setTransportControlFlags(mInternalClientGenId, 767 mTransportControlFlags); 768 } catch (RemoteException e) { 769 Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e); 770 detachFromDisplay_syncCacheLock(); 771 } 772 } 773 } 774 775 private void sendArtwork_syncCacheLock() { 776 if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { 777 // even though we have already scaled in setArtwork(), when this client needs to 778 // send the bitmap, there might be newer and smaller expected dimensions, so we have 779 // to check again. 780 mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight); 781 try { 782 mRcDisplay.setArtwork(mInternalClientGenId, mArtwork); 783 } catch (RemoteException e) { 784 Log.e(TAG, "Error in sendArtwork(), dead display "+e); 785 detachFromDisplay_syncCacheLock(); 786 } 787 } 788 } 789 790 private void sendMetadataWithArtwork_syncCacheLock() { 791 if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) { 792 // even though we have already scaled in setArtwork(), when this client needs to 793 // send the bitmap, there might be newer and smaller expected dimensions, so we have 794 // to check again. 795 mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight); 796 try { 797 mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork); 798 } catch (RemoteException e) { 799 Log.e(TAG, "Error in setAllMetadata(), dead display "+e); 800 detachFromDisplay_syncCacheLock(); 801 } 802 } 803 } 804 805 private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) { 806 synchronized (mCacheLock) { 807 // this remote control client is told it is the "focused" one: 808 // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true 809 mInternalClientGenId = clientGeneration.intValue(); 810 if (artWidth > 0) { 811 mArtworkExpectedWidth = artWidth; 812 mArtworkExpectedHeight = artHeight; 813 } 814 } 815 } 816 817 private void onNewCurrentClientGen(int clientGeneration) { 818 synchronized (mCacheLock) { 819 mCurrentClientGenId = clientGeneration; 820 } 821 } 822 823 private void onPlugDisplay(IRemoteControlDisplay rcd) { 824 synchronized(mCacheLock) { 825 mRcDisplay = rcd; 826 } 827 } 828 829 private void onUnplugDisplay(IRemoteControlDisplay rcd) { 830 synchronized(mCacheLock) { 831 if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) { 832 mRcDisplay = null; 833 mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE; 834 mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE; 835 } 836 } 837 } 838 839 /** 840 * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap. 841 * If the bitmap fits, then do nothing and return the original. 842 * 843 * @param bitmap 844 * @param maxWidth 845 * @param maxHeight 846 * @return 847 */ 848 849 private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) { 850 if (bitmap != null) { 851 final int width = bitmap.getWidth(); 852 final int height = bitmap.getHeight(); 853 if (width > maxWidth || height > maxHeight) { 854 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); 855 int newWidth = Math.round(scale * width); 856 int newHeight = Math.round(scale * height); 857 Bitmap.Config newConfig = bitmap.getConfig(); 858 if (newConfig == null) { 859 newConfig = Bitmap.Config.ARGB_8888; 860 } 861 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig); 862 Canvas canvas = new Canvas(outBitmap); 863 Paint paint = new Paint(); 864 paint.setAntiAlias(true); 865 paint.setFilterBitmap(true); 866 canvas.drawBitmap(bitmap, null, 867 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 868 bitmap = outBitmap; 869 } 870 } 871 return bitmap; 872 } 873 874 /** 875 * Fast routine to go through an array of allowed keys and return whether the key is part 876 * of that array 877 * @param key the key value 878 * @param validKeys the array of valid keys for a given type 879 * @return true if the key is part of the array, false otherwise 880 */ 881 private static boolean validTypeForKey(int key, int[] validKeys) { 882 try { 883 for (int i = 0 ; ; i++) { 884 if (key == validKeys[i]) { 885 return true; 886 } 887 } 888 } catch (ArrayIndexOutOfBoundsException e) { 889 return false; 890 } 891 } 892} 893