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