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