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