• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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