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