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