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