1 /* 2 * Copyright (C) 2013 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.Manifest; 20 import android.app.ActivityManager; 21 import android.app.PendingIntent; 22 import android.app.PendingIntent.CanceledException; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Bitmap; 26 import android.media.IRemoteControlDisplay; 27 import android.media.MediaMetadataEditor; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.SystemClock; 35 import android.util.DisplayMetrics; 36 import android.util.Log; 37 import android.view.KeyEvent; 38 39 import java.lang.ref.WeakReference; 40 41 /** 42 * The RemoteController class is used to control media playback, display and update media metadata 43 * and playback status, published by applications using the {@link RemoteControlClient} class. 44 * <p> 45 * A RemoteController shall be registered through 46 * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send 47 * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor. 48 * Implement the methods of the interface to receive the information published by the active 49 * {@link RemoteControlClient} instances. 50 * <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for 51 * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well. 52 * <p> 53 * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled 54 * notification listeners (see {@link android.service.notification.NotificationListenerService}). 55 */ 56 public final class RemoteController 57 { 58 private final static int MAX_BITMAP_DIMENSION = 512; 59 private final static int TRANSPORT_UNKNOWN = 0; 60 private final static String TAG = "RemoteController"; 61 private final static boolean DEBUG = false; 62 private final static Object mGenLock = new Object(); 63 private final static Object mInfoLock = new Object(); 64 private final RcDisplay mRcd; 65 private final Context mContext; 66 private final AudioManager mAudioManager; 67 private final int mMaxBitmapDimension; 68 private MetadataEditor mMetadataEditor; 69 70 /** 71 * Synchronized on mGenLock 72 */ 73 private int mClientGenerationIdCurrent = 0; 74 75 /** 76 * Synchronized on mInfoLock 77 */ 78 private boolean mIsRegistered = false; 79 private PendingIntent mClientPendingIntentCurrent; 80 private OnClientUpdateListener mOnClientUpdateListener; 81 private PlaybackInfo mLastPlaybackInfo; 82 private int mArtworkWidth = -1; 83 private int mArtworkHeight = -1; 84 private boolean mEnabled = true; 85 86 /** 87 * Class constructor. 88 * @param context the {@link Context}, must be non-null. 89 * @param updateListener the listener to be called whenever new client information is available, 90 * must be non-null. 91 * @throws IllegalArgumentException 92 */ RemoteController(Context context, OnClientUpdateListener updateListener)93 public RemoteController(Context context, OnClientUpdateListener updateListener) 94 throws IllegalArgumentException { 95 this(context, updateListener, null); 96 } 97 98 /** 99 * Class constructor. 100 * @param context the {@link Context}, must be non-null. 101 * @param updateListener the listener to be called whenever new client information is available, 102 * must be non-null. 103 * @param looper the {@link Looper} on which to run the event loop, 104 * or null to use the current thread's looper. 105 * @throws java.lang.IllegalArgumentException 106 */ RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)107 public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper) 108 throws IllegalArgumentException { 109 if (context == null) { 110 throw new IllegalArgumentException("Invalid null Context"); 111 } 112 if (updateListener == null) { 113 throw new IllegalArgumentException("Invalid null OnClientUpdateListener"); 114 } 115 if (looper != null) { 116 mEventHandler = new EventHandler(this, looper); 117 } else { 118 Looper l = Looper.myLooper(); 119 if (l != null) { 120 mEventHandler = new EventHandler(this, l); 121 } else { 122 throw new IllegalArgumentException("Calling thread not associated with a looper"); 123 } 124 } 125 mOnClientUpdateListener = updateListener; 126 mContext = context; 127 mRcd = new RcDisplay(this); 128 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 129 130 if (ActivityManager.isLowRamDeviceStatic()) { 131 mMaxBitmapDimension = MAX_BITMAP_DIMENSION; 132 } else { 133 final DisplayMetrics dm = context.getResources().getDisplayMetrics(); 134 mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels); 135 } 136 } 137 138 139 /** 140 * Interface definition for the callbacks to be invoked whenever media events, metadata 141 * and playback status are available. 142 */ 143 public interface OnClientUpdateListener { 144 /** 145 * Called whenever all information, previously received through the other 146 * methods of the listener, is no longer valid and is about to be refreshed. 147 * This is typically called whenever a new {@link RemoteControlClient} has been selected 148 * by the system to have its media information published. 149 * @param clearing true if there is no selected RemoteControlClient and no information 150 * is available. 151 */ onClientChange(boolean clearing)152 public void onClientChange(boolean clearing); 153 154 /** 155 * Called whenever the playback state has changed. 156 * It is called when no information is known about the playback progress in the media and 157 * the playback speed. 158 * @param state one of the playback states authorized 159 * in {@link RemoteControlClient#setPlaybackState(int)}. 160 */ onClientPlaybackStateUpdate(int state)161 public void onClientPlaybackStateUpdate(int state); 162 /** 163 * Called whenever the playback state has changed, and playback position 164 * and speed are known. 165 * @param state one of the playback states authorized 166 * in {@link RemoteControlClient#setPlaybackState(int)}. 167 * @param stateChangeTimeMs the system time at which the state change was reported, 168 * expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}. 169 * @param currentPosMs a positive value for the current media playback position expressed 170 * in ms, a negative value if the position is temporarily unknown. 171 * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 172 * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 173 * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}). 174 */ onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed)175 public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, 176 long currentPosMs, float speed); 177 /** 178 * Called whenever the transport control flags have changed. 179 * @param transportControlFlags one of the flags authorized 180 * in {@link RemoteControlClient#setTransportControlFlags(int)}. 181 */ onClientTransportControlUpdate(int transportControlFlags)182 public void onClientTransportControlUpdate(int transportControlFlags); 183 /** 184 * Called whenever new metadata is available. 185 * See the {@link MediaMetadataEditor#putLong(int, long)}, 186 * {@link MediaMetadataEditor#putString(int, String)}, 187 * {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and 188 * {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that 189 * can be queried. 190 * @param metadataEditor the container of the new metadata. 191 */ onClientMetadataUpdate(MetadataEditor metadataEditor)192 public void onClientMetadataUpdate(MetadataEditor metadataEditor); 193 }; 194 195 196 /** 197 * @hide 198 */ getRemoteControlClientPackageName()199 public String getRemoteControlClientPackageName() { 200 return mClientPendingIntentCurrent != null ? 201 mClientPendingIntentCurrent.getCreatorPackage() : null; 202 } 203 204 /** 205 * Return the estimated playback position of the current media track or a negative value 206 * if not available. 207 * 208 * <p>The value returned is estimated by the current process and may not be perfect. 209 * The time returned by this method is calculated from the last state change time based 210 * on the current play position at that time and the last known playback speed. 211 * An application may call {@link #setSynchronizationMode(int)} to apply 212 * a synchronization policy that will periodically re-sync the estimated position 213 * with the RemoteControlClient.</p> 214 * 215 * @return the current estimated playback position in milliseconds or a negative value 216 * if not available 217 * 218 * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float) 219 */ getEstimatedMediaPosition()220 public long getEstimatedMediaPosition() { 221 if (mLastPlaybackInfo != null) { 222 if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) { 223 return mLastPlaybackInfo.mCurrentPosMs; 224 } 225 226 // Take the current position at the time of state change and estimate. 227 final long thenPos = mLastPlaybackInfo.mCurrentPosMs; 228 if (thenPos < 0) { 229 return -1; 230 } 231 232 final long now = SystemClock.elapsedRealtime(); 233 final long then = mLastPlaybackInfo.mStateChangeTimeMs; 234 final long sinceThen = now - then; 235 final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed); 236 return thenPos + scaledSinceThen; 237 } 238 return -1; 239 } 240 241 242 /** 243 * Send a simulated key event for a media button to be received by the current client. 244 * To simulate a key press, you must first send a KeyEvent built with 245 * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} 246 * action. 247 * <p>The key event will be sent to the registered receiver 248 * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated 249 * {@link RemoteControlClient}'s metadata and playback state is published (there may be 250 * none under some circumstances). 251 * @param keyEvent a {@link KeyEvent} instance whose key code is one of 252 * {@link KeyEvent#KEYCODE_MUTE}, 253 * {@link KeyEvent#KEYCODE_HEADSETHOOK}, 254 * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, 255 * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, 256 * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, 257 * {@link KeyEvent#KEYCODE_MEDIA_STOP}, 258 * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, 259 * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, 260 * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, 261 * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, 262 * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, 263 * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, 264 * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, 265 * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. 266 * @return true if the event was successfully sent, false otherwise. 267 * @throws IllegalArgumentException 268 */ sendMediaKeyEvent(KeyEvent keyEvent)269 public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException { 270 if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) { 271 throw new IllegalArgumentException("not a media key event"); 272 } 273 final PendingIntent pi; 274 synchronized(mInfoLock) { 275 if (!mIsRegistered) { 276 Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); 277 return false; 278 } 279 if (!mEnabled) { 280 Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController"); 281 return false; 282 } 283 pi = mClientPendingIntentCurrent; 284 } 285 if (pi != null) { 286 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); 287 intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 288 try { 289 pi.send(mContext, 0, intent); 290 } catch (CanceledException e) { 291 Log.e(TAG, "Error sending intent for media button down: ", e); 292 return false; 293 } 294 } else { 295 Log.i(TAG, "No-op when sending key click, no receiver right now"); 296 return false; 297 } 298 return true; 299 } 300 301 302 /** 303 * Sets the new playback position. 304 * This method can only be called on a registered RemoteController. 305 * @param timeMs a 0 or positive value for the new playback position, expressed in ms. 306 * @return true if the command to set the playback position was successfully sent. 307 * @throws IllegalArgumentException 308 */ seekTo(long timeMs)309 public boolean seekTo(long timeMs) throws IllegalArgumentException { 310 if (!mEnabled) { 311 Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController"); 312 return false; 313 } 314 if (timeMs < 0) { 315 throw new IllegalArgumentException("illegal negative time value"); 316 } 317 final int genId; 318 synchronized (mGenLock) { 319 genId = mClientGenerationIdCurrent; 320 } 321 mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs); 322 return true; 323 } 324 325 326 /** 327 * @hide 328 * @param wantBitmap 329 * @param width 330 * @param height 331 * @return true if successful 332 * @throws IllegalArgumentException 333 */ setArtworkConfiguration(boolean wantBitmap, int width, int height)334 public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height) 335 throws IllegalArgumentException { 336 synchronized (mInfoLock) { 337 if (wantBitmap) { 338 if ((width > 0) && (height > 0)) { 339 if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; } 340 if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; } 341 mArtworkWidth = width; 342 mArtworkHeight = height; 343 } else { 344 throw new IllegalArgumentException("Invalid dimensions"); 345 } 346 } else { 347 mArtworkWidth = -1; 348 mArtworkHeight = -1; 349 } 350 if (mIsRegistered) { 351 mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, 352 mArtworkWidth, mArtworkHeight); 353 } // else new values have been stored, and will be read by AudioManager with 354 // RemoteController.getArtworkSize() when AudioManager.registerRemoteController() 355 // is called. 356 } 357 return true; 358 } 359 360 /** 361 * Set the maximum artwork image dimensions to be received in the metadata. 362 * No bitmaps will be received unless this has been specified. 363 * @param width the maximum width in pixels 364 * @param height the maximum height in pixels 365 * @return true if the artwork dimension was successfully set. 366 * @throws IllegalArgumentException 367 */ setArtworkConfiguration(int width, int height)368 public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException { 369 return setArtworkConfiguration(true, width, height); 370 } 371 372 /** 373 * Prevents this RemoteController from receiving artwork images. 374 * @return true if receiving artwork images was successfully disabled. 375 */ clearArtworkConfiguration()376 public boolean clearArtworkConfiguration() { 377 return setArtworkConfiguration(false, -1, -1); 378 } 379 380 381 /** 382 * Default playback position synchronization mode where the RemoteControlClient is not 383 * asked regularly for its playback position to see if it has drifted from the estimated 384 * position. 385 */ 386 public static final int POSITION_SYNCHRONIZATION_NONE = 0; 387 388 /** 389 * The playback position synchronization mode where the RemoteControlClient instances which 390 * expose their playback position to the framework, will be regularly polled to check 391 * whether any drift has been noticed between their estimated position and the one they report. 392 * Note that this mode should only ever be used when needing to display very accurate playback 393 * position, as regularly polling a RemoteControlClient for its position may have an impact 394 * on battery life (if applicable) when this query will trigger network transactions in the 395 * case of remote playback. 396 */ 397 public static final int POSITION_SYNCHRONIZATION_CHECK = 1; 398 399 /** 400 * Set the playback position synchronization mode. 401 * Must be called on a registered RemoteController. 402 * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK} 403 * @return true if the synchronization mode was successfully set. 404 * @throws IllegalArgumentException 405 */ setSynchronizationMode(int sync)406 public boolean setSynchronizationMode(int sync) throws IllegalArgumentException { 407 if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) { 408 throw new IllegalArgumentException("Unknown synchronization mode " + sync); 409 } 410 if (!mIsRegistered) { 411 Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController"); 412 return false; 413 } 414 mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd, 415 POSITION_SYNCHRONIZATION_CHECK == sync); 416 return true; 417 } 418 419 420 /** 421 * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of 422 * the current {@link RemoteControlClient}. 423 * This method can only be called on a registered RemoteController. 424 * @return a new MetadataEditor instance. 425 */ editMetadata()426 public MetadataEditor editMetadata() { 427 MetadataEditor editor = new MetadataEditor(); 428 editor.mEditorMetadata = new Bundle(); 429 editor.mEditorArtwork = null; 430 editor.mMetadataChanged = true; 431 editor.mArtworkChanged = true; 432 editor.mEditableKeys = 0; 433 return editor; 434 } 435 436 437 /** 438 * A class to read the metadata published by a {@link RemoteControlClient}, or send a 439 * {@link RemoteControlClient} new values for keys that can be edited. 440 */ 441 public class MetadataEditor extends MediaMetadataEditor { 442 /** 443 * @hide 444 */ MetadataEditor()445 protected MetadataEditor() { } 446 447 /** 448 * @hide 449 */ MetadataEditor(Bundle metadata, long editableKeys)450 protected MetadataEditor(Bundle metadata, long editableKeys) { 451 mEditorMetadata = metadata; 452 mEditableKeys = editableKeys; 453 454 mEditorArtwork = (Bitmap) metadata.getParcelable( 455 String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)); 456 if (mEditorArtwork != null) { 457 cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); 458 } 459 460 mMetadataChanged = true; 461 mArtworkChanged = true; 462 mApplied = false; 463 } 464 cleanupBitmapFromBundle(int key)465 private void cleanupBitmapFromBundle(int key) { 466 if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) { 467 mEditorMetadata.remove(String.valueOf(key)); 468 } 469 } 470 471 /** 472 * Applies all of the metadata changes that have been set since the MediaMetadataEditor 473 * instance was created with {@link RemoteController#editMetadata()} 474 * or since {@link #clear()} was called. 475 */ apply()476 public synchronized void apply() { 477 // "applying" a metadata bundle in RemoteController is only for sending edited 478 // key values back to the RemoteControlClient, so here we only care about the only 479 // editable key we support: RATING_KEY_BY_USER 480 if (!mMetadataChanged) { 481 return; 482 } 483 final int genId; 484 synchronized(mGenLock) { 485 genId = mClientGenerationIdCurrent; 486 } 487 synchronized(mInfoLock) { 488 if (mEditorMetadata.containsKey( 489 String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) { 490 Rating rating = (Rating) getObject( 491 MediaMetadataEditor.RATING_KEY_BY_USER, null); 492 mAudioManager.updateRemoteControlClientMetadata(genId, 493 MediaMetadataEditor.RATING_KEY_BY_USER, 494 rating); 495 } else { 496 Log.e(TAG, "no metadata to apply"); 497 } 498 // NOT setting mApplied to true as this type of MetadataEditor will be applied 499 // multiple times, whenever the user of a RemoteController needs to change the 500 // metadata (e.g. user changes the rating of a song more than once during playback) 501 mApplied = false; 502 } 503 } 504 505 } 506 507 508 //================================================== 509 // Implementation of IRemoteControlDisplay interface 510 private static class RcDisplay extends IRemoteControlDisplay.Stub { 511 private final WeakReference<RemoteController> mController; 512 RcDisplay(RemoteController rc)513 RcDisplay(RemoteController rc) { 514 mController = new WeakReference<RemoteController>(rc); 515 } 516 setCurrentClientId(int genId, PendingIntent clientMediaIntent, boolean clearing)517 public void setCurrentClientId(int genId, PendingIntent clientMediaIntent, 518 boolean clearing) { 519 final RemoteController rc = mController.get(); 520 if (rc == null) { 521 return; 522 } 523 boolean isNew = false; 524 synchronized(mGenLock) { 525 if (rc.mClientGenerationIdCurrent != genId) { 526 rc.mClientGenerationIdCurrent = genId; 527 isNew = true; 528 } 529 } 530 if (clientMediaIntent != null) { 531 sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE, 532 genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/); 533 } 534 if (isNew || clearing) { 535 sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, 536 genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/); 537 } 538 } 539 setEnabled(boolean enabled)540 public void setEnabled(boolean enabled) { 541 final RemoteController rc = mController.get(); 542 if (rc == null) { 543 return; 544 } 545 sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE, 546 enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/); 547 } 548 setPlaybackState(int genId, int state, long stateChangeTimeMs, long currentPosMs, float speed)549 public void setPlaybackState(int genId, int state, 550 long stateChangeTimeMs, long currentPosMs, float speed) { 551 final RemoteController rc = mController.get(); 552 if (rc == null) { 553 return; 554 } 555 if (DEBUG) { 556 Log.d(TAG, "> new playback state: genId="+genId 557 + " state="+ state 558 + " changeTime="+ stateChangeTimeMs 559 + " pos=" + currentPosMs 560 + "ms speed=" + speed); 561 } 562 563 synchronized(mGenLock) { 564 if (rc.mClientGenerationIdCurrent != genId) { 565 return; 566 } 567 } 568 final PlaybackInfo playbackInfo = 569 new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed); 570 sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, 571 genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/); 572 573 } 574 setTransportControlInfo(int genId, int transportControlFlags, int posCapabilities)575 public void setTransportControlInfo(int genId, int transportControlFlags, 576 int posCapabilities) { 577 final RemoteController rc = mController.get(); 578 if (rc == null) { 579 return; 580 } 581 synchronized(mGenLock) { 582 if (rc.mClientGenerationIdCurrent != genId) { 583 return; 584 } 585 } 586 sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, 587 genId /*arg1*/, transportControlFlags /*arg2*/, 588 null /*obj*/, 0 /*delay*/); 589 } 590 setMetadata(int genId, Bundle metadata)591 public void setMetadata(int genId, Bundle metadata) { 592 final RemoteController rc = mController.get(); 593 if (rc == null) { 594 return; 595 } 596 if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); } 597 if (metadata == null) { 598 return; 599 } 600 synchronized(mGenLock) { 601 if (rc.mClientGenerationIdCurrent != genId) { 602 return; 603 } 604 } 605 sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, 606 genId /*arg1*/, 0 /*arg2*/, 607 metadata /*obj*/, 0 /*delay*/); 608 } 609 setArtwork(int genId, Bitmap artwork)610 public void setArtwork(int genId, Bitmap artwork) { 611 final RemoteController rc = mController.get(); 612 if (rc == null) { 613 return; 614 } 615 if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); } 616 synchronized(mGenLock) { 617 if (rc.mClientGenerationIdCurrent != genId) { 618 return; 619 } 620 } 621 Bundle metadata = new Bundle(1); 622 metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork); 623 sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, 624 genId /*arg1*/, 0 /*arg2*/, 625 metadata /*obj*/, 0 /*delay*/); 626 } 627 setAllMetadata(int genId, Bundle metadata, Bitmap artwork)628 public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) { 629 final RemoteController rc = mController.get(); 630 if (rc == null) { 631 return; 632 } 633 if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); } 634 if ((metadata == null) && (artwork == null)) { 635 return; 636 } 637 synchronized(mGenLock) { 638 if (rc.mClientGenerationIdCurrent != genId) { 639 return; 640 } 641 } 642 if (metadata == null) { 643 metadata = new Bundle(1); 644 } 645 if (artwork != null) { 646 metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), 647 artwork); 648 } 649 sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, 650 genId /*arg1*/, 0 /*arg2*/, 651 metadata /*obj*/, 0 /*delay*/); 652 } 653 } 654 655 //================================================== 656 // Event handling 657 private final EventHandler mEventHandler; 658 private final static int MSG_NEW_PENDING_INTENT = 0; 659 private final static int MSG_NEW_PLAYBACK_INFO = 1; 660 private final static int MSG_NEW_TRANSPORT_INFO = 2; 661 private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter 662 private final static int MSG_CLIENT_CHANGE = 4; 663 private final static int MSG_DISPLAY_ENABLE = 5; 664 665 private class EventHandler extends Handler { 666 EventHandler(RemoteController rc, Looper looper)667 public EventHandler(RemoteController rc, Looper looper) { 668 super(looper); 669 } 670 671 @Override handleMessage(Message msg)672 public void handleMessage(Message msg) { 673 switch(msg.what) { 674 case MSG_NEW_PENDING_INTENT: 675 onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj); 676 break; 677 case MSG_NEW_PLAYBACK_INFO: 678 onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj); 679 break; 680 case MSG_NEW_TRANSPORT_INFO: 681 onNewTransportInfo(msg.arg1, msg.arg2); 682 break; 683 case MSG_NEW_METADATA: 684 onNewMetadata(msg.arg1, (Bundle)msg.obj); 685 break; 686 case MSG_CLIENT_CHANGE: 687 onClientChange(msg.arg1, msg.arg2 == 1); 688 break; 689 case MSG_DISPLAY_ENABLE: 690 onDisplayEnable(msg.arg1 == 1); 691 break; 692 default: 693 Log.e(TAG, "unknown event " + msg.what); 694 } 695 } 696 } 697 698 /** If the msg is already queued, replace it with this one. */ 699 private static final int SENDMSG_REPLACE = 0; 700 /** If the msg is already queued, ignore this one and leave the old. */ 701 private static final int SENDMSG_NOOP = 1; 702 /** If the msg is already queued, queue this one and leave the old. */ 703 private static final int SENDMSG_QUEUE = 2; 704 sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delayMs)705 private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, 706 int arg1, int arg2, Object obj, int delayMs) { 707 if (handler == null) { 708 Log.e(TAG, "null event handler, will not deliver message " + msg); 709 return; 710 } 711 if (existingMsgPolicy == SENDMSG_REPLACE) { 712 handler.removeMessages(msg); 713 } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { 714 return; 715 } 716 handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs); 717 } 718 onNewPendingIntent(int genId, PendingIntent pi)719 private void onNewPendingIntent(int genId, PendingIntent pi) { 720 synchronized(mGenLock) { 721 if (mClientGenerationIdCurrent != genId) { 722 return; 723 } 724 } 725 synchronized(mInfoLock) { 726 mClientPendingIntentCurrent = pi; 727 } 728 } 729 onNewPlaybackInfo(int genId, PlaybackInfo pi)730 private void onNewPlaybackInfo(int genId, PlaybackInfo pi) { 731 synchronized(mGenLock) { 732 if (mClientGenerationIdCurrent != genId) { 733 return; 734 } 735 } 736 final OnClientUpdateListener l; 737 synchronized(mInfoLock) { 738 l = this.mOnClientUpdateListener; 739 mLastPlaybackInfo = pi; 740 } 741 if (l != null) { 742 if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { 743 l.onClientPlaybackStateUpdate(pi.mState); 744 } else { 745 l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs, 746 pi.mSpeed); 747 } 748 } 749 } 750 onNewTransportInfo(int genId, int transportControlFlags)751 private void onNewTransportInfo(int genId, int transportControlFlags) { 752 synchronized(mGenLock) { 753 if (mClientGenerationIdCurrent != genId) { 754 return; 755 } 756 } 757 final OnClientUpdateListener l; 758 synchronized(mInfoLock) { 759 l = mOnClientUpdateListener; 760 } 761 if (l != null) { 762 l.onClientTransportControlUpdate(transportControlFlags); 763 } 764 } 765 766 /** 767 * @param genId 768 * @param metadata guaranteed to be always non-null 769 */ onNewMetadata(int genId, Bundle metadata)770 private void onNewMetadata(int genId, Bundle metadata) { 771 synchronized(mGenLock) { 772 if (mClientGenerationIdCurrent != genId) { 773 return; 774 } 775 } 776 final OnClientUpdateListener l; 777 final MetadataEditor metadataEditor; 778 // prepare the received Bundle to be used inside a MetadataEditor 779 final long editableKeys = metadata.getLong( 780 String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0); 781 if (editableKeys != 0) { 782 metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK)); 783 } 784 synchronized(mInfoLock) { 785 l = mOnClientUpdateListener; 786 if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) { 787 if (mMetadataEditor.mEditorMetadata != metadata) { 788 // existing metadata, merge existing and new 789 mMetadataEditor.mEditorMetadata.putAll(metadata); 790 } 791 792 mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, 793 (Bitmap)metadata.getParcelable( 794 String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK))); 795 mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); 796 } else { 797 mMetadataEditor = new MetadataEditor(metadata, editableKeys); 798 } 799 metadataEditor = mMetadataEditor; 800 } 801 if (l != null) { 802 l.onClientMetadataUpdate(metadataEditor); 803 } 804 } 805 onClientChange(int genId, boolean clearing)806 private void onClientChange(int genId, boolean clearing) { 807 synchronized(mGenLock) { 808 if (mClientGenerationIdCurrent != genId) { 809 return; 810 } 811 } 812 final OnClientUpdateListener l; 813 synchronized(mInfoLock) { 814 l = mOnClientUpdateListener; 815 mMetadataEditor = null; 816 } 817 if (l != null) { 818 l.onClientChange(clearing); 819 } 820 } 821 onDisplayEnable(boolean enabled)822 private void onDisplayEnable(boolean enabled) { 823 final OnClientUpdateListener l; 824 synchronized(mInfoLock) { 825 mEnabled = enabled; 826 l = this.mOnClientUpdateListener; 827 } 828 if (!enabled) { 829 // when disabling, reset all info sent to the user 830 final int genId; 831 synchronized (mGenLock) { 832 genId = mClientGenerationIdCurrent; 833 } 834 // send "stopped" state, happened "now", playback position is 0, speed 0.0f 835 final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED, 836 SystemClock.elapsedRealtime() /*stateChangeTimeMs*/, 837 0 /*currentPosMs*/, 0.0f /*speed*/); 838 sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, 839 genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/); 840 // send "blank" transport control info: no controls are supported 841 sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, 842 genId /*arg1*/, 0 /*arg2, no flags*/, 843 null /*obj, ignored*/, 0 /*delay*/); 844 // send dummy metadata with empty string for title and artist, duration of 0 845 Bundle metadata = new Bundle(3); 846 metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), ""); 847 metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), ""); 848 metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0); 849 sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, 850 genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/); 851 } 852 } 853 854 //================================================== 855 private static class PlaybackInfo { 856 int mState; 857 long mStateChangeTimeMs; 858 long mCurrentPosMs; 859 float mSpeed; 860 PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed)861 PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) { 862 mState = state; 863 mStateChangeTimeMs = stateChangeTimeMs; 864 mCurrentPosMs = currentPosMs; 865 mSpeed = speed; 866 } 867 } 868 869 /** 870 * @hide 871 * Used by AudioManager to mark this instance as registered. 872 * @param registered 873 */ setIsRegistered(boolean registered)874 void setIsRegistered(boolean registered) { 875 synchronized (mInfoLock) { 876 mIsRegistered = registered; 877 } 878 } 879 880 /** 881 * @hide 882 * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl 883 * @return 884 */ getRcDisplay()885 RcDisplay getRcDisplay() { 886 return mRcd; 887 } 888 889 /** 890 * @hide 891 * Used by AudioManager to read the current artwork dimension 892 * @return array containing width (index 0) and height (index 1) of currently set artwork size 893 */ getArtworkSize()894 int[] getArtworkSize() { 895 synchronized (mInfoLock) { 896 int[] size = { mArtworkWidth, mArtworkHeight }; 897 return size; 898 } 899 } 900 901 /** 902 * @hide 903 * Used by AudioManager to access user listener receiving the client update notifications 904 * @return 905 */ getUpdateListener()906 OnClientUpdateListener getUpdateListener() { 907 return mOnClientUpdateListener; 908 } 909 } 910