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