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