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