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