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.app.PendingIntent;
21 import android.app.PendingIntent.CanceledException;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.graphics.Bitmap;
26 import android.media.session.MediaController;
27 import android.media.session.MediaSession;
28 import android.media.session.MediaSessionLegacyHelper;
29 import android.media.session.MediaSessionManager;
30 import android.media.session.PlaybackState;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.SystemClock;
36 import android.os.UserHandle;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.view.KeyEvent;
40 
41 import java.lang.ref.WeakReference;
42 import java.util.List;
43 
44 /**
45  * The RemoteController class is used to control media playback, display and update media metadata
46  * and playback status, published by applications using the {@link RemoteControlClient} class.
47  * <p>
48  * A RemoteController shall be registered through
49  * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
50  * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor.
51  * Implement the methods of the interface to receive the information published by the active
52  * {@link RemoteControlClient} instances.
53  * <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for
54  * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
55  * <p>
56  * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled
57  * notification listeners (see {@link android.service.notification.NotificationListenerService}).
58  *
59  * @deprecated Use {@link MediaController} instead.
60  */
61 @Deprecated public final class RemoteController
62 {
63     private final static int MAX_BITMAP_DIMENSION = 512;
64     private final static int TRANSPORT_UNKNOWN = 0;
65     private final static String TAG = "RemoteController";
66     private final static boolean DEBUG = false;
67     private final static boolean USE_SESSIONS = true;
68     private final static Object mGenLock = new Object();
69     private final static Object mInfoLock = new Object();
70     private final RcDisplay mRcd;
71     private final Context mContext;
72     private final AudioManager mAudioManager;
73     private final int mMaxBitmapDimension;
74     private MetadataEditor mMetadataEditor;
75 
76     private MediaSessionManager mSessionManager;
77     private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener;
78     private MediaController.Callback mSessionCb = new MediaControllerCallback();
79 
80     /**
81      * Synchronized on mGenLock
82      */
83     private int mClientGenerationIdCurrent = 0;
84 
85     /**
86      * Synchronized on mInfoLock
87      */
88     private boolean mIsRegistered = false;
89     private PendingIntent mClientPendingIntentCurrent;
90     private OnClientUpdateListener mOnClientUpdateListener;
91     private PlaybackInfo mLastPlaybackInfo;
92     private int mArtworkWidth = -1;
93     private int mArtworkHeight = -1;
94     private boolean mEnabled = true;
95     // synchronized on mInfoLock, for USE_SESSION apis.
96     private MediaController mCurrentSession;
97 
98     /**
99      * Class constructor.
100      * @param context the {@link Context}, must be non-null.
101      * @param updateListener the listener to be called whenever new client information is available,
102      *     must be non-null.
103      * @throws IllegalArgumentException
104      */
RemoteController(Context context, OnClientUpdateListener updateListener)105     public RemoteController(Context context, OnClientUpdateListener updateListener)
106             throws IllegalArgumentException {
107         this(context, updateListener, null);
108     }
109 
110     /**
111      * Class constructor.
112      * @param context the {@link Context}, must be non-null.
113      * @param updateListener the listener to be called whenever new client information is available,
114      *     must be non-null.
115      * @param looper the {@link Looper} on which to run the event loop,
116      *     or null to use the current thread's looper.
117      * @throws java.lang.IllegalArgumentException
118      */
RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)119     public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)
120             throws IllegalArgumentException {
121         if (context == null) {
122             throw new IllegalArgumentException("Invalid null Context");
123         }
124         if (updateListener == null) {
125             throw new IllegalArgumentException("Invalid null OnClientUpdateListener");
126         }
127         if (looper != null) {
128             mEventHandler = new EventHandler(this, looper);
129         } else {
130             Looper l = Looper.myLooper();
131             if (l != null) {
132                 mEventHandler = new EventHandler(this, l);
133             } else {
134                 throw new IllegalArgumentException("Calling thread not associated with a looper");
135             }
136         }
137         mOnClientUpdateListener = updateListener;
138         mContext = context;
139         mRcd = new RcDisplay(this);
140         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
141         mSessionManager = (MediaSessionManager) context
142                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
143         mSessionListener = new TopTransportSessionListener();
144 
145         if (ActivityManager.isLowRamDeviceStatic()) {
146             mMaxBitmapDimension = MAX_BITMAP_DIMENSION;
147         } else {
148             final DisplayMetrics dm = context.getResources().getDisplayMetrics();
149             mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels);
150         }
151     }
152 
153 
154     /**
155      * Interface definition for the callbacks to be invoked whenever media events, metadata
156      * and playback status are available.
157      */
158     public interface OnClientUpdateListener {
159         /**
160          * Called whenever all information, previously received through the other
161          * methods of the listener, is no longer valid and is about to be refreshed.
162          * This is typically called whenever a new {@link RemoteControlClient} has been selected
163          * by the system to have its media information published.
164          * @param clearing true if there is no selected RemoteControlClient and no information
165          *     is available.
166          */
onClientChange(boolean clearing)167         public void onClientChange(boolean clearing);
168 
169         /**
170          * Called whenever the playback state has changed.
171          * It is called when no information is known about the playback progress in the media and
172          * the playback speed.
173          * @param state one of the playback states authorized
174          *     in {@link RemoteControlClient#setPlaybackState(int)}.
175          */
onClientPlaybackStateUpdate(int state)176         public void onClientPlaybackStateUpdate(int state);
177         /**
178          * Called whenever the playback state has changed, and playback position
179          * and speed are known.
180          * @param state one of the playback states authorized
181          *     in {@link RemoteControlClient#setPlaybackState(int)}.
182          * @param stateChangeTimeMs the system time at which the state change was reported,
183          *     expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}.
184          * @param currentPosMs a positive value for the current media playback position expressed
185          *     in ms, a negative value if the position is temporarily unknown.
186          * @param speed  a value expressed as a ratio of 1x playback: 1.0f is normal playback,
187          *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
188          *    playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
189          */
onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed)190         public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
191                 long currentPosMs, float speed);
192         /**
193          * Called whenever the transport control flags have changed.
194          * @param transportControlFlags one of the flags authorized
195          *     in {@link RemoteControlClient#setTransportControlFlags(int)}.
196          */
onClientTransportControlUpdate(int transportControlFlags)197         public void onClientTransportControlUpdate(int transportControlFlags);
198         /**
199          * Called whenever new metadata is available.
200          * See the {@link MediaMetadataEditor#putLong(int, long)},
201          *  {@link MediaMetadataEditor#putString(int, String)},
202          *  {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and
203          *  {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that
204          *  can be queried.
205          * @param metadataEditor the container of the new metadata.
206          */
onClientMetadataUpdate(MetadataEditor metadataEditor)207         public void onClientMetadataUpdate(MetadataEditor metadataEditor);
208     };
209 
210 
211     /**
212      * @hide
213      */
getRemoteControlClientPackageName()214     public String getRemoteControlClientPackageName() {
215         if (USE_SESSIONS) {
216             synchronized (mInfoLock) {
217                 return mCurrentSession != null ? mCurrentSession.getPackageName()
218                         : null;
219             }
220         } else {
221             return mClientPendingIntentCurrent != null ?
222                     mClientPendingIntentCurrent.getCreatorPackage() : null;
223         }
224     }
225 
226     /**
227      * Return the estimated playback position of the current media track or a negative value
228      * if not available.
229      *
230      * <p>The value returned is estimated by the current process and may not be perfect.
231      * The time returned by this method is calculated from the last state change time based
232      * on the current play position at that time and the last known playback speed.
233      * An application may call {@link #setSynchronizationMode(int)} to apply
234      * a synchronization policy that will periodically re-sync the estimated position
235      * with the RemoteControlClient.</p>
236      *
237      * @return the current estimated playback position in milliseconds or a negative value
238      *         if not available
239      *
240      * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
241      */
getEstimatedMediaPosition()242     public long getEstimatedMediaPosition() {
243         if (USE_SESSIONS) {
244             synchronized (mInfoLock) {
245                 if (mCurrentSession != null) {
246                     PlaybackState state = mCurrentSession.getPlaybackState();
247                     if (state != null) {
248                         return state.getPosition();
249                     }
250                 }
251             }
252         } else {
253             final PlaybackInfo lastPlaybackInfo;
254             synchronized (mInfoLock) {
255                 lastPlaybackInfo = mLastPlaybackInfo;
256             }
257             if (lastPlaybackInfo != null) {
258                 if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
259                     return lastPlaybackInfo.mCurrentPosMs;
260                 }
261 
262                 // Take the current position at the time of state change and
263                 // estimate.
264                 final long thenPos = lastPlaybackInfo.mCurrentPosMs;
265                 if (thenPos < 0) {
266                     return -1;
267                 }
268 
269                 final long now = SystemClock.elapsedRealtime();
270                 final long then = lastPlaybackInfo.mStateChangeTimeMs;
271                 final long sinceThen = now - then;
272                 final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
273                 return thenPos + scaledSinceThen;
274             }
275         }
276         return -1;
277     }
278 
279 
280     /**
281      * Send a simulated key event for a media button to be received by the current client.
282      * To simulate a key press, you must first send a KeyEvent built with
283      * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP}
284      * action.
285      * <p>The key event will be sent to the registered receiver
286      * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated
287      * {@link RemoteControlClient}'s metadata and playback state is published (there may be
288      * none under some circumstances).
289      * @param keyEvent a {@link KeyEvent} instance whose key code is one of
290      *     {@link KeyEvent#KEYCODE_MUTE},
291      *     {@link KeyEvent#KEYCODE_HEADSETHOOK},
292      *     {@link KeyEvent#KEYCODE_MEDIA_PLAY},
293      *     {@link KeyEvent#KEYCODE_MEDIA_PAUSE},
294      *     {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE},
295      *     {@link KeyEvent#KEYCODE_MEDIA_STOP},
296      *     {@link KeyEvent#KEYCODE_MEDIA_NEXT},
297      *     {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS},
298      *     {@link KeyEvent#KEYCODE_MEDIA_REWIND},
299      *     {@link KeyEvent#KEYCODE_MEDIA_RECORD},
300      *     {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD},
301      *     {@link KeyEvent#KEYCODE_MEDIA_CLOSE},
302      *     {@link KeyEvent#KEYCODE_MEDIA_EJECT},
303      *     or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
304      * @return true if the event was successfully sent, false otherwise.
305      * @throws IllegalArgumentException
306      */
sendMediaKeyEvent(KeyEvent keyEvent)307     public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
308         if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
309             throw new IllegalArgumentException("not a media key event");
310         }
311         if (USE_SESSIONS) {
312             synchronized (mInfoLock) {
313                 if (mCurrentSession != null) {
314                     return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
315                 }
316                 return false;
317             }
318         } else {
319             final PendingIntent pi;
320             synchronized (mInfoLock) {
321                 if (!mIsRegistered) {
322                     Log.e(TAG,
323                             "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
324                     return false;
325                 }
326                 if (!mEnabled) {
327                     Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
328                     return false;
329                 }
330                 pi = mClientPendingIntentCurrent;
331             }
332             if (pi != null) {
333                 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
334                 intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
335                 try {
336                     pi.send(mContext, 0, intent);
337                 } catch (CanceledException e) {
338                     Log.e(TAG, "Error sending intent for media button down: ", e);
339                     return false;
340                 }
341             } else {
342                 Log.i(TAG, "No-op when sending key click, no receiver right now");
343                 return false;
344             }
345         }
346         return true;
347     }
348 
349 
350     /**
351      * Sets the new playback position.
352      * This method can only be called on a registered RemoteController.
353      * @param timeMs a 0 or positive value for the new playback position, expressed in ms.
354      * @return true if the command to set the playback position was successfully sent.
355      * @throws IllegalArgumentException
356      */
seekTo(long timeMs)357     public boolean seekTo(long timeMs) throws IllegalArgumentException {
358         if (!mEnabled) {
359             Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController");
360             return false;
361         }
362         if (timeMs < 0) {
363             throw new IllegalArgumentException("illegal negative time value");
364         }
365         synchronized (mInfoLock) {
366             if (mCurrentSession != null) {
367                 mCurrentSession.getTransportControls().seekTo(timeMs);
368             }
369         }
370         return true;
371     }
372 
373 
374     /**
375      * @hide
376      * @param wantBitmap
377      * @param width
378      * @param height
379      * @return true if successful
380      * @throws IllegalArgumentException
381      */
setArtworkConfiguration(boolean wantBitmap, int width, int height)382     public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height)
383             throws IllegalArgumentException {
384         synchronized (mInfoLock) {
385             if (wantBitmap) {
386                 if ((width > 0) && (height > 0)) {
387                     if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; }
388                     if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; }
389                     mArtworkWidth = width;
390                     mArtworkHeight = height;
391                 } else {
392                     throw new IllegalArgumentException("Invalid dimensions");
393                 }
394             } else {
395                 mArtworkWidth = -1;
396                 mArtworkHeight = -1;
397             }
398         }
399         return true;
400     }
401 
402     /**
403      * Set the maximum artwork image dimensions to be received in the metadata.
404      * No bitmaps will be received unless this has been specified.
405      * @param width the maximum width in pixels
406      * @param height  the maximum height in pixels
407      * @return true if the artwork dimension was successfully set.
408      * @throws IllegalArgumentException
409      */
setArtworkConfiguration(int width, int height)410     public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException {
411         return setArtworkConfiguration(true, width, height);
412     }
413 
414     /**
415      * Prevents this RemoteController from receiving artwork images.
416      * @return true if receiving artwork images was successfully disabled.
417      */
clearArtworkConfiguration()418     public boolean clearArtworkConfiguration() {
419         return setArtworkConfiguration(false, -1, -1);
420     }
421 
422 
423     /**
424      * Default playback position synchronization mode where the RemoteControlClient is not
425      * asked regularly for its playback position to see if it has drifted from the estimated
426      * position.
427      */
428     public static final int POSITION_SYNCHRONIZATION_NONE = 0;
429 
430     /**
431      * The playback position synchronization mode where the RemoteControlClient instances which
432      * expose their playback position to the framework, will be regularly polled to check
433      * whether any drift has been noticed between their estimated position and the one they report.
434      * Note that this mode should only ever be used when needing to display very accurate playback
435      * position, as regularly polling a RemoteControlClient for its position may have an impact
436      * on battery life (if applicable) when this query will trigger network transactions in the
437      * case of remote playback.
438      */
439     public static final int POSITION_SYNCHRONIZATION_CHECK = 1;
440 
441     /**
442      * Set the playback position synchronization mode.
443      * Must be called on a registered RemoteController.
444      * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK}
445      * @return true if the synchronization mode was successfully set.
446      * @throws IllegalArgumentException
447      */
setSynchronizationMode(int sync)448     public boolean setSynchronizationMode(int sync) throws IllegalArgumentException {
449         if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) {
450             throw new IllegalArgumentException("Unknown synchronization mode " + sync);
451         }
452         if (!mIsRegistered) {
453             Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
454             return false;
455         }
456         mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
457                 POSITION_SYNCHRONIZATION_CHECK == sync);
458         return true;
459     }
460 
461 
462     /**
463      * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of
464      * the current {@link RemoteControlClient}.
465      * This method can only be called on a registered RemoteController.
466      * @return a new MetadataEditor instance.
467      */
editMetadata()468     public MetadataEditor editMetadata() {
469         MetadataEditor editor = new MetadataEditor();
470         editor.mEditorMetadata = new Bundle();
471         editor.mEditorArtwork = null;
472         editor.mMetadataChanged = true;
473         editor.mArtworkChanged = true;
474         editor.mEditableKeys = 0;
475         return editor;
476     }
477 
478     /**
479      * A class to read the metadata published by a {@link RemoteControlClient}, or send a
480      * {@link RemoteControlClient} new values for keys that can be edited.
481      */
482     public class MetadataEditor extends MediaMetadataEditor {
483         /**
484          * @hide
485          */
MetadataEditor()486         protected MetadataEditor() { }
487 
488         /**
489          * @hide
490          */
MetadataEditor(Bundle metadata, long editableKeys)491         protected MetadataEditor(Bundle metadata, long editableKeys) {
492             mEditorMetadata = metadata;
493             mEditableKeys = editableKeys;
494 
495             mEditorArtwork = (Bitmap) metadata.getParcelable(
496                     String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK));
497             if (mEditorArtwork != null) {
498                 cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
499             }
500 
501             mMetadataChanged = true;
502             mArtworkChanged = true;
503             mApplied = false;
504         }
505 
cleanupBitmapFromBundle(int key)506         private void cleanupBitmapFromBundle(int key) {
507             if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) {
508                 mEditorMetadata.remove(String.valueOf(key));
509             }
510         }
511 
512         /**
513          * Applies all of the metadata changes that have been set since the MediaMetadataEditor
514          * instance was created with {@link RemoteController#editMetadata()}
515          * or since {@link #clear()} was called.
516          */
apply()517         public synchronized void apply() {
518             // "applying" a metadata bundle in RemoteController is only for sending edited
519             // key values back to the RemoteControlClient, so here we only care about the only
520             // editable key we support: RATING_KEY_BY_USER
521             if (!mMetadataChanged) {
522                 return;
523             }
524             synchronized (mInfoLock) {
525                 if (mCurrentSession != null) {
526                     if (mEditorMetadata.containsKey(
527                             String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
528                         Rating rating = (Rating) getObject(
529                                 MediaMetadataEditor.RATING_KEY_BY_USER, null);
530                         if (rating != null) {
531                             mCurrentSession.getTransportControls().setRating(rating);
532                         }
533                     }
534                 }
535             }
536             // NOT setting mApplied to true as this type of MetadataEditor will be applied
537             // multiple times, whenever the user of a RemoteController needs to change the
538             // metadata (e.g. user changes the rating of a song more than once during playback)
539             mApplied = false;
540         }
541 
542     }
543 
544 
545     //==================================================
546     // Implementation of IRemoteControlDisplay interface
547     private static class RcDisplay extends IRemoteControlDisplay.Stub {
548         private final WeakReference<RemoteController> mController;
549 
RcDisplay(RemoteController rc)550         RcDisplay(RemoteController rc) {
551             mController = new WeakReference<RemoteController>(rc);
552         }
553 
setCurrentClientId(int genId, PendingIntent clientMediaIntent, boolean clearing)554         public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
555                 boolean clearing) {
556             final RemoteController rc = mController.get();
557             if (rc == null) {
558                 return;
559             }
560             boolean isNew = false;
561             synchronized(mGenLock) {
562                 if (rc.mClientGenerationIdCurrent != genId) {
563                     rc.mClientGenerationIdCurrent = genId;
564                     isNew = true;
565                 }
566             }
567             if (clientMediaIntent != null) {
568                 sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
569                         genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
570             }
571             if (isNew || clearing) {
572                 sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
573                         genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
574             }
575         }
576 
setEnabled(boolean enabled)577         public void setEnabled(boolean enabled) {
578             final RemoteController rc = mController.get();
579             if (rc == null) {
580                 return;
581             }
582             sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE,
583                     enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/);
584         }
585 
setPlaybackState(int genId, int state, long stateChangeTimeMs, long currentPosMs, float speed)586         public void setPlaybackState(int genId, int state,
587                 long stateChangeTimeMs, long currentPosMs, float speed) {
588             final RemoteController rc = mController.get();
589             if (rc == null) {
590                 return;
591             }
592             if (DEBUG) {
593                 Log.d(TAG, "> new playback state: genId="+genId
594                         + " state="+ state
595                         + " changeTime="+ stateChangeTimeMs
596                         + " pos=" + currentPosMs
597                         + "ms speed=" + speed);
598             }
599 
600             synchronized(mGenLock) {
601                 if (rc.mClientGenerationIdCurrent != genId) {
602                     return;
603                 }
604             }
605             final PlaybackInfo playbackInfo =
606                     new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
607             sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
608                     genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
609 
610         }
611 
setTransportControlInfo(int genId, int transportControlFlags, int posCapabilities)612         public void setTransportControlInfo(int genId, int transportControlFlags,
613                 int posCapabilities) {
614             final RemoteController rc = mController.get();
615             if (rc == null) {
616                 return;
617             }
618             synchronized(mGenLock) {
619                 if (rc.mClientGenerationIdCurrent != genId) {
620                     return;
621                 }
622             }
623             sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
624                     genId /*arg1*/, transportControlFlags /*arg2*/,
625                     null /*obj*/, 0 /*delay*/);
626         }
627 
setMetadata(int genId, Bundle metadata)628         public void setMetadata(int genId, Bundle metadata) {
629             final RemoteController rc = mController.get();
630             if (rc == null) {
631                 return;
632             }
633             if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
634             if (metadata == null) {
635                 return;
636             }
637             synchronized(mGenLock) {
638                 if (rc.mClientGenerationIdCurrent != genId) {
639                     return;
640                 }
641             }
642             sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
643                     genId /*arg1*/, 0 /*arg2*/,
644                     metadata /*obj*/, 0 /*delay*/);
645         }
646 
setArtwork(int genId, Bitmap artwork)647         public void setArtwork(int genId, Bitmap artwork) {
648             final RemoteController rc = mController.get();
649             if (rc == null) {
650                 return;
651             }
652             if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
653             synchronized(mGenLock) {
654                 if (rc.mClientGenerationIdCurrent != genId) {
655                     return;
656                 }
657             }
658             Bundle metadata = new Bundle(1);
659             metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
660             sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
661                     genId /*arg1*/, 0 /*arg2*/,
662                     metadata /*obj*/, 0 /*delay*/);
663         }
664 
setAllMetadata(int genId, Bundle metadata, Bitmap artwork)665         public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
666             final RemoteController rc = mController.get();
667             if (rc == null) {
668                 return;
669             }
670             if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
671             if ((metadata == null) && (artwork == null)) {
672                 return;
673             }
674             synchronized(mGenLock) {
675                 if (rc.mClientGenerationIdCurrent != genId) {
676                     return;
677                 }
678             }
679             if (metadata == null) {
680                 metadata = new Bundle(1);
681             }
682             if (artwork != null) {
683                 metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
684                         artwork);
685             }
686             sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
687                     genId /*arg1*/, 0 /*arg2*/,
688                     metadata /*obj*/, 0 /*delay*/);
689         }
690     }
691 
692     /**
693      * This receives updates when the current session changes. This is
694      * registered to receive the updates on the handler thread so it can call
695      * directly into the appropriate methods.
696      */
697     private class MediaControllerCallback extends MediaController.Callback {
698         @Override
onPlaybackStateChanged(PlaybackState state)699         public void onPlaybackStateChanged(PlaybackState state) {
700             onNewPlaybackState(state);
701         }
702 
703         @Override
onMetadataChanged(MediaMetadata metadata)704         public void onMetadataChanged(MediaMetadata metadata) {
705             onNewMediaMetadata(metadata);
706         }
707     }
708 
709     /**
710      * Listens for changes to the active session stack and replaces the
711      * currently tracked session if it has changed.
712      */
713     private class TopTransportSessionListener implements
714             MediaSessionManager.OnActiveSessionsChangedListener {
715 
716         @Override
onActiveSessionsChanged(List<MediaController> controllers)717         public void onActiveSessionsChanged(List<MediaController> controllers) {
718             int size = controllers.size();
719             for (int i = 0; i < size; i++) {
720                 MediaController controller = controllers.get(i);
721                 long flags = controller.getFlags();
722                 // We only care about sessions that handle transport controls,
723                 // which will be true for apps using RCC
724                 if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
725                     updateController(controller);
726                     return;
727                 }
728             }
729             updateController(null);
730         }
731 
732     }
733 
734     //==================================================
735     // Event handling
736     private final EventHandler mEventHandler;
737     private final static int MSG_NEW_PENDING_INTENT = 0;
738     private final static int MSG_NEW_PLAYBACK_INFO =  1;
739     private final static int MSG_NEW_TRANSPORT_INFO = 2;
740     private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
741     private final static int MSG_CLIENT_CHANGE      = 4;
742     private final static int MSG_DISPLAY_ENABLE     = 5;
743     private final static int MSG_NEW_PLAYBACK_STATE = 6;
744     private final static int MSG_NEW_MEDIA_METADATA = 7;
745 
746     private class EventHandler extends Handler {
747 
EventHandler(RemoteController rc, Looper looper)748         public EventHandler(RemoteController rc, Looper looper) {
749             super(looper);
750         }
751 
752         @Override
handleMessage(Message msg)753         public void handleMessage(Message msg) {
754             switch(msg.what) {
755                 case MSG_NEW_PENDING_INTENT:
756                     onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
757                     break;
758                 case MSG_NEW_PLAYBACK_INFO:
759                     onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
760                     break;
761                 case MSG_NEW_TRANSPORT_INFO:
762                     onNewTransportInfo(msg.arg1, msg.arg2);
763                     break;
764                 case MSG_NEW_METADATA:
765                     onNewMetadata(msg.arg1, (Bundle)msg.obj);
766                     break;
767                 case MSG_CLIENT_CHANGE:
768                     onClientChange(msg.arg1, msg.arg2 == 1);
769                     break;
770                 case MSG_DISPLAY_ENABLE:
771                     onDisplayEnable(msg.arg1 == 1);
772                     break;
773                 case MSG_NEW_PLAYBACK_STATE:
774                     // same as new playback info but using new apis
775                     onNewPlaybackState((PlaybackState) msg.obj);
776                     break;
777                 case MSG_NEW_MEDIA_METADATA:
778                     onNewMediaMetadata((MediaMetadata) msg.obj);
779                     break;
780                 default:
781                     Log.e(TAG, "unknown event " + msg.what);
782             }
783         }
784     }
785 
786     /**
787      * @hide
788      */
startListeningToSessions()789     void startListeningToSessions() {
790         final ComponentName listenerComponent = new ComponentName(mContext,
791                 mOnClientUpdateListener.getClass());
792         Handler handler = null;
793         if (Looper.myLooper() == null) {
794             handler = new Handler(Looper.getMainLooper());
795         }
796         mSessionManager.addOnActiveSessionsChangedListener(mSessionListener, listenerComponent,
797                 UserHandle.myUserId(), handler);
798         mSessionListener.onActiveSessionsChanged(mSessionManager
799                 .getActiveSessions(listenerComponent));
800         if (DEBUG) {
801             Log.d(TAG, "Registered session listener with component " + listenerComponent
802                     + " for user " + UserHandle.myUserId());
803         }
804     }
805 
806     /**
807      * @hide
808      */
stopListeningToSessions()809     void stopListeningToSessions() {
810         mSessionManager.removeOnActiveSessionsChangedListener(mSessionListener);
811         if (DEBUG) {
812             Log.d(TAG, "Unregistered session listener for user "
813                     + UserHandle.myUserId());
814         }
815     }
816 
817     /** If the msg is already queued, replace it with this one. */
818     private static final int SENDMSG_REPLACE = 0;
819     /** If the msg is already queued, ignore this one and leave the old. */
820     private static final int SENDMSG_NOOP = 1;
821     /** If the msg is already queued, queue this one and leave the old. */
822     private static final int SENDMSG_QUEUE = 2;
823 
sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delayMs)824     private static void sendMsg(Handler handler, int msg, int existingMsgPolicy,
825             int arg1, int arg2, Object obj, int delayMs) {
826         if (handler == null) {
827             Log.e(TAG, "null event handler, will not deliver message " + msg);
828             return;
829         }
830         if (existingMsgPolicy == SENDMSG_REPLACE) {
831             handler.removeMessages(msg);
832         } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
833             return;
834         }
835         handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
836     }
837 
838     ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
onNewPendingIntent(int genId, PendingIntent pi)839     private void onNewPendingIntent(int genId, PendingIntent pi) {
840         synchronized(mGenLock) {
841             if (mClientGenerationIdCurrent != genId) {
842                 return;
843             }
844         }
845         synchronized(mInfoLock) {
846             mClientPendingIntentCurrent = pi;
847         }
848     }
849 
onNewPlaybackInfo(int genId, PlaybackInfo pi)850     private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
851         synchronized(mGenLock) {
852             if (mClientGenerationIdCurrent != genId) {
853                 return;
854             }
855         }
856         final OnClientUpdateListener l;
857         synchronized(mInfoLock) {
858             l = this.mOnClientUpdateListener;
859             mLastPlaybackInfo = pi;
860         }
861         if (l != null) {
862             if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
863                 l.onClientPlaybackStateUpdate(pi.mState);
864             } else {
865                 l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
866                         pi.mSpeed);
867             }
868         }
869     }
870 
onNewTransportInfo(int genId, int transportControlFlags)871     private void onNewTransportInfo(int genId, int transportControlFlags) {
872         synchronized(mGenLock) {
873             if (mClientGenerationIdCurrent != genId) {
874                 return;
875             }
876         }
877         final OnClientUpdateListener l;
878         synchronized(mInfoLock) {
879             l = mOnClientUpdateListener;
880         }
881         if (l != null) {
882             l.onClientTransportControlUpdate(transportControlFlags);
883         }
884     }
885 
886     /**
887      * @param genId
888      * @param metadata guaranteed to be always non-null
889      */
onNewMetadata(int genId, Bundle metadata)890     private void onNewMetadata(int genId, Bundle metadata) {
891         synchronized(mGenLock) {
892             if (mClientGenerationIdCurrent != genId) {
893                 return;
894             }
895         }
896         final OnClientUpdateListener l;
897         final MetadataEditor metadataEditor;
898         // prepare the received Bundle to be used inside a MetadataEditor
899         final long editableKeys = metadata.getLong(
900                 String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
901         if (editableKeys != 0) {
902             metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
903         }
904         synchronized(mInfoLock) {
905             l = mOnClientUpdateListener;
906             if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
907                 if (mMetadataEditor.mEditorMetadata != metadata) {
908                     // existing metadata, merge existing and new
909                     mMetadataEditor.mEditorMetadata.putAll(metadata);
910                 }
911 
912                 mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
913                         (Bitmap)metadata.getParcelable(
914                                 String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
915                 mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
916             } else {
917                 mMetadataEditor = new MetadataEditor(metadata, editableKeys);
918             }
919             metadataEditor = mMetadataEditor;
920         }
921         if (l != null) {
922             l.onClientMetadataUpdate(metadataEditor);
923         }
924     }
925 
onClientChange(int genId, boolean clearing)926     private void onClientChange(int genId, boolean clearing) {
927         synchronized(mGenLock) {
928             if (mClientGenerationIdCurrent != genId) {
929                 return;
930             }
931         }
932         final OnClientUpdateListener l;
933         synchronized(mInfoLock) {
934             l = mOnClientUpdateListener;
935             mMetadataEditor = null;
936         }
937         if (l != null) {
938             l.onClientChange(clearing);
939         }
940     }
941 
onDisplayEnable(boolean enabled)942     private void onDisplayEnable(boolean enabled) {
943         final OnClientUpdateListener l;
944         synchronized(mInfoLock) {
945             mEnabled = enabled;
946             l = this.mOnClientUpdateListener;
947         }
948         if (!enabled) {
949             // when disabling, reset all info sent to the user
950             final int genId;
951             synchronized (mGenLock) {
952                 genId = mClientGenerationIdCurrent;
953             }
954             // send "stopped" state, happened "now", playback position is 0, speed 0.0f
955             final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED,
956                     SystemClock.elapsedRealtime() /*stateChangeTimeMs*/,
957                     0 /*currentPosMs*/, 0.0f /*speed*/);
958             sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
959                     genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/);
960             // send "blank" transport control info: no controls are supported
961             sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
962                     genId /*arg1*/, 0 /*arg2, no flags*/,
963                     null /*obj, ignored*/, 0 /*delay*/);
964             // send dummy metadata with empty string for title and artist, duration of 0
965             Bundle metadata = new Bundle(3);
966             metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), "");
967             metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), "");
968             metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0);
969             sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
970                     genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/);
971         }
972     }
973 
974     ///////////// These calls are used by the new APIs with Sessions //////////////////////
updateController(MediaController controller)975     private void updateController(MediaController controller) {
976         if (DEBUG) {
977             Log.d(TAG, "Updating controller to " + controller + " previous controller is "
978                     + mCurrentSession);
979         }
980         synchronized (mInfoLock) {
981             if (controller == null) {
982                 if (mCurrentSession != null) {
983                     mCurrentSession.unregisterCallback(mSessionCb);
984                     mCurrentSession = null;
985                     sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
986                             0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
987                 }
988             } else if (mCurrentSession == null
989                     || !controller.getSessionToken()
990                             .equals(mCurrentSession.getSessionToken())) {
991                 if (mCurrentSession != null) {
992                     mCurrentSession.unregisterCallback(mSessionCb);
993                 }
994                 sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
995                         0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
996                 mCurrentSession = controller;
997                 mCurrentSession.registerCallback(mSessionCb, mEventHandler);
998 
999                 PlaybackState state = controller.getPlaybackState();
1000                 sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
1001                         0 /* genId */, 0, state /* obj */, 0 /* delay */);
1002 
1003                 MediaMetadata metadata = controller.getMetadata();
1004                 sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
1005                         0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
1006             }
1007             // else same controller, no need to update
1008         }
1009     }
1010 
onNewPlaybackState(PlaybackState state)1011     private void onNewPlaybackState(PlaybackState state) {
1012         final OnClientUpdateListener l;
1013         synchronized (mInfoLock) {
1014             l = this.mOnClientUpdateListener;
1015         }
1016         if (l != null) {
1017             int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
1018                     .getRccStateFromState(state.getState());
1019             if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
1020                 l.onClientPlaybackStateUpdate(playstate);
1021             } else {
1022                 l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
1023                         state.getPosition(), state.getPlaybackSpeed());
1024             }
1025             if (state != null) {
1026                 l.onClientTransportControlUpdate(
1027                         PlaybackState.getRccControlFlagsFromActions(state.getActions()));
1028             }
1029         }
1030     }
1031 
onNewMediaMetadata(MediaMetadata metadata)1032     private void onNewMediaMetadata(MediaMetadata metadata) {
1033         if (metadata == null) {
1034             // RemoteController only handles non-null metadata
1035             return;
1036         }
1037         final OnClientUpdateListener l;
1038         final MetadataEditor metadataEditor;
1039         // prepare the received Bundle to be used inside a MetadataEditor
1040         synchronized(mInfoLock) {
1041             l = mOnClientUpdateListener;
1042             boolean canRate = mCurrentSession != null
1043                     && mCurrentSession.getRatingType() != Rating.RATING_NONE;
1044             long editableKeys = canRate ? MediaMetadataEditor.RATING_KEY_BY_USER : 0;
1045             Bundle legacyMetadata = MediaSessionLegacyHelper.getOldMetadata(metadata,
1046                     mArtworkWidth, mArtworkHeight);
1047             mMetadataEditor = new MetadataEditor(legacyMetadata, editableKeys);
1048             metadataEditor = mMetadataEditor;
1049         }
1050         if (l != null) {
1051             l.onClientMetadataUpdate(metadataEditor);
1052         }
1053     }
1054 
1055     //==================================================
1056     private static class PlaybackInfo {
1057         int mState;
1058         long mStateChangeTimeMs;
1059         long mCurrentPosMs;
1060         float mSpeed;
1061 
PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed)1062         PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
1063             mState = state;
1064             mStateChangeTimeMs = stateChangeTimeMs;
1065             mCurrentPosMs = currentPosMs;
1066             mSpeed = speed;
1067         }
1068     }
1069 
1070     /**
1071      * @hide
1072      * Used by AudioManager to mark this instance as registered.
1073      * @param registered
1074      */
setIsRegistered(boolean registered)1075     void setIsRegistered(boolean registered) {
1076         synchronized (mInfoLock) {
1077             mIsRegistered = registered;
1078         }
1079     }
1080 
1081     /**
1082      * @hide
1083      * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
1084      * @return
1085      */
getRcDisplay()1086     RcDisplay getRcDisplay() {
1087         return mRcd;
1088     }
1089 
1090     /**
1091      * @hide
1092      * Used by AudioManager to read the current artwork dimension
1093      * @return array containing width (index 0) and height (index 1) of currently set artwork size
1094      */
getArtworkSize()1095     int[] getArtworkSize() {
1096         synchronized (mInfoLock) {
1097             int[] size = { mArtworkWidth, mArtworkHeight };
1098             return size;
1099         }
1100     }
1101 
1102     /**
1103      * @hide
1104      * Used by AudioManager to access user listener receiving the client update notifications
1105      * @return
1106      */
getUpdateListener()1107     OnClientUpdateListener getUpdateListener() {
1108         return mOnClientUpdateListener;
1109     }
1110 }
1111