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.compat.annotation.UnsupportedAppUsage; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.graphics.Bitmap; 24 import android.media.session.MediaController; 25 import android.media.session.MediaSession; 26 import android.media.session.MediaSessionLegacyHelper; 27 import android.media.session.MediaSessionManager; 28 import android.media.session.PlaybackState; 29 import android.os.Build; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.UserHandle; 35 import android.util.DisplayMetrics; 36 import android.util.Log; 37 import android.view.KeyEvent; 38 39 import java.util.List; 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 * @deprecated Use {@link MediaController} instead. 57 */ 58 @Deprecated public final class RemoteController 59 { 60 private final static int MAX_BITMAP_DIMENSION = 512; 61 private final static String TAG = "RemoteController"; 62 private final static boolean DEBUG = false; 63 private final static Object mInfoLock = new Object(); 64 private final Context mContext; 65 private final int mMaxBitmapDimension; 66 private MetadataEditor mMetadataEditor; 67 68 private MediaSessionManager mSessionManager; 69 private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener; 70 private MediaController.Callback mSessionCb = new MediaControllerCallback(); 71 72 /** 73 * Synchronized on mInfoLock 74 */ 75 private boolean mIsRegistered = false; 76 private OnClientUpdateListener mOnClientUpdateListener; 77 private PlaybackInfo mLastPlaybackInfo; 78 private int mArtworkWidth = -1; 79 private int mArtworkHeight = -1; 80 private boolean mEnabled = true; 81 // synchronized on mInfoLock, for USE_SESSION apis. 82 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 83 private MediaController mCurrentSession; 84 85 /** 86 * Class constructor. 87 * @param context the {@link Context}, must be non-null. 88 * @param updateListener the listener to be called whenever new client information is available, 89 * must be non-null. 90 * @throws IllegalArgumentException 91 */ RemoteController(Context context, OnClientUpdateListener updateListener)92 public RemoteController(Context context, OnClientUpdateListener updateListener) 93 throws IllegalArgumentException { 94 this(context, updateListener, null); 95 } 96 97 /** 98 * Class constructor. 99 * @param context the {@link Context}, must be non-null. 100 * @param updateListener the listener to be called whenever new client information is available, 101 * must be non-null. 102 * @param looper the {@link Looper} on which to run the event loop, 103 * or null to use the current thread's looper. 104 * @throws java.lang.IllegalArgumentException 105 */ RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)106 public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper) 107 throws IllegalArgumentException { 108 if (context == null) { 109 throw new IllegalArgumentException("Invalid null Context"); 110 } 111 if (updateListener == null) { 112 throw new IllegalArgumentException("Invalid null OnClientUpdateListener"); 113 } 114 if (looper != null) { 115 mEventHandler = new EventHandler(this, looper); 116 } else { 117 Looper l = Looper.myLooper(); 118 if (l != null) { 119 mEventHandler = new EventHandler(this, l); 120 } else { 121 throw new IllegalArgumentException("Calling thread not associated with a looper"); 122 } 123 } 124 mOnClientUpdateListener = updateListener; 125 mContext = context; 126 mSessionManager = (MediaSessionManager) context 127 .getSystemService(Context.MEDIA_SESSION_SERVICE); 128 mSessionListener = new TopTransportSessionListener(); 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 * Return the estimated playback position of the current media track or a negative value 197 * if not available. 198 * 199 * <p>The value returned is estimated by the current process and may not be perfect. 200 * The time returned by this method is calculated from the last state change time based 201 * on the current play position at that time and the last known playback speed. 202 * An application may call {@link #setSynchronizationMode(int)} to apply 203 * a synchronization policy that will periodically re-sync the estimated position 204 * with the RemoteControlClient.</p> 205 * 206 * @return the current estimated playback position in milliseconds or a negative value 207 * if not available 208 * 209 * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float) 210 */ getEstimatedMediaPosition()211 public long getEstimatedMediaPosition() { 212 synchronized (mInfoLock) { 213 if (mCurrentSession != null) { 214 PlaybackState state = mCurrentSession.getPlaybackState(); 215 if (state != null) { 216 return state.getPosition(); 217 } 218 } 219 } 220 return -1; 221 } 222 223 224 /** 225 * Send a simulated key event for a media button to be received by the current client. 226 * To simulate a key press, you must first send a KeyEvent built with 227 * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} 228 * action. 229 * <p>The key event will be sent to the registered receiver 230 * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated 231 * {@link RemoteControlClient}'s metadata and playback state is published (there may be 232 * none under some circumstances). 233 * @param keyEvent a {@link KeyEvent} instance whose key code is one of 234 * {@link KeyEvent#KEYCODE_MUTE}, 235 * {@link KeyEvent#KEYCODE_HEADSETHOOK}, 236 * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, 237 * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, 238 * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, 239 * {@link KeyEvent#KEYCODE_MEDIA_STOP}, 240 * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, 241 * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, 242 * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, 243 * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, 244 * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, 245 * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, 246 * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, 247 * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. 248 * @return true if the event was successfully sent, false otherwise. 249 * @throws IllegalArgumentException 250 */ sendMediaKeyEvent(KeyEvent keyEvent)251 public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException { 252 if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { 253 throw new IllegalArgumentException("not a media key event"); 254 } 255 synchronized (mInfoLock) { 256 if (mCurrentSession != null) { 257 return mCurrentSession.dispatchMediaButtonEvent(keyEvent); 258 } 259 return false; 260 } 261 } 262 263 264 /** 265 * Sets the new playback position. 266 * This method can only be called on a registered RemoteController. 267 * @param timeMs a 0 or positive value for the new playback position, expressed in ms. 268 * @return true if the command to set the playback position was successfully sent. 269 * @throws IllegalArgumentException 270 */ seekTo(long timeMs)271 public boolean seekTo(long timeMs) throws IllegalArgumentException { 272 if (!mEnabled) { 273 Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController"); 274 return false; 275 } 276 if (timeMs < 0) { 277 throw new IllegalArgumentException("illegal negative time value"); 278 } 279 synchronized (mInfoLock) { 280 if (mCurrentSession != null) { 281 mCurrentSession.getTransportControls().seekTo(timeMs); 282 } 283 } 284 return true; 285 } 286 287 288 /** 289 * @hide 290 * @param wantBitmap 291 * @param width 292 * @param height 293 * @return true if successful 294 * @throws IllegalArgumentException 295 */ 296 @UnsupportedAppUsage setArtworkConfiguration(boolean wantBitmap, int width, int height)297 public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height) 298 throws IllegalArgumentException { 299 synchronized (mInfoLock) { 300 if (wantBitmap) { 301 if ((width > 0) && (height > 0)) { 302 if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; } 303 if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; } 304 mArtworkWidth = width; 305 mArtworkHeight = height; 306 } else { 307 throw new IllegalArgumentException("Invalid dimensions"); 308 } 309 } else { 310 mArtworkWidth = -1; 311 mArtworkHeight = -1; 312 } 313 } 314 return true; 315 } 316 317 /** 318 * Set the maximum artwork image dimensions to be received in the metadata. 319 * No bitmaps will be received unless this has been specified. 320 * @param width the maximum width in pixels 321 * @param height the maximum height in pixels 322 * @return true if the artwork dimension was successfully set. 323 * @throws IllegalArgumentException 324 */ setArtworkConfiguration(int width, int height)325 public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException { 326 return setArtworkConfiguration(true, width, height); 327 } 328 329 /** 330 * Prevents this RemoteController from receiving artwork images. 331 * @return true if receiving artwork images was successfully disabled. 332 */ clearArtworkConfiguration()333 public boolean clearArtworkConfiguration() { 334 return setArtworkConfiguration(false, -1, -1); 335 } 336 337 338 /** 339 * Default playback position synchronization mode where the RemoteControlClient is not 340 * asked regularly for its playback position to see if it has drifted from the estimated 341 * position. 342 */ 343 public static final int POSITION_SYNCHRONIZATION_NONE = 0; 344 345 /** 346 * The playback position synchronization mode where the RemoteControlClient instances which 347 * expose their playback position to the framework, will be regularly polled to check 348 * whether any drift has been noticed between their estimated position and the one they report. 349 * Note that this mode should only ever be used when needing to display very accurate playback 350 * position, as regularly polling a RemoteControlClient for its position may have an impact 351 * on battery life (if applicable) when this query will trigger network transactions in the 352 * case of remote playback. 353 */ 354 public static final int POSITION_SYNCHRONIZATION_CHECK = 1; 355 356 /** 357 * Set the playback position synchronization mode. 358 * Must be called on a registered RemoteController. 359 * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK} 360 * @return true if the synchronization mode was successfully set. 361 * @throws IllegalArgumentException 362 */ setSynchronizationMode(int sync)363 public boolean setSynchronizationMode(int sync) throws IllegalArgumentException { 364 if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) { 365 throw new IllegalArgumentException("Unknown synchronization mode " + sync); 366 } 367 if (!mIsRegistered) { 368 Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController"); 369 return false; 370 } 371 // deprecated, no-op 372 return true; 373 } 374 375 376 /** 377 * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of 378 * the current {@link RemoteControlClient}. 379 * This method can only be called on a registered RemoteController. 380 * @return a new MetadataEditor instance. 381 */ editMetadata()382 public MetadataEditor editMetadata() { 383 MetadataEditor editor = new MetadataEditor(); 384 editor.mEditorMetadata = new Bundle(); 385 editor.mEditorArtwork = null; 386 editor.mMetadataChanged = true; 387 editor.mArtworkChanged = true; 388 editor.mEditableKeys = 0; 389 return editor; 390 } 391 392 /** 393 * A class to read the metadata published by a {@link RemoteControlClient}, or send a 394 * {@link RemoteControlClient} new values for keys that can be edited. 395 */ 396 public class MetadataEditor extends MediaMetadataEditor { 397 /** 398 * @hide 399 */ MetadataEditor()400 protected MetadataEditor() { } 401 402 /** 403 * @hide 404 */ MetadataEditor(Bundle metadata, long editableKeys)405 protected MetadataEditor(Bundle metadata, long editableKeys) { 406 mEditorMetadata = metadata; 407 mEditableKeys = editableKeys; 408 409 mEditorArtwork = (Bitmap) metadata.getParcelable( 410 String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)); 411 if (mEditorArtwork != null) { 412 cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); 413 } 414 415 mMetadataChanged = true; 416 mArtworkChanged = true; 417 mApplied = false; 418 } 419 cleanupBitmapFromBundle(int key)420 private void cleanupBitmapFromBundle(int key) { 421 if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) { 422 mEditorMetadata.remove(String.valueOf(key)); 423 } 424 } 425 426 /** 427 * Applies all of the metadata changes that have been set since the MediaMetadataEditor 428 * instance was created with {@link RemoteController#editMetadata()} 429 * or since {@link #clear()} was called. 430 */ apply()431 public synchronized void apply() { 432 // "applying" a metadata bundle in RemoteController is only for sending edited 433 // key values back to the RemoteControlClient, so here we only care about the only 434 // editable key we support: RATING_KEY_BY_USER 435 if (!mMetadataChanged) { 436 return; 437 } 438 synchronized (mInfoLock) { 439 if (mCurrentSession != null) { 440 if (mEditorMetadata.containsKey( 441 String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) { 442 Rating rating = (Rating) getObject( 443 MediaMetadataEditor.RATING_KEY_BY_USER, null); 444 if (rating != null) { 445 mCurrentSession.getTransportControls().setRating(rating); 446 } 447 } 448 } 449 } 450 // NOT setting mApplied to true as this type of MetadataEditor will be applied 451 // multiple times, whenever the user of a RemoteController needs to change the 452 // metadata (e.g. user changes the rating of a song more than once during playback) 453 mApplied = false; 454 } 455 456 } 457 458 /** 459 * This receives updates when the current session changes. This is 460 * registered to receive the updates on the handler thread so it can call 461 * directly into the appropriate methods. 462 */ 463 private class MediaControllerCallback extends MediaController.Callback { 464 @Override onPlaybackStateChanged(PlaybackState state)465 public void onPlaybackStateChanged(PlaybackState state) { 466 onNewPlaybackState(state); 467 } 468 469 @Override onMetadataChanged(MediaMetadata metadata)470 public void onMetadataChanged(MediaMetadata metadata) { 471 onNewMediaMetadata(metadata); 472 } 473 } 474 475 /** 476 * Listens for changes to the active session stack and replaces the 477 * currently tracked session if it has changed. 478 */ 479 private class TopTransportSessionListener implements 480 MediaSessionManager.OnActiveSessionsChangedListener { 481 482 @Override onActiveSessionsChanged(List<MediaController> controllers)483 public void onActiveSessionsChanged(List<MediaController> controllers) { 484 int size = controllers.size(); 485 for (int i = 0; i < size; i++) { 486 MediaController controller = controllers.get(i); 487 long flags = controller.getFlags(); 488 // We only care about sessions that handle transport controls, 489 // which will be true for apps using RCC 490 if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) { 491 updateController(controller); 492 return; 493 } 494 } 495 updateController(null); 496 } 497 498 } 499 500 //================================================== 501 // Event handling 502 private final EventHandler mEventHandler; 503 private final static int MSG_CLIENT_CHANGE = 0; 504 private final static int MSG_NEW_PLAYBACK_STATE = 1; 505 private final static int MSG_NEW_MEDIA_METADATA = 2; 506 507 private class EventHandler extends Handler { 508 EventHandler(RemoteController rc, Looper looper)509 public EventHandler(RemoteController rc, Looper looper) { 510 super(looper); 511 } 512 513 @Override handleMessage(Message msg)514 public void handleMessage(Message msg) { 515 switch(msg.what) { 516 case MSG_CLIENT_CHANGE: 517 onClientChange(msg.arg2 == 1); 518 break; 519 case MSG_NEW_PLAYBACK_STATE: 520 onNewPlaybackState((PlaybackState) msg.obj); 521 break; 522 case MSG_NEW_MEDIA_METADATA: 523 onNewMediaMetadata((MediaMetadata) msg.obj); 524 break; 525 default: 526 Log.e(TAG, "unknown event " + msg.what); 527 } 528 } 529 } 530 531 /** 532 * @hide 533 */ startListeningToSessions()534 void startListeningToSessions() { 535 final ComponentName listenerComponent = new ComponentName(mContext, 536 mOnClientUpdateListener.getClass()); 537 Handler handler = null; 538 if (Looper.myLooper() == null) { 539 handler = new Handler(Looper.getMainLooper()); 540 } 541 mSessionManager.addOnActiveSessionsChangedListener(mSessionListener, listenerComponent, 542 handler); 543 mSessionListener.onActiveSessionsChanged(mSessionManager 544 .getActiveSessions(listenerComponent)); 545 if (DEBUG) { 546 Log.d(TAG, "Registered session listener with component " + listenerComponent 547 + " for user " + UserHandle.myUserId()); 548 } 549 } 550 551 /** 552 * @hide 553 */ stopListeningToSessions()554 void stopListeningToSessions() { 555 mSessionManager.removeOnActiveSessionsChangedListener(mSessionListener); 556 if (DEBUG) { 557 Log.d(TAG, "Unregistered session listener for user " 558 + UserHandle.myUserId()); 559 } 560 } 561 562 /** If the msg is already queued, replace it with this one. */ 563 private static final int SENDMSG_REPLACE = 0; 564 /** If the msg is already queued, ignore this one and leave the old. */ 565 private static final int SENDMSG_NOOP = 1; 566 /** If the msg is already queued, queue this one and leave the old. */ 567 private static final int SENDMSG_QUEUE = 2; 568 sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delayMs)569 private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, 570 int arg1, int arg2, Object obj, int delayMs) { 571 if (handler == null) { 572 Log.e(TAG, "null event handler, will not deliver message " + msg); 573 return; 574 } 575 if (existingMsgPolicy == SENDMSG_REPLACE) { 576 handler.removeMessages(msg); 577 } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { 578 return; 579 } 580 handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs); 581 } 582 onClientChange(boolean clearing)583 private void onClientChange(boolean clearing) { 584 final OnClientUpdateListener l; 585 synchronized(mInfoLock) { 586 l = mOnClientUpdateListener; 587 mMetadataEditor = null; 588 } 589 if (l != null) { 590 l.onClientChange(clearing); 591 } 592 } 593 updateController(MediaController controller)594 private void updateController(MediaController controller) { 595 if (DEBUG) { 596 Log.d(TAG, "Updating controller to " + controller + " previous controller is " 597 + mCurrentSession); 598 } 599 synchronized (mInfoLock) { 600 if (controller == null) { 601 if (mCurrentSession != null) { 602 mCurrentSession.unregisterCallback(mSessionCb); 603 mCurrentSession = null; 604 sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, 605 0 /* arg1 ignored */, 1 /* clearing */, null /* obj */, 0 /* delay */); 606 } 607 } else if (mCurrentSession == null 608 || !controller.getSessionToken() 609 .equals(mCurrentSession.getSessionToken())) { 610 if (mCurrentSession != null) { 611 mCurrentSession.unregisterCallback(mSessionCb); 612 } 613 sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, 614 0 /* arg1 ignored */, 0 /* clearing */, null /* obj */, 0 /* delay */); 615 mCurrentSession = controller; 616 mCurrentSession.registerCallback(mSessionCb, mEventHandler); 617 618 PlaybackState state = controller.getPlaybackState(); 619 sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE, 620 0 /* arg1 ignored */, 0 /* arg2 ignored */, state /* obj */, 0 /* delay */); 621 622 MediaMetadata metadata = controller.getMetadata(); 623 sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE, 624 0 /* arg1 ignored */, 0 /* arg2 ignored*/, metadata /* obj */, 0 /*delay*/); 625 } 626 // else same controller, no need to update 627 } 628 } 629 onNewPlaybackState(PlaybackState state)630 private void onNewPlaybackState(PlaybackState state) { 631 final OnClientUpdateListener l; 632 synchronized (mInfoLock) { 633 l = this.mOnClientUpdateListener; 634 } 635 if (l != null) { 636 int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE 637 : RemoteControlClient.getRccStateFromState(state.getState()); 638 if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) { 639 l.onClientPlaybackStateUpdate(playstate); 640 } else { 641 l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(), 642 state.getPosition(), state.getPlaybackSpeed()); 643 } 644 if (state != null) { 645 l.onClientTransportControlUpdate( 646 RemoteControlClient.getRccControlFlagsFromActions(state.getActions())); 647 } 648 } 649 } 650 onNewMediaMetadata(MediaMetadata metadata)651 private void onNewMediaMetadata(MediaMetadata metadata) { 652 if (metadata == null) { 653 // RemoteController only handles non-null metadata 654 return; 655 } 656 final OnClientUpdateListener l; 657 final MetadataEditor metadataEditor; 658 // prepare the received Bundle to be used inside a MetadataEditor 659 synchronized(mInfoLock) { 660 l = mOnClientUpdateListener; 661 boolean canRate = mCurrentSession != null 662 && mCurrentSession.getRatingType() != Rating.RATING_NONE; 663 long editableKeys = canRate ? MediaMetadataEditor.RATING_KEY_BY_USER : 0; 664 Bundle legacyMetadata = MediaSessionLegacyHelper.getOldMetadata(metadata, 665 mArtworkWidth, mArtworkHeight); 666 mMetadataEditor = new MetadataEditor(legacyMetadata, editableKeys); 667 metadataEditor = mMetadataEditor; 668 } 669 if (l != null) { 670 l.onClientMetadataUpdate(metadataEditor); 671 } 672 } 673 674 //================================================== 675 private static class PlaybackInfo { 676 int mState; 677 long mStateChangeTimeMs; 678 long mCurrentPosMs; 679 float mSpeed; 680 PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed)681 PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) { 682 mState = state; 683 mStateChangeTimeMs = stateChangeTimeMs; 684 mCurrentPosMs = currentPosMs; 685 mSpeed = speed; 686 } 687 } 688 689 /** 690 * @hide 691 * Used by AudioManager to access user listener receiving the client update notifications 692 * @return 693 */ 694 @UnsupportedAppUsage getUpdateListener()695 OnClientUpdateListener getUpdateListener() { 696 return mOnClientUpdateListener; 697 } 698 } 699