• 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.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