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