1 /*
2  * Copyright 2018 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.support.v4.media.session;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20 
21 import android.app.Activity;
22 import android.app.PendingIntent;
23 import android.content.Context;
24 import android.media.AudioManager;
25 import android.media.session.MediaController;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.RemoteException;
33 import android.os.ResultReceiver;
34 import android.support.v4.media.MediaBrowserCompat;
35 import android.support.v4.media.MediaDescriptionCompat;
36 import android.support.v4.media.MediaMetadataCompat;
37 import android.support.v4.media.RatingCompat;
38 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
39 import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.view.KeyEvent;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.RequiresApi;
46 import androidx.annotation.RestrictTo;
47 import androidx.core.app.BundleCompat;
48 import androidx.core.app.ComponentActivity;
49 import androidx.media.VolumeProviderCompat;
50 
51 import java.lang.ref.WeakReference;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 
57 /**
58  * Allows an app to interact with an ongoing media session. Media buttons and
59  * other commands can be sent to the session. A callback may be registered to
60  * receive updates from the session, such as metadata and play state changes.
61  * <p>
62  * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
63  * from the session owner.
64  * <p>
65  * MediaController objects are thread-safe.
66  * <p>
67  * This is a helper for accessing features in {@link android.media.session.MediaSession}
68  * introduced after API level 4 in a backwards compatible fashion.
69  * <p class="note">
70  * If MediaControllerCompat is created with a {@link MediaSessionCompat.Token session token}
71  * from another process, following methods will not work directly after the creation if the
72  * {@link MediaSessionCompat.Token session token} is not passed through a
73  * {@link MediaBrowserCompat}:
74  * <ul>
75  * <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
76  * <li>{@link #isCaptioningEnabled()}</li>
77  * <li>{@link #getRepeatMode()}</li>
78  * <li>{@link #getShuffleMode()}</li>
79  * </ul></p>
80  *
81  * <div class="special reference">
82  * <h3>Developer Guides</h3>
83  * <p>For information about building your media application, read the
84  * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
85  * </div>
86  */
87 public final class MediaControllerCompat {
88     static final String TAG = "MediaControllerCompat";
89 
90     /**
91      * @hide
92      */
93     @RestrictTo(LIBRARY)
94     public static final String COMMAND_GET_EXTRA_BINDER =
95             "android.support.v4.media.session.command.GET_EXTRA_BINDER";
96     /**
97      * @hide
98      */
99     @RestrictTo(LIBRARY)
100     public static final String COMMAND_ADD_QUEUE_ITEM =
101             "android.support.v4.media.session.command.ADD_QUEUE_ITEM";
102     /**
103      * @hide
104      */
105     @RestrictTo(LIBRARY)
106     public static final String COMMAND_ADD_QUEUE_ITEM_AT =
107             "android.support.v4.media.session.command.ADD_QUEUE_ITEM_AT";
108     /**
109      * @hide
110      */
111     @RestrictTo(LIBRARY)
112     public static final String COMMAND_REMOVE_QUEUE_ITEM =
113             "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM";
114     /**
115      * @hide
116      */
117     @RestrictTo(LIBRARY)
118     public static final String COMMAND_REMOVE_QUEUE_ITEM_AT =
119             "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM_AT";
120 
121     /**
122      * @hide
123      */
124     @RestrictTo(LIBRARY)
125     public static final String COMMAND_ARGUMENT_MEDIA_DESCRIPTION =
126             "android.support.v4.media.session.command.ARGUMENT_MEDIA_DESCRIPTION";
127     /**
128      * @hide
129      */
130     @RestrictTo(LIBRARY)
131     public static final String COMMAND_ARGUMENT_INDEX =
132             "android.support.v4.media.session.command.ARGUMENT_INDEX";
133 
134     private static class MediaControllerExtraData extends ComponentActivity.ExtraData {
135         private final MediaControllerCompat mMediaController;
136 
MediaControllerExtraData(MediaControllerCompat mediaController)137         MediaControllerExtraData(MediaControllerCompat mediaController) {
138             mMediaController = mediaController;
139         }
140 
getMediaController()141         MediaControllerCompat getMediaController() {
142             return mMediaController;
143         }
144     }
145 
146     /**
147      * Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via
148      * {@link #getMediaController(Activity)}.
149      *
150      * <p>This is compatible with {@link Activity#setMediaController(MediaController)}.
151      * If {@code activity} inherits {@link androidx.fragment.app.FragmentActivity}, the
152      * {@code mediaController} will be saved in the {@code activity}. In addition to that,
153      * on API 21 and later, {@link Activity#setMediaController(MediaController)} will be
154      * called.</p>
155      *
156      * @param activity The activity to set the {@code mediaController} in, must not be null.
157      * @param mediaController The controller for the session which should receive
158      *     media keys and volume changes on API 21 and later.
159      * @see #getMediaController(Activity)
160      * @see Activity#setMediaController(android.media.session.MediaController)
161      */
setMediaController(@onNull Activity activity, MediaControllerCompat mediaController)162     public static void setMediaController(@NonNull Activity activity,
163             MediaControllerCompat mediaController) {
164         if (activity instanceof ComponentActivity) {
165             ((ComponentActivity) activity).putExtraData(
166                     new MediaControllerExtraData(mediaController));
167         }
168         if (android.os.Build.VERSION.SDK_INT >= 21) {
169             Object controllerObj = null;
170             if (mediaController != null) {
171                 Object sessionTokenObj = mediaController.getSessionToken().getToken();
172                 controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj);
173             }
174             MediaControllerCompatApi21.setMediaController(activity, controllerObj);
175         }
176     }
177 
178     /**
179      * Retrieves the {@link MediaControllerCompat} set in the activity by
180      * {@link #setMediaController(Activity, MediaControllerCompat)} for sending media key and volume
181      * events.
182      *
183      * <p>This is compatible with {@link Activity#getMediaController()}.</p>
184      *
185      * @param activity The activity to get the media controller from, must not be null.
186      * @return The controller which should receive events.
187      * @see #setMediaController(Activity, MediaControllerCompat)
188      */
getMediaController(@onNull Activity activity)189     public static MediaControllerCompat getMediaController(@NonNull Activity activity) {
190         if (activity instanceof ComponentActivity) {
191             MediaControllerExtraData extraData =
192                     ((ComponentActivity) activity).getExtraData(MediaControllerExtraData.class);
193             return extraData != null ? extraData.getMediaController() : null;
194         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
195             Object controllerObj = MediaControllerCompatApi21.getMediaController(activity);
196             if (controllerObj == null) {
197                 return null;
198             }
199             Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj);
200             try {
201                 return new MediaControllerCompat(activity,
202                         MediaSessionCompat.Token.fromToken(sessionTokenObj));
203             } catch (RemoteException e) {
204                 Log.e(TAG, "Dead object in getMediaController.", e);
205             }
206         }
207         return null;
208     }
209 
validateCustomAction(String action, Bundle args)210     private static void validateCustomAction(String action, Bundle args) {
211         if (action == null) {
212             return;
213         }
214         switch(action) {
215             case MediaSessionCompat.ACTION_FOLLOW:
216             case MediaSessionCompat.ACTION_UNFOLLOW:
217                 if (args == null
218                         || !args.containsKey(MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE)) {
219                     throw new IllegalArgumentException("An extra field "
220                             + MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE + " is required "
221                             + "for this action " + action + ".");
222                 }
223                 break;
224         }
225     }
226 
227     private final MediaControllerImpl mImpl;
228     private final MediaSessionCompat.Token mToken;
229     // This set is used to keep references to registered callbacks to prevent them being GCed,
230     // since we only keep weak references for callbacks in this class and its inner classes.
231     private final HashSet<Callback> mRegisteredCallbacks = new HashSet<>();
232 
233     /**
234      * Creates a media controller from a session.
235      *
236      * @param session The session to be controlled.
237      */
MediaControllerCompat(Context context, @NonNull MediaSessionCompat session)238     public MediaControllerCompat(Context context, @NonNull MediaSessionCompat session) {
239         if (session == null) {
240             throw new IllegalArgumentException("session must not be null");
241         }
242         mToken = session.getSessionToken();
243 
244         if (android.os.Build.VERSION.SDK_INT >= 24) {
245             mImpl = new MediaControllerImplApi24(context, session);
246         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
247             mImpl = new MediaControllerImplApi23(context, session);
248         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
249             mImpl = new MediaControllerImplApi21(context, session);
250         } else {
251             mImpl = new MediaControllerImplBase(mToken);
252         }
253     }
254 
255     /**
256      * Creates a media controller from a session token which may have
257      * been obtained from another process.
258      *
259      * @param sessionToken The token of the session to be controlled.
260      * @throws RemoteException if the session is not accessible.
261      */
MediaControllerCompat(Context context, @NonNull MediaSessionCompat.Token sessionToken)262     public MediaControllerCompat(Context context, @NonNull MediaSessionCompat.Token sessionToken)
263             throws RemoteException {
264         if (sessionToken == null) {
265             throw new IllegalArgumentException("sessionToken must not be null");
266         }
267         mToken = sessionToken;
268 
269         if (android.os.Build.VERSION.SDK_INT >= 24) {
270             mImpl = new MediaControllerImplApi24(context, sessionToken);
271         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
272             mImpl = new MediaControllerImplApi23(context, sessionToken);
273         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
274             mImpl = new MediaControllerImplApi21(context, sessionToken);
275         } else {
276             mImpl = new MediaControllerImplBase(mToken);
277         }
278     }
279 
280     /**
281      * Gets a {@link TransportControls} instance for this session.
282      *
283      * @return A controls instance
284      */
getTransportControls()285     public TransportControls getTransportControls() {
286         return mImpl.getTransportControls();
287     }
288 
289     /**
290      * Sends the specified media button event to the session. Only media keys can
291      * be sent by this method, other keys will be ignored.
292      *
293      * @param keyEvent The media button event to dispatch.
294      * @return true if the event was sent to the session, false otherwise.
295      */
dispatchMediaButtonEvent(KeyEvent keyEvent)296     public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
297         if (keyEvent == null) {
298             throw new IllegalArgumentException("KeyEvent may not be null");
299         }
300         return mImpl.dispatchMediaButtonEvent(keyEvent);
301     }
302 
303     /**
304      * Gets the current playback state for this session.
305      *
306      * <p>If the session is not ready, {@link PlaybackStateCompat#getExtras()} on the result of
307      * this method may return null. </p>
308      *
309      * @return The current PlaybackState or null
310      * @see #isSessionReady
311      * @see Callback#onSessionReady
312      */
getPlaybackState()313     public PlaybackStateCompat getPlaybackState() {
314         return mImpl.getPlaybackState();
315     }
316 
317     /**
318      * Gets the current metadata for this session.
319      *
320      * @return The current MediaMetadata or null.
321      */
getMetadata()322     public MediaMetadataCompat getMetadata() {
323         return mImpl.getMetadata();
324     }
325 
326     /**
327      * Gets the current play queue for this session if one is set. If you only
328      * care about the current item {@link #getMetadata()} should be used.
329      *
330      * @return The current play queue or null.
331      */
getQueue()332     public List<QueueItem> getQueue() {
333         return mImpl.getQueue();
334     }
335 
336     /**
337      * Adds a queue item from the given {@code description} at the end of the play queue
338      * of this session. Not all sessions may support this. To know whether the session supports
339      * this, get the session's flags with {@link #getFlags()} and check that the flag
340      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
341      *
342      * @param description The {@link MediaDescriptionCompat} for creating the
343      *            {@link MediaSessionCompat.QueueItem} to be inserted.
344      * @throws UnsupportedOperationException If this session doesn't support this.
345      * @see #getFlags()
346      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
347      */
addQueueItem(MediaDescriptionCompat description)348     public void addQueueItem(MediaDescriptionCompat description) {
349         mImpl.addQueueItem(description);
350     }
351 
352     /**
353      * Adds a queue item from the given {@code description} at the specified position
354      * in the play queue of this session. Shifts the queue item currently at that position
355      * (if any) and any subsequent queue items to the right (adds one to their indices).
356      * Not all sessions may support this. To know whether the session supports this,
357      * get the session's flags with {@link #getFlags()} and check that the flag
358      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
359      *
360      * @param description The {@link MediaDescriptionCompat} for creating the
361      *            {@link MediaSessionCompat.QueueItem} to be inserted.
362      * @param index The index at which the created {@link MediaSessionCompat.QueueItem}
363      *            is to be inserted.
364      * @throws UnsupportedOperationException If this session doesn't support this.
365      * @see #getFlags()
366      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
367      */
addQueueItem(MediaDescriptionCompat description, int index)368     public void addQueueItem(MediaDescriptionCompat description, int index) {
369         mImpl.addQueueItem(description, index);
370     }
371 
372     /**
373      * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
374      * with the given {@link MediaDescriptionCompat description} in the play queue of the
375      * associated session. Not all sessions may support this. To know whether the session supports
376      * this, get the session's flags with {@link #getFlags()} and check that the flag
377      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
378      *
379      * @param description The {@link MediaDescriptionCompat} for denoting the
380      *            {@link MediaSessionCompat.QueueItem} to be removed.
381      * @throws UnsupportedOperationException If this session doesn't support this.
382      * @see #getFlags()
383      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
384      */
removeQueueItem(MediaDescriptionCompat description)385     public void removeQueueItem(MediaDescriptionCompat description) {
386         mImpl.removeQueueItem(description);
387     }
388 
389     /**
390      * Removes an queue item at the specified position in the play queue
391      * of this session. Not all sessions may support this. To know whether the session supports
392      * this, get the session's flags with {@link #getFlags()} and check that the flag
393      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
394      *
395      * @param index The index of the element to be removed.
396      * @throws UnsupportedOperationException If this session doesn't support this.
397      * @see #getFlags()
398      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
399      * @deprecated Use {@link #removeQueueItem(MediaDescriptionCompat)} instead.
400      */
401     @Deprecated
removeQueueItemAt(int index)402     public void removeQueueItemAt(int index) {
403         List<QueueItem> queue = getQueue();
404         if (queue != null && index >= 0 && index < queue.size()) {
405             QueueItem item = queue.get(index);
406             if (item != null) {
407                 removeQueueItem(item.getDescription());
408             }
409         }
410     }
411 
412     /**
413      * Gets the queue title for this session.
414      */
getQueueTitle()415     public CharSequence getQueueTitle() {
416         return mImpl.getQueueTitle();
417     }
418 
419     /**
420      * Gets the extras for this session.
421      */
getExtras()422     public Bundle getExtras() {
423         return mImpl.getExtras();
424     }
425 
426     /**
427      * Gets the rating type supported by the session. One of:
428      * <ul>
429      * <li>{@link RatingCompat#RATING_NONE}</li>
430      * <li>{@link RatingCompat#RATING_HEART}</li>
431      * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
432      * <li>{@link RatingCompat#RATING_3_STARS}</li>
433      * <li>{@link RatingCompat#RATING_4_STARS}</li>
434      * <li>{@link RatingCompat#RATING_5_STARS}</li>
435      * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
436      * </ul>
437      * <p>If the session is not ready, it will return {@link RatingCompat#RATING_NONE}.</p>
438      *
439      * @return The supported rating type, or {@link RatingCompat#RATING_NONE} if the value is not
440      *         set or the session is not ready.
441      * @see #isSessionReady
442      * @see Callback#onSessionReady
443      */
getRatingType()444     public int getRatingType() {
445         return mImpl.getRatingType();
446     }
447 
448     /**
449      * Returns whether captioning is enabled for this session.
450      *
451      * <p>If the session is not ready, it will return a {@code false}.</p>
452      *
453      * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
454      * @see #isSessionReady
455      * @see Callback#onSessionReady
456      */
isCaptioningEnabled()457     public boolean isCaptioningEnabled() {
458         return mImpl.isCaptioningEnabled();
459     }
460 
461     /**
462      * Gets the repeat mode for this session.
463      *
464      * @return The latest repeat mode set to the session,
465      *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set, or
466      *         {@link PlaybackStateCompat#REPEAT_MODE_INVALID} if the session is not ready yet.
467      * @see #isSessionReady
468      * @see Callback#onSessionReady
469      */
getRepeatMode()470     public int getRepeatMode() {
471         return mImpl.getRepeatMode();
472     }
473 
474     /**
475      * Gets the shuffle mode for this session.
476      *
477      * @return The latest shuffle mode set to the session, or
478      *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if disabled or not set, or
479      *         {@link PlaybackStateCompat#SHUFFLE_MODE_INVALID} if the session is not ready yet.
480      * @see #isSessionReady
481      * @see Callback#onSessionReady
482      */
getShuffleMode()483     public int getShuffleMode() {
484         return mImpl.getShuffleMode();
485     }
486 
487     /**
488      * Gets the flags for this session. Flags are defined in
489      * {@link MediaSessionCompat}.
490      *
491      * @return The current set of flags for the session.
492      */
getFlags()493     public long getFlags() {
494         return mImpl.getFlags();
495     }
496 
497     /**
498      * Gets the current playback info for this session.
499      *
500      * @return The current playback info or null.
501      */
getPlaybackInfo()502     public PlaybackInfo getPlaybackInfo() {
503         return mImpl.getPlaybackInfo();
504     }
505 
506     /**
507      * Gets an intent for launching UI associated with this session if one
508      * exists.
509      *
510      * @return A {@link PendingIntent} to launch UI or null.
511      */
getSessionActivity()512     public PendingIntent getSessionActivity() {
513         return mImpl.getSessionActivity();
514     }
515 
516     /**
517      * Gets the token for the session this controller is connected to.
518      *
519      * @return The session's token.
520      */
getSessionToken()521     public MediaSessionCompat.Token getSessionToken() {
522         return mToken;
523     }
524 
525     /**
526      * Sets the volume of the output this session is playing on. The command will
527      * be ignored if it does not support
528      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
529      * {@link AudioManager} may be used to affect the handling.
530      *
531      * @see #getPlaybackInfo()
532      * @param value The value to set it to, between 0 and the reported max.
533      * @param flags Flags from {@link AudioManager} to include with the volume
534      *            request.
535      */
setVolumeTo(int value, int flags)536     public void setVolumeTo(int value, int flags) {
537         mImpl.setVolumeTo(value, flags);
538     }
539 
540     /**
541      * Adjusts the volume of the output this session is playing on. The direction
542      * must be one of {@link AudioManager#ADJUST_LOWER},
543      * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
544      * The command will be ignored if the session does not support
545      * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
546      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
547      * {@link AudioManager} may be used to affect the handling.
548      *
549      * @see #getPlaybackInfo()
550      * @param direction The direction to adjust the volume in.
551      * @param flags Any flags to pass with the command.
552      */
adjustVolume(int direction, int flags)553     public void adjustVolume(int direction, int flags) {
554         mImpl.adjustVolume(direction, flags);
555     }
556 
557     /**
558      * Adds a callback to receive updates from the Session. Updates will be
559      * posted on the caller's thread.
560      *
561      * @param callback The callback object, must not be null.
562      */
registerCallback(@onNull Callback callback)563     public void registerCallback(@NonNull Callback callback) {
564         registerCallback(callback, null);
565     }
566 
567     /**
568      * Adds a callback to receive updates from the session. Updates will be
569      * posted on the specified handler's thread.
570      *
571      * @param callback The callback object, must not be null.
572      * @param handler The handler to post updates on. If null the callers thread
573      *            will be used.
574      */
registerCallback(@onNull Callback callback, Handler handler)575     public void registerCallback(@NonNull Callback callback, Handler handler) {
576         if (callback == null) {
577             throw new IllegalArgumentException("callback must not be null");
578         }
579         if (handler == null) {
580             handler = new Handler();
581         }
582         callback.setHandler(handler);
583         mImpl.registerCallback(callback, handler);
584         mRegisteredCallbacks.add(callback);
585     }
586 
587     /**
588      * Stops receiving updates on the specified callback. If an update has
589      * already been posted you may still receive it after calling this method.
590      *
591      * @param callback The callback to remove
592      */
unregisterCallback(@onNull Callback callback)593     public void unregisterCallback(@NonNull Callback callback) {
594         if (callback == null) {
595             throw new IllegalArgumentException("callback must not be null");
596         }
597         try {
598             mRegisteredCallbacks.remove(callback);
599             mImpl.unregisterCallback(callback);
600         } finally {
601             callback.setHandler(null);
602         }
603     }
604 
605     /**
606      * Sends a generic command to the session. It is up to the session creator
607      * to decide what commands and parameters they will support. As such,
608      * commands should only be sent to sessions that the controller owns.
609      *
610      * @param command The command to send
611      * @param params Any parameters to include with the command
612      * @param cb The callback to receive the result on
613      */
sendCommand(@onNull String command, Bundle params, ResultReceiver cb)614     public void sendCommand(@NonNull String command, Bundle params, ResultReceiver cb) {
615         if (TextUtils.isEmpty(command)) {
616             throw new IllegalArgumentException("command must neither be null nor empty");
617         }
618         mImpl.sendCommand(command, params, cb);
619     }
620 
621     /**
622      * Returns whether the session is ready or not.
623      *
624      * <p>If the session is not ready, following methods can work incorrectly.</p>
625      * <ul>
626      * <li>{@link #getPlaybackState()}</li>
627      * <li>{@link #getRatingType()}</li>
628      * <li>{@link #getRepeatMode()}</li>
629      * <li>{@link #getShuffleMode()}</li>
630      * <li>{@link #isCaptioningEnabled()}</li>
631      * </ul>
632      *
633      * @return true if the session is ready, false otherwise.
634      * @see Callback#onSessionReady()
635      */
isSessionReady()636     public boolean isSessionReady() {
637         return mImpl.isSessionReady();
638     }
639 
640     /**
641      * Gets the session owner's package name.
642      *
643      * @return The package name of of the session owner.
644      */
getPackageName()645     public String getPackageName() {
646         return mImpl.getPackageName();
647     }
648 
649     /**
650      * Gets the underlying framework
651      * {@link android.media.session.MediaController} object.
652      * <p>
653      * This method is only supported on API 21+.
654      * </p>
655      *
656      * @return The underlying {@link android.media.session.MediaController}
657      *         object, or null if none.
658      */
getMediaController()659     public Object getMediaController() {
660         return mImpl.getMediaController();
661     }
662 
663     /**
664      * Callback for receiving updates on from the session. A Callback can be
665      * registered using {@link #registerCallback}
666      */
667     public static abstract class Callback implements IBinder.DeathRecipient {
668         private final Object mCallbackObj;
669         MessageHandler mHandler;
670         IMediaControllerCallback mIControllerCallback;
671 
Callback()672         public Callback() {
673             if (android.os.Build.VERSION.SDK_INT >= 21) {
674                 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21(this));
675             } else {
676                 mCallbackObj = mIControllerCallback = new StubCompat(this);
677             }
678         }
679 
680         /**
681          * Override to handle the session being ready.
682          *
683          * @see MediaControllerCompat#isSessionReady
684          */
onSessionReady()685         public void onSessionReady() {
686         }
687 
688         /**
689          * Override to handle the session being destroyed. The session is no
690          * longer valid after this call and calls to it will be ignored.
691          */
onSessionDestroyed()692         public void onSessionDestroyed() {
693         }
694 
695         /**
696          * Override to handle custom events sent by the session owner without a
697          * specified interface. Controllers should only handle these for
698          * sessions they own.
699          *
700          * @param event The event from the session.
701          * @param extras Optional parameters for the event.
702          */
onSessionEvent(String event, Bundle extras)703         public void onSessionEvent(String event, Bundle extras) {
704         }
705 
706         /**
707          * Override to handle changes in playback state.
708          *
709          * @param state The new playback state of the session
710          */
onPlaybackStateChanged(PlaybackStateCompat state)711         public void onPlaybackStateChanged(PlaybackStateCompat state) {
712         }
713 
714         /**
715          * Override to handle changes to the current metadata.
716          *
717          * @param metadata The current metadata for the session or null if none.
718          * @see MediaMetadataCompat
719          */
onMetadataChanged(MediaMetadataCompat metadata)720         public void onMetadataChanged(MediaMetadataCompat metadata) {
721         }
722 
723         /**
724          * Override to handle changes to items in the queue.
725          *
726          * @see MediaSessionCompat.QueueItem
727          * @param queue A list of items in the current play queue. It should
728          *            include the currently playing item as well as previous and
729          *            upcoming items if applicable.
730          */
onQueueChanged(List<QueueItem> queue)731         public void onQueueChanged(List<QueueItem> queue) {
732         }
733 
734         /**
735          * Override to handle changes to the queue title.
736          *
737          * @param title The title that should be displayed along with the play
738          *            queue such as "Now Playing". May be null if there is no
739          *            such title.
740          */
onQueueTitleChanged(CharSequence title)741         public void onQueueTitleChanged(CharSequence title) {
742         }
743 
744         /**
745          * Override to handle changes to the {@link MediaSessionCompat} extras.
746          *
747          * @param extras The extras that can include other information
748          *            associated with the {@link MediaSessionCompat}.
749          */
onExtrasChanged(Bundle extras)750         public void onExtrasChanged(Bundle extras) {
751         }
752 
753         /**
754          * Override to handle changes to the audio info.
755          *
756          * @param info The current audio info for this session.
757          */
onAudioInfoChanged(PlaybackInfo info)758         public void onAudioInfoChanged(PlaybackInfo info) {
759         }
760 
761         /**
762          * Override to handle changes to the captioning enabled status.
763          *
764          * @param enabled {@code true} if captioning is enabled, {@code false} otherwise.
765          */
onCaptioningEnabledChanged(boolean enabled)766         public void onCaptioningEnabledChanged(boolean enabled) {
767         }
768 
769         /**
770          * Override to handle changes to the repeat mode.
771          *
772          * @param repeatMode The repeat mode. It should be one of followings:
773          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
774          *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
775          *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
776          *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
777          */
onRepeatModeChanged(@laybackStateCompat.RepeatMode int repeatMode)778         public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
779         }
780 
781         /**
782          * Override to handle changes to the shuffle mode.
783          *
784          * @param shuffleMode The shuffle mode. Must be one of the followings:
785          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
786          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
787          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
788          */
onShuffleModeChanged(@laybackStateCompat.ShuffleMode int shuffleMode)789         public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
790         }
791 
792         /**
793          * @hide
794          */
795         @RestrictTo(LIBRARY)
getIControllerCallback()796         public IMediaControllerCallback getIControllerCallback() {
797             return mIControllerCallback;
798         }
799 
800         @Override
binderDied()801         public void binderDied() {
802             onSessionDestroyed();
803         }
804 
805         /**
806          * Set the handler to use for callbacks.
807          */
setHandler(Handler handler)808         void setHandler(Handler handler) {
809             if (handler == null) {
810                 if (mHandler != null) {
811                     mHandler.mRegistered = false;
812                     mHandler.removeCallbacksAndMessages(null);
813                     mHandler = null;
814                 }
815             } else {
816                 mHandler = new MessageHandler(handler.getLooper());
817                 mHandler.mRegistered = true;
818             }
819         }
820 
postToHandler(int what, Object obj, Bundle data)821         void postToHandler(int what, Object obj, Bundle data) {
822             if (mHandler != null) {
823                 Message msg = mHandler.obtainMessage(what, obj);
824                 msg.setData(data);
825                 msg.sendToTarget();
826             }
827         }
828 
829         private static class StubApi21 implements MediaControllerCompatApi21.Callback {
830             private final WeakReference<MediaControllerCompat.Callback> mCallback;
831 
StubApi21(MediaControllerCompat.Callback callback)832             StubApi21(MediaControllerCompat.Callback callback) {
833                 mCallback = new WeakReference<>(callback);
834             }
835 
836             @Override
onSessionDestroyed()837             public void onSessionDestroyed() {
838                 MediaControllerCompat.Callback callback = mCallback.get();
839                 if (callback != null) {
840                     callback.onSessionDestroyed();
841                 }
842             }
843 
844             @Override
onSessionEvent(String event, Bundle extras)845             public void onSessionEvent(String event, Bundle extras) {
846                 MediaControllerCompat.Callback callback = mCallback.get();
847                 if (callback != null) {
848                     if (callback.mIControllerCallback != null
849                             && android.os.Build.VERSION.SDK_INT < 23) {
850                         // Ignore. ExtraCallback will handle this.
851                     } else {
852                         callback.onSessionEvent(event, extras);
853                     }
854                 }
855             }
856 
857             @Override
onPlaybackStateChanged(Object stateObj)858             public void onPlaybackStateChanged(Object stateObj) {
859                 MediaControllerCompat.Callback callback = mCallback.get();
860                 if (callback != null) {
861                     if (callback.mIControllerCallback != null) {
862                         // Ignore. ExtraCallback will handle this.
863                     } else {
864                         callback.onPlaybackStateChanged(
865                                 PlaybackStateCompat.fromPlaybackState(stateObj));
866                     }
867                 }
868             }
869 
870             @Override
onMetadataChanged(Object metadataObj)871             public void onMetadataChanged(Object metadataObj) {
872                 MediaControllerCompat.Callback callback = mCallback.get();
873                 if (callback != null) {
874                     callback.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
875                 }
876             }
877 
878             @Override
onQueueChanged(List<?> queue)879             public void onQueueChanged(List<?> queue) {
880                 MediaControllerCompat.Callback callback = mCallback.get();
881                 if (callback != null) {
882                     callback.onQueueChanged(QueueItem.fromQueueItemList(queue));
883                 }
884             }
885 
886             @Override
onQueueTitleChanged(CharSequence title)887             public void onQueueTitleChanged(CharSequence title) {
888                 MediaControllerCompat.Callback callback = mCallback.get();
889                 if (callback != null) {
890                     callback.onQueueTitleChanged(title);
891                 }
892             }
893 
894             @Override
onExtrasChanged(Bundle extras)895             public void onExtrasChanged(Bundle extras) {
896                 MediaControllerCompat.Callback callback = mCallback.get();
897                 if (callback != null) {
898                     callback.onExtrasChanged(extras);
899                 }
900             }
901 
902             @Override
onAudioInfoChanged( int type, int stream, int control, int max, int current)903             public void onAudioInfoChanged(
904                     int type, int stream, int control, int max, int current) {
905                 MediaControllerCompat.Callback callback = mCallback.get();
906                 if (callback != null) {
907                     callback.onAudioInfoChanged(
908                             new PlaybackInfo(type, stream, control, max, current));
909                 }
910             }
911         }
912 
913         private static class StubCompat extends IMediaControllerCallback.Stub {
914             private final WeakReference<MediaControllerCompat.Callback> mCallback;
915 
StubCompat(MediaControllerCompat.Callback callback)916             StubCompat(MediaControllerCompat.Callback callback) {
917                 mCallback = new WeakReference<>(callback);
918             }
919 
920             @Override
onEvent(String event, Bundle extras)921             public void onEvent(String event, Bundle extras) throws RemoteException {
922                 MediaControllerCompat.Callback callback = mCallback.get();
923                 if (callback != null) {
924                     callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
925                 }
926             }
927 
928             @Override
onSessionDestroyed()929             public void onSessionDestroyed() throws RemoteException {
930                 MediaControllerCompat.Callback callback = mCallback.get();
931                 if (callback != null) {
932                     callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
933                 }
934             }
935 
936             @Override
onPlaybackStateChanged(PlaybackStateCompat state)937             public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
938                 MediaControllerCompat.Callback callback = mCallback.get();
939                 if (callback != null) {
940                     callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
941                 }
942             }
943 
944             @Override
onMetadataChanged(MediaMetadataCompat metadata)945             public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
946                 MediaControllerCompat.Callback callback = mCallback.get();
947                 if (callback != null) {
948                     callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
949                 }
950             }
951 
952             @Override
onQueueChanged(List<QueueItem> queue)953             public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
954                 MediaControllerCompat.Callback callback = mCallback.get();
955                 if (callback != null) {
956                     callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
957                 }
958             }
959 
960             @Override
onQueueTitleChanged(CharSequence title)961             public void onQueueTitleChanged(CharSequence title) throws RemoteException {
962                 MediaControllerCompat.Callback callback = mCallback.get();
963                 if (callback != null) {
964                     callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
965                 }
966             }
967 
968             @Override
onCaptioningEnabledChanged(boolean enabled)969             public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException {
970                 MediaControllerCompat.Callback callback = mCallback.get();
971                 if (callback != null) {
972                     callback.postToHandler(
973                             MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
974                 }
975             }
976 
977             @Override
onRepeatModeChanged(int repeatMode)978             public void onRepeatModeChanged(int repeatMode) throws RemoteException {
979                 MediaControllerCompat.Callback callback = mCallback.get();
980                 if (callback != null) {
981                     callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
982                 }
983             }
984 
985             @Override
onShuffleModeChangedRemoved(boolean enabled)986             public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
987                 // Do nothing.
988             }
989 
990             @Override
onShuffleModeChanged(int shuffleMode)991             public void onShuffleModeChanged(int shuffleMode) throws RemoteException {
992                 MediaControllerCompat.Callback callback = mCallback.get();
993                 if (callback != null) {
994                     callback.postToHandler(
995                             MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
996                 }
997             }
998 
999             @Override
onExtrasChanged(Bundle extras)1000             public void onExtrasChanged(Bundle extras) throws RemoteException {
1001                 MediaControllerCompat.Callback callback = mCallback.get();
1002                 if (callback != null) {
1003                     callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
1004                 }
1005             }
1006 
1007             @Override
onVolumeInfoChanged(ParcelableVolumeInfo info)1008             public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
1009                 MediaControllerCompat.Callback callback = mCallback.get();
1010                 if (callback != null) {
1011                     PlaybackInfo pi = null;
1012                     if (info != null) {
1013                         pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
1014                                 info.maxVolume, info.currentVolume);
1015                     }
1016                     callback.postToHandler(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
1017                 }
1018             }
1019 
1020             @Override
onSessionReady()1021             public void onSessionReady() throws RemoteException {
1022                 MediaControllerCompat.Callback callback = mCallback.get();
1023                 if (callback != null) {
1024                     callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
1025                 }
1026             }
1027         }
1028 
1029         private class MessageHandler extends Handler {
1030             private static final int MSG_EVENT = 1;
1031             private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1032             private static final int MSG_UPDATE_METADATA = 3;
1033             private static final int MSG_UPDATE_VOLUME = 4;
1034             private static final int MSG_UPDATE_QUEUE = 5;
1035             private static final int MSG_UPDATE_QUEUE_TITLE = 6;
1036             private static final int MSG_UPDATE_EXTRAS = 7;
1037             private static final int MSG_DESTROYED = 8;
1038             private static final int MSG_UPDATE_REPEAT_MODE = 9;
1039             private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
1040             private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
1041             private static final int MSG_SESSION_READY = 13;
1042 
1043             boolean mRegistered = false;
1044 
MessageHandler(Looper looper)1045             MessageHandler(Looper looper) {
1046                 super(looper);
1047             }
1048 
1049             @Override
handleMessage(Message msg)1050             public void handleMessage(Message msg) {
1051                 if (!mRegistered) {
1052                     return;
1053                 }
1054                 switch (msg.what) {
1055                     case MSG_EVENT:
1056                         onSessionEvent((String) msg.obj, msg.getData());
1057                         break;
1058                     case MSG_UPDATE_PLAYBACK_STATE:
1059                         onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
1060                         break;
1061                     case MSG_UPDATE_METADATA:
1062                         onMetadataChanged((MediaMetadataCompat) msg.obj);
1063                         break;
1064                     case MSG_UPDATE_QUEUE:
1065                         onQueueChanged((List<QueueItem>) msg.obj);
1066                         break;
1067                     case MSG_UPDATE_QUEUE_TITLE:
1068                         onQueueTitleChanged((CharSequence) msg.obj);
1069                         break;
1070                     case MSG_UPDATE_CAPTIONING_ENABLED:
1071                         onCaptioningEnabledChanged((boolean) msg.obj);
1072                         break;
1073                     case MSG_UPDATE_REPEAT_MODE:
1074                         onRepeatModeChanged((int) msg.obj);
1075                         break;
1076                     case MSG_UPDATE_SHUFFLE_MODE:
1077                         onShuffleModeChanged((int) msg.obj);
1078                         break;
1079                     case MSG_UPDATE_EXTRAS:
1080                         onExtrasChanged((Bundle) msg.obj);
1081                         break;
1082                     case MSG_UPDATE_VOLUME:
1083                         onAudioInfoChanged((PlaybackInfo) msg.obj);
1084                         break;
1085                     case MSG_DESTROYED:
1086                         onSessionDestroyed();
1087                         break;
1088                     case MSG_SESSION_READY:
1089                         onSessionReady();
1090                         break;
1091                 }
1092             }
1093         }
1094     }
1095 
1096     /**
1097      * Interface for controlling media playback on a session. This allows an app
1098      * to send media transport commands to the session.
1099      */
1100     public static abstract class TransportControls {
1101         /**
1102          * Used as an integer extra field in {@link #playFromMediaId(String, Bundle)} or
1103          * {@link #prepareFromMediaId(String, Bundle)} to indicate the stream type to be used by the
1104          * media player when playing or preparing the specified media id. See {@link AudioManager}
1105          * for a list of stream types.
1106          */
1107         public static final String EXTRA_LEGACY_STREAM_TYPE =
1108                 "android.media.session.extra.LEGACY_STREAM_TYPE";
1109 
TransportControls()1110         TransportControls() {
1111         }
1112 
1113         /**
1114          * Request that the player prepare for playback. This can decrease the time it takes to
1115          * start playback when a play command is received. Preparation is not required. You can
1116          * call {@link #play} without calling this method beforehand.
1117          */
prepare()1118         public abstract void prepare();
1119 
1120         /**
1121          * Request that the player prepare playback for a specific media id. This can decrease the
1122          * time it takes to start playback when a play command is received. Preparation is not
1123          * required. You can call {@link #playFromMediaId} without calling this method beforehand.
1124          *
1125          * @param mediaId The id of the requested media.
1126          * @param extras Optional extras that can include extra information about the media item
1127          *               to be prepared.
1128          */
prepareFromMediaId(String mediaId, Bundle extras)1129         public abstract void prepareFromMediaId(String mediaId, Bundle extras);
1130 
1131         /**
1132          * Request that the player prepare playback for a specific search query. This can decrease
1133          * the time it takes to start playback when a play command is received. An empty or null
1134          * query should be treated as a request to prepare any music. Preparation is not required.
1135          * You can call {@link #playFromSearch} without calling this method beforehand.
1136          *
1137          * @param query The search query.
1138          * @param extras Optional extras that can include extra information
1139          *               about the query.
1140          */
prepareFromSearch(String query, Bundle extras)1141         public abstract void prepareFromSearch(String query, Bundle extras);
1142 
1143         /**
1144          * Request that the player prepare playback for a specific {@link Uri}. This can decrease
1145          * the time it takes to start playback when a play command is received. Preparation is not
1146          * required. You can call {@link #playFromUri} without calling this method beforehand.
1147          *
1148          * @param uri The URI of the requested media.
1149          * @param extras Optional extras that can include extra information about the media item
1150          *               to be prepared.
1151          */
prepareFromUri(Uri uri, Bundle extras)1152         public abstract void prepareFromUri(Uri uri, Bundle extras);
1153 
1154         /**
1155          * Request that the player start its playback at its current position.
1156          */
play()1157         public abstract void play();
1158 
1159         /**
1160          * Request that the player start playback for a specific {@link Uri}.
1161          *
1162          * @param mediaId The uri of the requested media.
1163          * @param extras Optional extras that can include extra information
1164          *            about the media item to be played.
1165          */
playFromMediaId(String mediaId, Bundle extras)1166         public abstract void playFromMediaId(String mediaId, Bundle extras);
1167 
1168         /**
1169          * Request that the player start playback for a specific search query.
1170          * An empty or null query should be treated as a request to play any
1171          * music.
1172          *
1173          * @param query The search query.
1174          * @param extras Optional extras that can include extra information
1175          *            about the query.
1176          */
playFromSearch(String query, Bundle extras)1177         public abstract void playFromSearch(String query, Bundle extras);
1178 
1179         /**
1180          * Request that the player start playback for a specific {@link Uri}.
1181          *
1182          * @param uri  The URI of the requested media.
1183          * @param extras Optional extras that can include extra information about the media item
1184          *               to be played.
1185          */
playFromUri(Uri uri, Bundle extras)1186         public abstract void playFromUri(Uri uri, Bundle extras);
1187 
1188         /**
1189          * Plays an item with a specific id in the play queue. If you specify an
1190          * id that is not in the play queue, the behavior is undefined.
1191          */
skipToQueueItem(long id)1192         public abstract void skipToQueueItem(long id);
1193 
1194         /**
1195          * Request that the player pause its playback and stay at its current
1196          * position.
1197          */
pause()1198         public abstract void pause();
1199 
1200         /**
1201          * Request that the player stop its playback; it may clear its state in
1202          * whatever way is appropriate.
1203          */
stop()1204         public abstract void stop();
1205 
1206         /**
1207          * Moves to a new location in the media stream.
1208          *
1209          * @param pos Position to move to, in milliseconds.
1210          */
seekTo(long pos)1211         public abstract void seekTo(long pos);
1212 
1213         /**
1214          * Starts fast forwarding. If playback is already fast forwarding this
1215          * may increase the rate.
1216          */
fastForward()1217         public abstract void fastForward();
1218 
1219         /**
1220          * Skips to the next item.
1221          */
skipToNext()1222         public abstract void skipToNext();
1223 
1224         /**
1225          * Starts rewinding. If playback is already rewinding this may increase
1226          * the rate.
1227          */
rewind()1228         public abstract void rewind();
1229 
1230         /**
1231          * Skips to the previous item.
1232          */
skipToPrevious()1233         public abstract void skipToPrevious();
1234 
1235         /**
1236          * Rates the current content. This will cause the rating to be set for
1237          * the current user. The rating type of the given {@link RatingCompat} must match the type
1238          * returned by {@link #getRatingType()}.
1239          *
1240          * @param rating The rating to set for the current content
1241          */
setRating(RatingCompat rating)1242         public abstract void setRating(RatingCompat rating);
1243 
1244         /**
1245          * Rates a media item. This will cause the rating to be set for
1246          * the specific media item. The rating type of the given {@link RatingCompat} must match
1247          * the type returned by {@link #getRatingType()}.
1248          *
1249          * @param rating The rating to set for the media item.
1250          * @param extras Optional arguments that can include information about the media item
1251          *               to be rated.
1252          *
1253          * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE
1254          * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE_VALUE
1255          */
setRating(RatingCompat rating, Bundle extras)1256         public abstract void setRating(RatingCompat rating, Bundle extras);
1257 
1258         /**
1259          * Enables/disables captioning for this session.
1260          *
1261          * @param enabled {@code true} to enable captioning, {@code false} to disable.
1262          */
setCaptioningEnabled(boolean enabled)1263         public abstract void setCaptioningEnabled(boolean enabled);
1264 
1265         /**
1266          * Sets the repeat mode for this session.
1267          *
1268          * @param repeatMode The repeat mode. Must be one of the followings:
1269          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
1270          *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
1271          *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
1272          *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
1273          */
setRepeatMode(@laybackStateCompat.RepeatMode int repeatMode)1274         public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
1275 
1276         /**
1277          * Sets the shuffle mode for this session.
1278          *
1279          * @param shuffleMode The shuffle mode. Must be one of the followings:
1280          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
1281          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
1282          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
1283          */
setShuffleMode(@laybackStateCompat.ShuffleMode int shuffleMode)1284         public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
1285 
1286         /**
1287          * Sends a custom action for the {@link MediaSessionCompat} to perform.
1288          *
1289          * @param customAction The action to perform.
1290          * @param args Optional arguments to supply to the
1291          *            {@link MediaSessionCompat} for this custom action.
1292          */
sendCustomAction(PlaybackStateCompat.CustomAction customAction, Bundle args)1293         public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
1294                 Bundle args);
1295 
1296         /**
1297          * Sends the id and args from a custom action for the
1298          * {@link MediaSessionCompat} to perform.
1299          *
1300          * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
1301          *      Bundle args)
1302          * @see MediaSessionCompat#ACTION_FLAG_AS_INAPPROPRIATE
1303          * @see MediaSessionCompat#ACTION_SKIP_AD
1304          * @see MediaSessionCompat#ACTION_FOLLOW
1305          * @see MediaSessionCompat#ACTION_UNFOLLOW
1306          * @param action The action identifier of the
1307          *            {@link PlaybackStateCompat.CustomAction} as specified by
1308          *            the {@link MediaSessionCompat}.
1309          * @param args Optional arguments to supply to the
1310          *            {@link MediaSessionCompat} for this custom action.
1311          */
sendCustomAction(String action, Bundle args)1312         public abstract void sendCustomAction(String action, Bundle args);
1313     }
1314 
1315     /**
1316      * Holds information about the way volume is handled for this session.
1317      */
1318     public static final class PlaybackInfo {
1319         /**
1320          * The session uses local playback.
1321          */
1322         public static final int PLAYBACK_TYPE_LOCAL = 1;
1323         /**
1324          * The session uses remote playback.
1325          */
1326         public static final int PLAYBACK_TYPE_REMOTE = 2;
1327 
1328         private final int mPlaybackType;
1329         // TODO update audio stream with AudioAttributes support version
1330         private final int mAudioStream;
1331         private final int mVolumeControl;
1332         private final int mMaxVolume;
1333         private final int mCurrentVolume;
1334 
PlaybackInfo(int type, int stream, int control, int max, int current)1335         PlaybackInfo(int type, int stream, int control, int max, int current) {
1336             mPlaybackType = type;
1337             mAudioStream = stream;
1338             mVolumeControl = control;
1339             mMaxVolume = max;
1340             mCurrentVolume = current;
1341         }
1342 
1343         /**
1344          * Gets the type of volume handling, either local or remote. One of:
1345          * <ul>
1346          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
1347          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
1348          * </ul>
1349          *
1350          * @return The type of volume handling this session is using.
1351          */
getPlaybackType()1352         public int getPlaybackType() {
1353             return mPlaybackType;
1354         }
1355 
1356         /**
1357          * Gets the stream this is currently controlling volume on. When the volume
1358          * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
1359          * have meaning and should be ignored.
1360          *
1361          * @return The stream this session is playing on.
1362          */
getAudioStream()1363         public int getAudioStream() {
1364             // TODO switch to AudioAttributesCompat when it is added.
1365             return mAudioStream;
1366         }
1367 
1368         /**
1369          * Gets the type of volume control that can be used. One of:
1370          * <ul>
1371          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
1372          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
1373          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
1374          * </ul>
1375          *
1376          * @return The type of volume control that may be used with this
1377          *         session.
1378          */
getVolumeControl()1379         public int getVolumeControl() {
1380             return mVolumeControl;
1381         }
1382 
1383         /**
1384          * Gets the maximum volume that may be set for this session.
1385          *
1386          * @return The maximum allowed volume where this session is playing.
1387          */
getMaxVolume()1388         public int getMaxVolume() {
1389             return mMaxVolume;
1390         }
1391 
1392         /**
1393          * Gets the current volume for this session.
1394          *
1395          * @return The current volume where this session is playing.
1396          */
getCurrentVolume()1397         public int getCurrentVolume() {
1398             return mCurrentVolume;
1399         }
1400     }
1401 
1402     interface MediaControllerImpl {
registerCallback(Callback callback, Handler handler)1403         void registerCallback(Callback callback, Handler handler);
1404 
unregisterCallback(Callback callback)1405         void unregisterCallback(Callback callback);
dispatchMediaButtonEvent(KeyEvent keyEvent)1406         boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
getTransportControls()1407         TransportControls getTransportControls();
getPlaybackState()1408         PlaybackStateCompat getPlaybackState();
getMetadata()1409         MediaMetadataCompat getMetadata();
1410 
getQueue()1411         List<QueueItem> getQueue();
addQueueItem(MediaDescriptionCompat description)1412         void addQueueItem(MediaDescriptionCompat description);
addQueueItem(MediaDescriptionCompat description, int index)1413         void addQueueItem(MediaDescriptionCompat description, int index);
removeQueueItem(MediaDescriptionCompat description)1414         void removeQueueItem(MediaDescriptionCompat description);
getQueueTitle()1415         CharSequence getQueueTitle();
getExtras()1416         Bundle getExtras();
getRatingType()1417         int getRatingType();
isCaptioningEnabled()1418         boolean isCaptioningEnabled();
getRepeatMode()1419         int getRepeatMode();
getShuffleMode()1420         int getShuffleMode();
getFlags()1421         long getFlags();
getPlaybackInfo()1422         PlaybackInfo getPlaybackInfo();
getSessionActivity()1423         PendingIntent getSessionActivity();
1424 
setVolumeTo(int value, int flags)1425         void setVolumeTo(int value, int flags);
adjustVolume(int direction, int flags)1426         void adjustVolume(int direction, int flags);
sendCommand(String command, Bundle params, ResultReceiver cb)1427         void sendCommand(String command, Bundle params, ResultReceiver cb);
1428 
isSessionReady()1429         boolean isSessionReady();
getPackageName()1430         String getPackageName();
getMediaController()1431         Object getMediaController();
1432     }
1433 
1434     static class MediaControllerImplBase implements MediaControllerImpl {
1435         private IMediaSession mBinder;
1436         private TransportControls mTransportControls;
1437 
MediaControllerImplBase(MediaSessionCompat.Token token)1438         public MediaControllerImplBase(MediaSessionCompat.Token token) {
1439             mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
1440         }
1441 
1442         @Override
registerCallback(Callback callback, Handler handler)1443         public void registerCallback(Callback callback, Handler handler) {
1444             if (callback == null) {
1445                 throw new IllegalArgumentException("callback may not be null.");
1446             }
1447             try {
1448                 mBinder.asBinder().linkToDeath(callback, 0);
1449                 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
1450             } catch (RemoteException e) {
1451                 Log.e(TAG, "Dead object in registerCallback.", e);
1452                 callback.onSessionDestroyed();
1453             }
1454         }
1455 
1456         @Override
unregisterCallback(Callback callback)1457         public void unregisterCallback(Callback callback) {
1458             if (callback == null) {
1459                 throw new IllegalArgumentException("callback may not be null.");
1460             }
1461             try {
1462                 mBinder.unregisterCallbackListener(
1463                         (IMediaControllerCallback) callback.mCallbackObj);
1464                 mBinder.asBinder().unlinkToDeath(callback, 0);
1465             } catch (RemoteException e) {
1466                 Log.e(TAG, "Dead object in unregisterCallback.", e);
1467             }
1468         }
1469 
1470         @Override
dispatchMediaButtonEvent(KeyEvent event)1471         public boolean dispatchMediaButtonEvent(KeyEvent event) {
1472             if (event == null) {
1473                 throw new IllegalArgumentException("event may not be null.");
1474             }
1475             try {
1476                 mBinder.sendMediaButton(event);
1477             } catch (RemoteException e) {
1478                 Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e);
1479             }
1480             return false;
1481         }
1482 
1483         @Override
getTransportControls()1484         public TransportControls getTransportControls() {
1485             if (mTransportControls == null) {
1486                 mTransportControls = new TransportControlsBase(mBinder);
1487             }
1488 
1489             return mTransportControls;
1490         }
1491 
1492         @Override
getPlaybackState()1493         public PlaybackStateCompat getPlaybackState() {
1494             try {
1495                 return mBinder.getPlaybackState();
1496             } catch (RemoteException e) {
1497                 Log.e(TAG, "Dead object in getPlaybackState.", e);
1498             }
1499             return null;
1500         }
1501 
1502         @Override
getMetadata()1503         public MediaMetadataCompat getMetadata() {
1504             try {
1505                 return mBinder.getMetadata();
1506             } catch (RemoteException e) {
1507                 Log.e(TAG, "Dead object in getMetadata.", e);
1508             }
1509             return null;
1510         }
1511 
1512         @Override
getQueue()1513         public List<QueueItem> getQueue() {
1514             try {
1515                 return mBinder.getQueue();
1516             } catch (RemoteException e) {
1517                 Log.e(TAG, "Dead object in getQueue.", e);
1518             }
1519             return null;
1520         }
1521 
1522         @Override
addQueueItem(MediaDescriptionCompat description)1523         public void addQueueItem(MediaDescriptionCompat description) {
1524             try {
1525                 long flags = mBinder.getFlags();
1526                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
1527                     throw new UnsupportedOperationException(
1528                             "This session doesn't support queue management operations");
1529                 }
1530                 mBinder.addQueueItem(description);
1531             } catch (RemoteException e) {
1532                 Log.e(TAG, "Dead object in addQueueItem.", e);
1533             }
1534         }
1535 
1536         @Override
addQueueItem(MediaDescriptionCompat description, int index)1537         public void addQueueItem(MediaDescriptionCompat description, int index) {
1538             try {
1539                 long flags = mBinder.getFlags();
1540                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
1541                     throw new UnsupportedOperationException(
1542                             "This session doesn't support queue management operations");
1543                 }
1544                 mBinder.addQueueItemAt(description, index);
1545             } catch (RemoteException e) {
1546                 Log.e(TAG, "Dead object in addQueueItemAt.", e);
1547             }
1548         }
1549 
1550         @Override
removeQueueItem(MediaDescriptionCompat description)1551         public void removeQueueItem(MediaDescriptionCompat description) {
1552             try {
1553                 long flags = mBinder.getFlags();
1554                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
1555                     throw new UnsupportedOperationException(
1556                             "This session doesn't support queue management operations");
1557                 }
1558                 mBinder.removeQueueItem(description);
1559             } catch (RemoteException e) {
1560                 Log.e(TAG, "Dead object in removeQueueItem.", e);
1561             }
1562         }
1563 
1564         @Override
getQueueTitle()1565         public CharSequence getQueueTitle() {
1566             try {
1567                 return mBinder.getQueueTitle();
1568             } catch (RemoteException e) {
1569                 Log.e(TAG, "Dead object in getQueueTitle.", e);
1570             }
1571             return null;
1572         }
1573 
1574         @Override
getExtras()1575         public Bundle getExtras() {
1576             try {
1577                 return mBinder.getExtras();
1578             } catch (RemoteException e) {
1579                 Log.e(TAG, "Dead object in getExtras.", e);
1580             }
1581             return null;
1582         }
1583 
1584         @Override
getRatingType()1585         public int getRatingType() {
1586             try {
1587                 return mBinder.getRatingType();
1588             } catch (RemoteException e) {
1589                 Log.e(TAG, "Dead object in getRatingType.", e);
1590             }
1591             return 0;
1592         }
1593 
1594         @Override
isCaptioningEnabled()1595         public boolean isCaptioningEnabled() {
1596             try {
1597                 return mBinder.isCaptioningEnabled();
1598             } catch (RemoteException e) {
1599                 Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
1600             }
1601             return false;
1602         }
1603 
1604         @Override
getRepeatMode()1605         public int getRepeatMode() {
1606             try {
1607                 return mBinder.getRepeatMode();
1608             } catch (RemoteException e) {
1609                 Log.e(TAG, "Dead object in getRepeatMode.", e);
1610             }
1611             return PlaybackStateCompat.REPEAT_MODE_INVALID;
1612         }
1613 
1614         @Override
getShuffleMode()1615         public int getShuffleMode() {
1616             try {
1617                 return mBinder.getShuffleMode();
1618             } catch (RemoteException e) {
1619                 Log.e(TAG, "Dead object in getShuffleMode.", e);
1620             }
1621             return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
1622         }
1623 
1624         @Override
getFlags()1625         public long getFlags() {
1626             try {
1627                 return mBinder.getFlags();
1628             } catch (RemoteException e) {
1629                 Log.e(TAG, "Dead object in getFlags.", e);
1630             }
1631             return 0;
1632         }
1633 
1634         @Override
getPlaybackInfo()1635         public PlaybackInfo getPlaybackInfo() {
1636             try {
1637                 ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
1638                 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
1639                         info.controlType, info.maxVolume, info.currentVolume);
1640                 return pi;
1641             } catch (RemoteException e) {
1642                 Log.e(TAG, "Dead object in getPlaybackInfo.", e);
1643             }
1644             return null;
1645         }
1646 
1647         @Override
getSessionActivity()1648         public PendingIntent getSessionActivity() {
1649             try {
1650                 return mBinder.getLaunchPendingIntent();
1651             } catch (RemoteException e) {
1652                 Log.e(TAG, "Dead object in getSessionActivity.", e);
1653             }
1654             return null;
1655         }
1656 
1657         @Override
setVolumeTo(int value, int flags)1658         public void setVolumeTo(int value, int flags) {
1659             try {
1660                 mBinder.setVolumeTo(value, flags, null);
1661             } catch (RemoteException e) {
1662                 Log.e(TAG, "Dead object in setVolumeTo.", e);
1663             }
1664         }
1665 
1666         @Override
adjustVolume(int direction, int flags)1667         public void adjustVolume(int direction, int flags) {
1668             try {
1669                 mBinder.adjustVolume(direction, flags, null);
1670             } catch (RemoteException e) {
1671                 Log.e(TAG, "Dead object in adjustVolume.", e);
1672             }
1673         }
1674 
1675         @Override
sendCommand(String command, Bundle params, ResultReceiver cb)1676         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1677             try {
1678                 mBinder.sendCommand(command, params,
1679                         new MediaSessionCompat.ResultReceiverWrapper(cb));
1680             } catch (RemoteException e) {
1681                 Log.e(TAG, "Dead object in sendCommand.", e);
1682             }
1683         }
1684 
1685         @Override
isSessionReady()1686         public boolean isSessionReady() {
1687             return true;
1688         }
1689 
1690         @Override
getPackageName()1691         public String getPackageName() {
1692             try {
1693                 return mBinder.getPackageName();
1694             } catch (RemoteException e) {
1695                 Log.e(TAG, "Dead object in getPackageName.", e);
1696             }
1697             return null;
1698         }
1699 
1700         @Override
getMediaController()1701         public Object getMediaController() {
1702             return null;
1703         }
1704     }
1705 
1706     static class TransportControlsBase extends TransportControls {
1707         private IMediaSession mBinder;
1708 
TransportControlsBase(IMediaSession binder)1709         public TransportControlsBase(IMediaSession binder) {
1710             mBinder = binder;
1711         }
1712 
1713         @Override
prepare()1714         public void prepare() {
1715             try {
1716                 mBinder.prepare();
1717             } catch (RemoteException e) {
1718                 Log.e(TAG, "Dead object in prepare.", e);
1719             }
1720         }
1721 
1722         @Override
prepareFromMediaId(String mediaId, Bundle extras)1723         public void prepareFromMediaId(String mediaId, Bundle extras) {
1724             try {
1725                 mBinder.prepareFromMediaId(mediaId, extras);
1726             } catch (RemoteException e) {
1727                 Log.e(TAG, "Dead object in prepareFromMediaId.", e);
1728             }
1729         }
1730 
1731         @Override
prepareFromSearch(String query, Bundle extras)1732         public void prepareFromSearch(String query, Bundle extras) {
1733             try {
1734                 mBinder.prepareFromSearch(query, extras);
1735             } catch (RemoteException e) {
1736                 Log.e(TAG, "Dead object in prepareFromSearch.", e);
1737             }
1738         }
1739 
1740         @Override
prepareFromUri(Uri uri, Bundle extras)1741         public void prepareFromUri(Uri uri, Bundle extras) {
1742             try {
1743                 mBinder.prepareFromUri(uri, extras);
1744             } catch (RemoteException e) {
1745                 Log.e(TAG, "Dead object in prepareFromUri.", e);
1746             }
1747         }
1748 
1749         @Override
play()1750         public void play() {
1751             try {
1752                 mBinder.play();
1753             } catch (RemoteException e) {
1754                 Log.e(TAG, "Dead object in play.", e);
1755             }
1756         }
1757 
1758         @Override
playFromMediaId(String mediaId, Bundle extras)1759         public void playFromMediaId(String mediaId, Bundle extras) {
1760             try {
1761                 mBinder.playFromMediaId(mediaId, extras);
1762             } catch (RemoteException e) {
1763                 Log.e(TAG, "Dead object in playFromMediaId.", e);
1764             }
1765         }
1766 
1767         @Override
playFromSearch(String query, Bundle extras)1768         public void playFromSearch(String query, Bundle extras) {
1769             try {
1770                 mBinder.playFromSearch(query, extras);
1771             } catch (RemoteException e) {
1772                 Log.e(TAG, "Dead object in playFromSearch.", e);
1773             }
1774         }
1775 
1776         @Override
playFromUri(Uri uri, Bundle extras)1777         public void playFromUri(Uri uri, Bundle extras) {
1778             try {
1779                 mBinder.playFromUri(uri, extras);
1780             } catch (RemoteException e) {
1781                 Log.e(TAG, "Dead object in playFromUri.", e);
1782             }
1783         }
1784 
1785         @Override
skipToQueueItem(long id)1786         public void skipToQueueItem(long id) {
1787             try {
1788                 mBinder.skipToQueueItem(id);
1789             } catch (RemoteException e) {
1790                 Log.e(TAG, "Dead object in skipToQueueItem.", e);
1791             }
1792         }
1793 
1794         @Override
pause()1795         public void pause() {
1796             try {
1797                 mBinder.pause();
1798             } catch (RemoteException e) {
1799                 Log.e(TAG, "Dead object in pause.", e);
1800             }
1801         }
1802 
1803         @Override
stop()1804         public void stop() {
1805             try {
1806                 mBinder.stop();
1807             } catch (RemoteException e) {
1808                 Log.e(TAG, "Dead object in stop.", e);
1809             }
1810         }
1811 
1812         @Override
seekTo(long pos)1813         public void seekTo(long pos) {
1814             try {
1815                 mBinder.seekTo(pos);
1816             } catch (RemoteException e) {
1817                 Log.e(TAG, "Dead object in seekTo.", e);
1818             }
1819         }
1820 
1821         @Override
fastForward()1822         public void fastForward() {
1823             try {
1824                 mBinder.fastForward();
1825             } catch (RemoteException e) {
1826                 Log.e(TAG, "Dead object in fastForward.", e);
1827             }
1828         }
1829 
1830         @Override
skipToNext()1831         public void skipToNext() {
1832             try {
1833                 mBinder.next();
1834             } catch (RemoteException e) {
1835                 Log.e(TAG, "Dead object in skipToNext.", e);
1836             }
1837         }
1838 
1839         @Override
rewind()1840         public void rewind() {
1841             try {
1842                 mBinder.rewind();
1843             } catch (RemoteException e) {
1844                 Log.e(TAG, "Dead object in rewind.", e);
1845             }
1846         }
1847 
1848         @Override
skipToPrevious()1849         public void skipToPrevious() {
1850             try {
1851                 mBinder.previous();
1852             } catch (RemoteException e) {
1853                 Log.e(TAG, "Dead object in skipToPrevious.", e);
1854             }
1855         }
1856 
1857         @Override
setRating(RatingCompat rating)1858         public void setRating(RatingCompat rating) {
1859             try {
1860                 mBinder.rate(rating);
1861             } catch (RemoteException e) {
1862                 Log.e(TAG, "Dead object in setRating.", e);
1863             }
1864         }
1865 
1866         @Override
setRating(RatingCompat rating, Bundle extras)1867         public void setRating(RatingCompat rating, Bundle extras) {
1868             try {
1869                 mBinder.rateWithExtras(rating, extras);
1870             } catch (RemoteException e) {
1871                 Log.e(TAG, "Dead object in setRating.", e);
1872             }
1873         }
1874 
1875         @Override
setCaptioningEnabled(boolean enabled)1876         public void setCaptioningEnabled(boolean enabled) {
1877             try {
1878                 mBinder.setCaptioningEnabled(enabled);
1879             } catch (RemoteException e) {
1880                 Log.e(TAG, "Dead object in setCaptioningEnabled.", e);
1881             }
1882         }
1883 
1884         @Override
setRepeatMode(@laybackStateCompat.RepeatMode int repeatMode)1885         public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
1886             try {
1887                 mBinder.setRepeatMode(repeatMode);
1888             } catch (RemoteException e) {
1889                 Log.e(TAG, "Dead object in setRepeatMode.", e);
1890             }
1891         }
1892 
1893         @Override
setShuffleMode(@laybackStateCompat.ShuffleMode int shuffleMode)1894         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
1895             try {
1896                 mBinder.setShuffleMode(shuffleMode);
1897             } catch (RemoteException e) {
1898                 Log.e(TAG, "Dead object in setShuffleMode.", e);
1899             }
1900         }
1901 
1902         @Override
sendCustomAction(CustomAction customAction, Bundle args)1903         public void sendCustomAction(CustomAction customAction, Bundle args) {
1904             sendCustomAction(customAction.getAction(), args);
1905         }
1906 
1907         @Override
sendCustomAction(String action, Bundle args)1908         public void sendCustomAction(String action, Bundle args) {
1909             validateCustomAction(action, args);
1910             try {
1911                 mBinder.sendCustomAction(action, args);
1912             } catch (RemoteException e) {
1913                 Log.e(TAG, "Dead object in sendCustomAction.", e);
1914             }
1915         }
1916     }
1917 
1918     @RequiresApi(21)
1919     static class MediaControllerImplApi21 implements MediaControllerImpl {
1920         protected final Object mControllerObj;
1921 
1922         private final List<Callback> mPendingCallbacks = new ArrayList<>();
1923 
1924         // Extra binder is used for applying the framework change of new APIs and bug fixes
1925         // after API 21.
1926         private IMediaSession mExtraBinder;
1927         private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>();
1928 
MediaControllerImplApi21(Context context, MediaSessionCompat session)1929         public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
1930             mControllerObj = MediaControllerCompatApi21.fromToken(context,
1931                     session.getSessionToken().getToken());
1932             mExtraBinder = session.getSessionToken().getExtraBinder();
1933             if (mExtraBinder == null) {
1934                 requestExtraBinder();
1935             }
1936         }
1937 
MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)1938         public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
1939                 throws RemoteException {
1940             mControllerObj = MediaControllerCompatApi21.fromToken(context,
1941                     sessionToken.getToken());
1942             if (mControllerObj == null) throw new RemoteException();
1943             mExtraBinder = sessionToken.getExtraBinder();
1944             if (mExtraBinder == null) {
1945                 requestExtraBinder();
1946             }
1947         }
1948 
1949         @Override
registerCallback(Callback callback, Handler handler)1950         public final void registerCallback(Callback callback, Handler handler) {
1951             MediaControllerCompatApi21.registerCallback(
1952                     mControllerObj, callback.mCallbackObj, handler);
1953             if (mExtraBinder != null) {
1954                 ExtraCallback extraCallback = new ExtraCallback(callback);
1955                 mCallbackMap.put(callback, extraCallback);
1956                 callback.mIControllerCallback = extraCallback;
1957                 try {
1958                     mExtraBinder.registerCallbackListener(extraCallback);
1959                 } catch (RemoteException e) {
1960                     Log.e(TAG, "Dead object in registerCallback.", e);
1961                 }
1962             } else {
1963                 synchronized (mPendingCallbacks) {
1964                     callback.mIControllerCallback = null;
1965                     mPendingCallbacks.add(callback);
1966                 }
1967             }
1968         }
1969 
1970         @Override
unregisterCallback(Callback callback)1971         public final void unregisterCallback(Callback callback) {
1972             MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
1973             if (mExtraBinder != null) {
1974                 try {
1975                     ExtraCallback extraCallback = mCallbackMap.remove(callback);
1976                     if (extraCallback != null) {
1977                         callback.mIControllerCallback = null;
1978                         mExtraBinder.unregisterCallbackListener(extraCallback);
1979                     }
1980                 } catch (RemoteException e) {
1981                     Log.e(TAG, "Dead object in unregisterCallback.", e);
1982                 }
1983             } else {
1984                 synchronized (mPendingCallbacks) {
1985                     mPendingCallbacks.remove(callback);
1986                 }
1987             }
1988         }
1989 
1990         @Override
dispatchMediaButtonEvent(KeyEvent event)1991         public boolean dispatchMediaButtonEvent(KeyEvent event) {
1992             return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
1993         }
1994 
1995         @Override
getTransportControls()1996         public TransportControls getTransportControls() {
1997             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1998             return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
1999         }
2000 
2001         @Override
getPlaybackState()2002         public PlaybackStateCompat getPlaybackState() {
2003             if (mExtraBinder != null) {
2004                 try {
2005                     return mExtraBinder.getPlaybackState();
2006                 } catch (RemoteException e) {
2007                     Log.e(TAG, "Dead object in getPlaybackState.", e);
2008                 }
2009             }
2010             Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
2011             return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
2012         }
2013 
2014         @Override
getMetadata()2015         public MediaMetadataCompat getMetadata() {
2016             Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
2017             return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
2018         }
2019 
2020         @Override
getQueue()2021         public List<QueueItem> getQueue() {
2022             List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
2023             return queueObjs != null ? QueueItem.fromQueueItemList(queueObjs) : null;
2024         }
2025 
2026         @Override
addQueueItem(MediaDescriptionCompat description)2027         public void addQueueItem(MediaDescriptionCompat description) {
2028             long flags = getFlags();
2029             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
2030                 throw new UnsupportedOperationException(
2031                         "This session doesn't support queue management operations");
2032             }
2033             Bundle params = new Bundle();
2034             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
2035             sendCommand(COMMAND_ADD_QUEUE_ITEM, params, null);
2036         }
2037 
2038         @Override
addQueueItem(MediaDescriptionCompat description, int index)2039         public void addQueueItem(MediaDescriptionCompat description, int index) {
2040             long flags = getFlags();
2041             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
2042                 throw new UnsupportedOperationException(
2043                         "This session doesn't support queue management operations");
2044             }
2045             Bundle params = new Bundle();
2046             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
2047             params.putInt(COMMAND_ARGUMENT_INDEX, index);
2048             sendCommand(COMMAND_ADD_QUEUE_ITEM_AT, params, null);
2049         }
2050 
2051         @Override
removeQueueItem(MediaDescriptionCompat description)2052         public void removeQueueItem(MediaDescriptionCompat description) {
2053             long flags = getFlags();
2054             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
2055                 throw new UnsupportedOperationException(
2056                         "This session doesn't support queue management operations");
2057             }
2058             Bundle params = new Bundle();
2059             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
2060             sendCommand(COMMAND_REMOVE_QUEUE_ITEM, params, null);
2061         }
2062 
2063         @Override
getQueueTitle()2064         public CharSequence getQueueTitle() {
2065             return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
2066         }
2067 
2068         @Override
getExtras()2069         public Bundle getExtras() {
2070             return MediaControllerCompatApi21.getExtras(mControllerObj);
2071         }
2072 
2073         @Override
getRatingType()2074         public int getRatingType() {
2075             if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) {
2076                 try {
2077                     return mExtraBinder.getRatingType();
2078                 } catch (RemoteException e) {
2079                     Log.e(TAG, "Dead object in getRatingType.", e);
2080                 }
2081             }
2082             return MediaControllerCompatApi21.getRatingType(mControllerObj);
2083         }
2084 
2085         @Override
isCaptioningEnabled()2086         public boolean isCaptioningEnabled() {
2087             if (mExtraBinder != null) {
2088                 try {
2089                     return mExtraBinder.isCaptioningEnabled();
2090                 } catch (RemoteException e) {
2091                     Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
2092                 }
2093             }
2094             return false;
2095         }
2096 
2097         @Override
getRepeatMode()2098         public int getRepeatMode() {
2099             if (mExtraBinder != null) {
2100                 try {
2101                     return mExtraBinder.getRepeatMode();
2102                 } catch (RemoteException e) {
2103                     Log.e(TAG, "Dead object in getRepeatMode.", e);
2104                 }
2105             }
2106             return PlaybackStateCompat.REPEAT_MODE_INVALID;
2107         }
2108 
2109         @Override
getShuffleMode()2110         public int getShuffleMode() {
2111             if (mExtraBinder != null) {
2112                 try {
2113                     return mExtraBinder.getShuffleMode();
2114                 } catch (RemoteException e) {
2115                     Log.e(TAG, "Dead object in getShuffleMode.", e);
2116                 }
2117             }
2118             return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
2119         }
2120 
2121         @Override
getFlags()2122         public long getFlags() {
2123             return MediaControllerCompatApi21.getFlags(mControllerObj);
2124         }
2125 
2126         @Override
getPlaybackInfo()2127         public PlaybackInfo getPlaybackInfo() {
2128             Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
2129             return volumeInfoObj != null ? new PlaybackInfo(
2130                     MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
2131                     MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
2132                     MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
2133                     MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
2134                     MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
2135         }
2136 
2137         @Override
getSessionActivity()2138         public PendingIntent getSessionActivity() {
2139             return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
2140         }
2141 
2142         @Override
setVolumeTo(int value, int flags)2143         public void setVolumeTo(int value, int flags) {
2144             MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
2145         }
2146 
2147         @Override
adjustVolume(int direction, int flags)2148         public void adjustVolume(int direction, int flags) {
2149             MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
2150         }
2151 
2152         @Override
sendCommand(String command, Bundle params, ResultReceiver cb)2153         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
2154             MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
2155         }
2156 
2157         @Override
isSessionReady()2158         public boolean isSessionReady() {
2159             return mExtraBinder != null;
2160         }
2161 
2162         @Override
getPackageName()2163         public String getPackageName() {
2164             return MediaControllerCompatApi21.getPackageName(mControllerObj);
2165         }
2166 
2167         @Override
getMediaController()2168         public Object getMediaController() {
2169             return mControllerObj;
2170         }
2171 
requestExtraBinder()2172         private void requestExtraBinder() {
2173             sendCommand(COMMAND_GET_EXTRA_BINDER, null,
2174                     new ExtraBinderRequestResultReceiver(this, new Handler()));
2175         }
2176 
processPendingCallbacks()2177         private void processPendingCallbacks() {
2178             if (mExtraBinder == null) {
2179                 return;
2180             }
2181             synchronized (mPendingCallbacks) {
2182                 for (Callback callback : mPendingCallbacks) {
2183                     ExtraCallback extraCallback = new ExtraCallback(callback);
2184                     mCallbackMap.put(callback, extraCallback);
2185                     callback.mIControllerCallback = extraCallback;
2186                     try {
2187                         mExtraBinder.registerCallbackListener(extraCallback);
2188                     } catch (RemoteException e) {
2189                         Log.e(TAG, "Dead object in registerCallback.", e);
2190                         break;
2191                     }
2192                     callback.onSessionReady();
2193                 }
2194                 mPendingCallbacks.clear();
2195             }
2196         }
2197 
2198         private static class ExtraBinderRequestResultReceiver extends ResultReceiver {
2199             private WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
2200 
ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl, Handler handler)2201             public ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl,
2202                     Handler handler) {
2203                 super(handler);
2204                 mMediaControllerImpl = new WeakReference<>(mediaControllerImpl);
2205             }
2206 
2207             @Override
onReceiveResult(int resultCode, Bundle resultData)2208             protected void onReceiveResult(int resultCode, Bundle resultData) {
2209                 MediaControllerImplApi21 mediaControllerImpl = mMediaControllerImpl.get();
2210                 if (mediaControllerImpl == null || resultData == null) {
2211                     return;
2212                 }
2213                 mediaControllerImpl.mExtraBinder = IMediaSession.Stub.asInterface(
2214                         BundleCompat.getBinder(resultData, MediaSessionCompat.EXTRA_BINDER));
2215                 mediaControllerImpl.processPendingCallbacks();
2216             }
2217         }
2218 
2219         private static class ExtraCallback extends Callback.StubCompat {
ExtraCallback(Callback callback)2220             ExtraCallback(Callback callback) {
2221                 super(callback);
2222             }
2223 
2224             @Override
onSessionDestroyed()2225             public void onSessionDestroyed() throws RemoteException {
2226                 // Will not be called.
2227                 throw new AssertionError();
2228             }
2229 
2230             @Override
onMetadataChanged(MediaMetadataCompat metadata)2231             public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
2232                 // Will not be called.
2233                 throw new AssertionError();
2234             }
2235 
2236             @Override
onQueueChanged(List<QueueItem> queue)2237             public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
2238                 // Will not be called.
2239                 throw new AssertionError();
2240             }
2241 
2242             @Override
onQueueTitleChanged(CharSequence title)2243             public void onQueueTitleChanged(CharSequence title) throws RemoteException {
2244                 // Will not be called.
2245                 throw new AssertionError();
2246             }
2247 
2248             @Override
onExtrasChanged(Bundle extras)2249             public void onExtrasChanged(Bundle extras) throws RemoteException {
2250                 // Will not be called.
2251                 throw new AssertionError();
2252             }
2253 
2254             @Override
onVolumeInfoChanged(ParcelableVolumeInfo info)2255             public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
2256                 // Will not be called.
2257                 throw new AssertionError();
2258             }
2259         }
2260     }
2261 
2262     static class TransportControlsApi21 extends TransportControls {
2263         protected final Object mControlsObj;
2264 
TransportControlsApi21(Object controlsObj)2265         public TransportControlsApi21(Object controlsObj) {
2266             mControlsObj = controlsObj;
2267         }
2268 
2269         @Override
prepare()2270         public void prepare() {
2271             sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
2272         }
2273 
2274         @Override
prepareFromMediaId(String mediaId, Bundle extras)2275         public void prepareFromMediaId(String mediaId, Bundle extras) {
2276             Bundle bundle = new Bundle();
2277             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
2278             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
2279             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
2280         }
2281 
2282         @Override
prepareFromSearch(String query, Bundle extras)2283         public void prepareFromSearch(String query, Bundle extras) {
2284             Bundle bundle = new Bundle();
2285             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
2286             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
2287             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
2288         }
2289 
2290         @Override
prepareFromUri(Uri uri, Bundle extras)2291         public void prepareFromUri(Uri uri, Bundle extras) {
2292             Bundle bundle = new Bundle();
2293             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
2294             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
2295             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
2296         }
2297 
2298         @Override
play()2299         public void play() {
2300             MediaControllerCompatApi21.TransportControls.play(mControlsObj);
2301         }
2302 
2303         @Override
pause()2304         public void pause() {
2305             MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
2306         }
2307 
2308         @Override
stop()2309         public void stop() {
2310             MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
2311         }
2312 
2313         @Override
seekTo(long pos)2314         public void seekTo(long pos) {
2315             MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
2316         }
2317 
2318         @Override
fastForward()2319         public void fastForward() {
2320             MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
2321         }
2322 
2323         @Override
rewind()2324         public void rewind() {
2325             MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
2326         }
2327 
2328         @Override
skipToNext()2329         public void skipToNext() {
2330             MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
2331         }
2332 
2333         @Override
skipToPrevious()2334         public void skipToPrevious() {
2335             MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
2336         }
2337 
2338         @Override
setRating(RatingCompat rating)2339         public void setRating(RatingCompat rating) {
2340             MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
2341                     rating != null ? rating.getRating() : null);
2342         }
2343 
2344         @Override
setRating(RatingCompat rating, Bundle extras)2345         public void setRating(RatingCompat rating, Bundle extras) {
2346             Bundle bundle = new Bundle();
2347             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_RATING, rating);
2348             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
2349             sendCustomAction(MediaSessionCompat.ACTION_SET_RATING, bundle);
2350         }
2351 
2352         @Override
setCaptioningEnabled(boolean enabled)2353         public void setCaptioningEnabled(boolean enabled) {
2354             Bundle bundle = new Bundle();
2355             bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
2356             sendCustomAction(MediaSessionCompat.ACTION_SET_CAPTIONING_ENABLED, bundle);
2357         }
2358 
2359         @Override
setRepeatMode(@laybackStateCompat.RepeatMode int repeatMode)2360         public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
2361             Bundle bundle = new Bundle();
2362             bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_REPEAT_MODE, repeatMode);
2363             sendCustomAction(MediaSessionCompat.ACTION_SET_REPEAT_MODE, bundle);
2364         }
2365 
2366         @Override
setShuffleMode(@laybackStateCompat.ShuffleMode int shuffleMode)2367         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
2368             Bundle bundle = new Bundle();
2369             bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
2370             sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE, bundle);
2371         }
2372 
2373         @Override
playFromMediaId(String mediaId, Bundle extras)2374         public void playFromMediaId(String mediaId, Bundle extras) {
2375             MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
2376                     extras);
2377         }
2378 
2379         @Override
playFromSearch(String query, Bundle extras)2380         public void playFromSearch(String query, Bundle extras) {
2381             MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
2382                     extras);
2383         }
2384 
2385         @Override
playFromUri(Uri uri, Bundle extras)2386         public void playFromUri(Uri uri, Bundle extras) {
2387             if (uri == null || Uri.EMPTY.equals(uri)) {
2388                 throw new IllegalArgumentException(
2389                         "You must specify a non-empty Uri for playFromUri.");
2390             }
2391             Bundle bundle = new Bundle();
2392             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
2393             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
2394             sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
2395         }
2396 
2397         @Override
skipToQueueItem(long id)2398         public void skipToQueueItem(long id) {
2399             MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
2400         }
2401 
2402         @Override
sendCustomAction(CustomAction customAction, Bundle args)2403         public void sendCustomAction(CustomAction customAction, Bundle args) {
2404             validateCustomAction(customAction.getAction(), args);
2405             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
2406                     customAction.getAction(), args);
2407         }
2408 
2409         @Override
sendCustomAction(String action, Bundle args)2410         public void sendCustomAction(String action, Bundle args) {
2411             validateCustomAction(action, args);
2412             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
2413                     args);
2414         }
2415     }
2416 
2417     @RequiresApi(23)
2418     static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
2419 
MediaControllerImplApi23(Context context, MediaSessionCompat session)2420         public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
2421             super(context, session);
2422         }
2423 
MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)2424         public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
2425                 throws RemoteException {
2426             super(context, sessionToken);
2427         }
2428 
2429         @Override
getTransportControls()2430         public TransportControls getTransportControls() {
2431             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
2432             return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
2433         }
2434     }
2435 
2436     @RequiresApi(23)
2437     static class TransportControlsApi23 extends TransportControlsApi21 {
2438 
TransportControlsApi23(Object controlsObj)2439         public TransportControlsApi23(Object controlsObj) {
2440             super(controlsObj);
2441         }
2442 
2443         @Override
playFromUri(Uri uri, Bundle extras)2444         public void playFromUri(Uri uri, Bundle extras) {
2445             MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
2446                     extras);
2447         }
2448     }
2449 
2450     @RequiresApi(24)
2451     static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
2452 
MediaControllerImplApi24(Context context, MediaSessionCompat session)2453         public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
2454             super(context, session);
2455         }
2456 
MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)2457         public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
2458                 throws RemoteException {
2459             super(context, sessionToken);
2460         }
2461 
2462         @Override
getTransportControls()2463         public TransportControls getTransportControls() {
2464             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
2465             return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
2466         }
2467     }
2468 
2469     @RequiresApi(24)
2470     static class TransportControlsApi24 extends TransportControlsApi23 {
2471 
TransportControlsApi24(Object controlsObj)2472         public TransportControlsApi24(Object controlsObj) {
2473             super(controlsObj);
2474         }
2475 
2476         @Override
prepare()2477         public void prepare() {
2478             MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
2479         }
2480 
2481         @Override
prepareFromMediaId(String mediaId, Bundle extras)2482         public void prepareFromMediaId(String mediaId, Bundle extras) {
2483             MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
2484                     mControlsObj, mediaId, extras);
2485         }
2486 
2487         @Override
prepareFromSearch(String query, Bundle extras)2488         public void prepareFromSearch(String query, Bundle extras) {
2489             MediaControllerCompatApi24.TransportControls.prepareFromSearch(
2490                     mControlsObj, query, extras);
2491         }
2492 
2493         @Override
prepareFromUri(Uri uri, Bundle extras)2494         public void prepareFromUri(Uri uri, Bundle extras) {
2495             MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
2496         }
2497     }
2498 }
2499