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