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