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