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