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.PendingIntent;
20import android.content.Context;
21import android.media.AudioManager;
22import android.net.Uri;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ResultReceiver;
30import android.support.v4.media.MediaMetadataCompat;
31import android.support.v4.media.RatingCompat;
32import android.support.v4.media.VolumeProviderCompat;
33import android.support.v4.media.session.MediaSessionCompat.QueueItem;
34import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
35import android.text.TextUtils;
36import android.util.Log;
37import android.view.KeyEvent;
38
39import java.util.List;
40
41/**
42 * Allows an app to interact with an ongoing media session. Media buttons and
43 * other commands can be sent to the session. A callback may be registered to
44 * receive updates from the session, such as metadata and play state changes.
45 * <p>
46 * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
47 * from the session owner.
48 * <p>
49 * MediaController objects are thread-safe.
50 * <p>
51 * This is a helper for accessing features in {@link android.media.session.MediaSession}
52 * introduced after API level 4 in a backwards compatible fashion.
53 */
54public final class MediaControllerCompat {
55    static final String TAG = "MediaControllerCompat";
56
57    private final MediaControllerImpl mImpl;
58    private final MediaSessionCompat.Token mToken;
59
60    /**
61     * Creates a media controller from a session.
62     *
63     * @param session The session to be controlled.
64     */
65    public MediaControllerCompat(Context context, MediaSessionCompat session) {
66        if (session == null) {
67            throw new IllegalArgumentException("session must not be null");
68        }
69        mToken = session.getSessionToken();
70
71        if (android.os.Build.VERSION.SDK_INT >= 24) {
72            mImpl = new MediaControllerImplApi24(context, session);
73        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
74            mImpl = new MediaControllerImplApi23(context, session);
75        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
76            mImpl = new MediaControllerImplApi21(context, session);
77        } else {
78            mImpl = new MediaControllerImplBase(mToken);
79        }
80    }
81
82    /**
83     * Creates a media controller from a session token which may have
84     * been obtained from another process.
85     *
86     * @param sessionToken The token of the session to be controlled.
87     * @throws RemoteException if the session is not accessible.
88     */
89    public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken)
90            throws RemoteException {
91        if (sessionToken == null) {
92            throw new IllegalArgumentException("sessionToken must not be null");
93        }
94        mToken = sessionToken;
95
96        if (android.os.Build.VERSION.SDK_INT >= 24) {
97            mImpl = new MediaControllerImplApi24(context, sessionToken);
98        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
99            mImpl = new MediaControllerImplApi23(context, sessionToken);
100        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
101            mImpl = new MediaControllerImplApi21(context, sessionToken);
102        } else {
103            mImpl = new MediaControllerImplBase(mToken);
104        }
105    }
106
107    /**
108     * Get a {@link TransportControls} instance for this session.
109     *
110     * @return A controls instance
111     */
112    public TransportControls getTransportControls() {
113        return mImpl.getTransportControls();
114    }
115
116    /**
117     * Send the specified media button event to the session. Only media keys can
118     * be sent by this method, other keys will be ignored.
119     *
120     * @param keyEvent The media button event to dispatch.
121     * @return true if the event was sent to the session, false otherwise.
122     */
123    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
124        if (keyEvent == null) {
125            throw new IllegalArgumentException("KeyEvent may not be null");
126        }
127        return mImpl.dispatchMediaButtonEvent(keyEvent);
128    }
129
130    /**
131     * Get the current playback state for this session.
132     *
133     * @return The current PlaybackState or null
134     */
135    public PlaybackStateCompat getPlaybackState() {
136        return mImpl.getPlaybackState();
137    }
138
139    /**
140     * Get the current metadata for this session.
141     *
142     * @return The current MediaMetadata or null.
143     */
144    public MediaMetadataCompat getMetadata() {
145        return mImpl.getMetadata();
146    }
147
148    /**
149     * Get the current play queue for this session if one is set. If you only
150     * care about the current item {@link #getMetadata()} should be used.
151     *
152     * @return The current play queue or null.
153     */
154    public List<MediaSessionCompat.QueueItem> getQueue() {
155        return mImpl.getQueue();
156    }
157
158    /**
159     * Get the queue title for this session.
160     */
161    public CharSequence getQueueTitle() {
162        return mImpl.getQueueTitle();
163    }
164
165    /**
166     * Get the extras for this session.
167     */
168    public Bundle getExtras() {
169        return mImpl.getExtras();
170    }
171
172    /**
173     * Get the rating type supported by the session. One of:
174     * <ul>
175     * <li>{@link RatingCompat#RATING_NONE}</li>
176     * <li>{@link RatingCompat#RATING_HEART}</li>
177     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
178     * <li>{@link RatingCompat#RATING_3_STARS}</li>
179     * <li>{@link RatingCompat#RATING_4_STARS}</li>
180     * <li>{@link RatingCompat#RATING_5_STARS}</li>
181     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
182     * </ul>
183     *
184     * @return The supported rating type
185     */
186    public int getRatingType() {
187        return mImpl.getRatingType();
188    }
189
190    /**
191     * Get the flags for this session. Flags are defined in
192     * {@link MediaSessionCompat}.
193     *
194     * @return The current set of flags for the session.
195     */
196    public long getFlags() {
197        return mImpl.getFlags();
198    }
199
200    /**
201     * Get the current playback info for this session.
202     *
203     * @return The current playback info or null.
204     */
205    public PlaybackInfo getPlaybackInfo() {
206        return mImpl.getPlaybackInfo();
207    }
208
209    /**
210     * Get an intent for launching UI associated with this session if one
211     * exists.
212     *
213     * @return A {@link PendingIntent} to launch UI or null.
214     */
215    public PendingIntent getSessionActivity() {
216        return mImpl.getSessionActivity();
217    }
218
219    /**
220     * Get the token for the session this controller is connected to.
221     *
222     * @return The session's token.
223     */
224    public MediaSessionCompat.Token getSessionToken() {
225        return mToken;
226    }
227
228    /**
229     * Set the volume of the output this session is playing on. The command will
230     * be ignored if it does not support
231     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
232     * {@link AudioManager} may be used to affect the handling.
233     *
234     * @see #getPlaybackInfo()
235     * @param value The value to set it to, between 0 and the reported max.
236     * @param flags Flags from {@link AudioManager} to include with the volume
237     *            request.
238     */
239    public void setVolumeTo(int value, int flags) {
240        mImpl.setVolumeTo(value, flags);
241    }
242
243    /**
244     * Adjust the volume of the output this session is playing on. The direction
245     * must be one of {@link AudioManager#ADJUST_LOWER},
246     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
247     * The command will be ignored if the session does not support
248     * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
249     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
250     * {@link AudioManager} may be used to affect the handling.
251     *
252     * @see #getPlaybackInfo()
253     * @param direction The direction to adjust the volume in.
254     * @param flags Any flags to pass with the command.
255     */
256    public void adjustVolume(int direction, int flags) {
257        mImpl.adjustVolume(direction, flags);
258    }
259
260    /**
261     * Adds a callback to receive updates from the Session. Updates will be
262     * posted on the caller's thread.
263     *
264     * @param callback The callback object, must not be null.
265     */
266    public void registerCallback(Callback callback) {
267        registerCallback(callback, null);
268    }
269
270    /**
271     * Adds a callback to receive updates from the session. Updates will be
272     * posted on the specified handler's thread.
273     *
274     * @param callback The callback object, must not be null.
275     * @param handler The handler to post updates on. If null the callers thread
276     *            will be used.
277     */
278    public void registerCallback(Callback callback, Handler handler) {
279        if (callback == null) {
280            throw new IllegalArgumentException("callback cannot be null");
281        }
282        if (handler == null) {
283            handler = new Handler();
284        }
285        mImpl.registerCallback(callback, handler);
286    }
287
288    /**
289     * Stop receiving updates on the specified callback. If an update has
290     * already been posted you may still receive it after calling this method.
291     *
292     * @param callback The callback to remove
293     */
294    public void unregisterCallback(Callback callback) {
295        if (callback == null) {
296            throw new IllegalArgumentException("callback cannot be null");
297        }
298        mImpl.unregisterCallback(callback);
299    }
300
301    /**
302     * Sends a generic command to the session. It is up to the session creator
303     * to decide what commands and parameters they will support. As such,
304     * commands should only be sent to sessions that the controller owns.
305     *
306     * @param command The command to send
307     * @param params Any parameters to include with the command
308     * @param cb The callback to receive the result on
309     */
310    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
311        if (TextUtils.isEmpty(command)) {
312            throw new IllegalArgumentException("command cannot be null or empty");
313        }
314        mImpl.sendCommand(command, params, cb);
315    }
316
317    /**
318     * Get the session owner's package name.
319     *
320     * @return The package name of of the session owner.
321     */
322    public String getPackageName() {
323        return mImpl.getPackageName();
324    }
325
326    /**
327     * Gets the underlying framework
328     * {@link android.media.session.MediaController} object.
329     * <p>
330     * This method is only supported on API 21+.
331     * </p>
332     *
333     * @return The underlying {@link android.media.session.MediaController}
334     *         object, or null if none.
335     */
336    public Object getMediaController() {
337        return mImpl.getMediaController();
338    }
339
340    /**
341     * Callback for receiving updates on from the session. A Callback can be
342     * registered using {@link #registerCallback}
343     */
344    public static abstract class Callback implements IBinder.DeathRecipient {
345        private final Object mCallbackObj;
346        MessageHandler mHandler;
347
348        boolean mRegistered = false;
349
350        public Callback() {
351            if (android.os.Build.VERSION.SDK_INT >= 21) {
352                mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
353            } else {
354                mCallbackObj = new StubCompat();
355            }
356        }
357
358        /**
359         * Override to handle the session being destroyed. The session is no
360         * longer valid after this call and calls to it will be ignored.
361         */
362        public void onSessionDestroyed() {
363        }
364
365        /**
366         * Override to handle custom events sent by the session owner without a
367         * specified interface. Controllers should only handle these for
368         * sessions they own.
369         *
370         * @param event The event from the session.
371         * @param extras Optional parameters for the event.
372         */
373        public void onSessionEvent(String event, Bundle extras) {
374        }
375
376        /**
377         * Override to handle changes in playback state.
378         *
379         * @param state The new playback state of the session
380         */
381        public void onPlaybackStateChanged(PlaybackStateCompat state) {
382        }
383
384        /**
385         * Override to handle changes to the current metadata.
386         *
387         * @param metadata The current metadata for the session or null if none.
388         * @see MediaMetadataCompat
389         */
390        public void onMetadataChanged(MediaMetadataCompat metadata) {
391        }
392
393        /**
394         * Override to handle changes to items in the queue.
395         *
396         * @see MediaSessionCompat.QueueItem
397         * @param queue A list of items in the current play queue. It should
398         *            include the currently playing item as well as previous and
399         *            upcoming items if applicable.
400         */
401        public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
402        }
403
404        /**
405         * Override to handle changes to the queue title.
406         *
407         * @param title The title that should be displayed along with the play
408         *            queue such as "Now Playing". May be null if there is no
409         *            such title.
410         */
411        public void onQueueTitleChanged(CharSequence title) {
412        }
413
414        /**
415         * Override to handle chagnes to the {@link MediaSessionCompat} extras.
416         *
417         * @param extras The extras that can include other information
418         *            associated with the {@link MediaSessionCompat}.
419         */
420        public void onExtrasChanged(Bundle extras) {
421        }
422
423        /**
424         * Override to handle changes to the audio info.
425         *
426         * @param info The current audio info for this session.
427         */
428        public void onAudioInfoChanged(PlaybackInfo info) {
429        }
430
431        @Override
432        public void binderDied() {
433            onSessionDestroyed();
434        }
435
436        /**
437         * Set the handler to use for pre 21 callbacks.
438         */
439        private void setHandler(Handler handler) {
440            mHandler = new MessageHandler(handler.getLooper());
441        }
442
443        private class StubApi21 implements MediaControllerCompatApi21.Callback {
444            StubApi21() {
445            }
446
447            @Override
448            public void onSessionDestroyed() {
449                Callback.this.onSessionDestroyed();
450            }
451
452            @Override
453            public void onSessionEvent(String event, Bundle extras) {
454                Callback.this.onSessionEvent(event, extras);
455            }
456
457            @Override
458            public void onPlaybackStateChanged(Object stateObj) {
459                Callback.this.onPlaybackStateChanged(
460                        PlaybackStateCompat.fromPlaybackState(stateObj));
461            }
462
463            @Override
464            public void onMetadataChanged(Object metadataObj) {
465                Callback.this.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
466            }
467
468            @Override
469            public void onQueueChanged(List<?> queue) {
470                Callback.this.onQueueChanged(QueueItem.fromQueueItemList(queue));
471            }
472
473            @Override
474            public void onQueueTitleChanged(CharSequence title) {
475                Callback.this.onQueueTitleChanged(title);
476            }
477
478            @Override
479            public void onExtrasChanged(Bundle extras) {
480                Callback.this.onExtrasChanged(extras);
481            }
482
483            @Override
484            public void onAudioInfoChanged(
485                    int type, int stream, int control, int max, int current) {
486                Callback.this.onAudioInfoChanged(
487                        new PlaybackInfo(type, stream, control, max, current));
488            }
489        }
490
491        private class StubCompat extends IMediaControllerCallback.Stub {
492
493            StubCompat() {
494            }
495
496            @Override
497            public void onEvent(String event, Bundle extras) throws RemoteException {
498                mHandler.post(MessageHandler.MSG_EVENT, event, extras);
499            }
500
501            @Override
502            public void onSessionDestroyed() throws RemoteException {
503                mHandler.post(MessageHandler.MSG_DESTROYED, null, null);
504            }
505
506            @Override
507            public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
508                mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
509            }
510
511            @Override
512            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
513                mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
514            }
515
516            @Override
517            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
518                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
519            }
520
521            @Override
522            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
523                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
524            }
525
526            @Override
527            public void onExtrasChanged(Bundle extras) throws RemoteException {
528                mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
529            }
530
531            @Override
532            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
533                PlaybackInfo pi = null;
534                if (info != null) {
535                    pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
536                            info.maxVolume, info.currentVolume);
537                }
538                mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
539            }
540        }
541
542        private class MessageHandler extends Handler {
543            private static final int MSG_EVENT = 1;
544            private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
545            private static final int MSG_UPDATE_METADATA = 3;
546            private static final int MSG_UPDATE_VOLUME = 4;
547            private static final int MSG_UPDATE_QUEUE = 5;
548            private static final int MSG_UPDATE_QUEUE_TITLE = 6;
549            private static final int MSG_UPDATE_EXTRAS = 7;
550            private static final int MSG_DESTROYED = 8;
551
552            public MessageHandler(Looper looper) {
553                super(looper);
554            }
555
556            @Override
557            public void handleMessage(Message msg) {
558                if (!mRegistered) {
559                    return;
560                }
561                switch (msg.what) {
562                    case MSG_EVENT:
563                        onSessionEvent((String) msg.obj, msg.getData());
564                        break;
565                    case MSG_UPDATE_PLAYBACK_STATE:
566                        onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
567                        break;
568                    case MSG_UPDATE_METADATA:
569                        onMetadataChanged((MediaMetadataCompat) msg.obj);
570                        break;
571                    case MSG_UPDATE_QUEUE:
572                        onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj);
573                        break;
574                    case MSG_UPDATE_QUEUE_TITLE:
575                        onQueueTitleChanged((CharSequence) msg.obj);
576                        break;
577                    case MSG_UPDATE_EXTRAS:
578                        onExtrasChanged((Bundle) msg.obj);
579                        break;
580                    case MSG_UPDATE_VOLUME:
581                        onAudioInfoChanged((PlaybackInfo) msg.obj);
582                        break;
583                    case MSG_DESTROYED:
584                        onSessionDestroyed();
585                        break;
586                }
587            }
588
589            public void post(int what, Object obj, Bundle data) {
590                Message msg = obtainMessage(what, obj);
591                msg.setData(data);
592                msg.sendToTarget();
593            }
594        }
595    }
596
597    /**
598     * Interface for controlling media playback on a session. This allows an app
599     * to send media transport commands to the session.
600     */
601    public static abstract class TransportControls {
602        TransportControls() {
603        }
604
605        /**
606         * Request that the player prepare its playback without audio focus. In other words, other
607         * session can continue to play during the preparation of this session. This method can be
608         * used to speed up the start of the playback. Once the preparation is done, the session
609         * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards,
610         * {@link #play} can be called to start playback. If the preparation is not needed,
611         * {@link #play} can be directly called without this method.
612         */
613        public abstract void prepare();
614
615        /**
616         * Request that the player prepare playback for a specific media id. In other words, other
617         * session can continue to play during the preparation of this session. This method can be
618         * used to speed up the start of the playback. Once the preparation is
619         * done, the session will change its playback state to
620         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
621         * start playback. If the preparation is not needed, {@link #playFromMediaId} can
622         * be directly called without this method.
623         *
624         * @param mediaId The id of the requested media.
625         * @param extras Optional extras that can include extra information about the media item
626         *               to be prepared.
627         */
628        public abstract void prepareFromMediaId(String mediaId, Bundle extras);
629
630        /**
631         * Request that the player prepare playback for a specific search query.
632         * An empty or null query should be treated as a request to prepare any
633         * music. In other words, other session can continue to play during
634         * the preparation of this session. This method can be used to speed up the start of the
635         * playback. Once the preparation is done, the session will change its playback state to
636         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
637         * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly
638         * called without this method.
639         *
640         * @param query The search query.
641         * @param extras Optional extras that can include extra information
642         *               about the query.
643         */
644        public abstract void prepareFromSearch(String query, Bundle extras);
645
646        /**
647         * Request that the player prepare playback for a specific {@link Uri}.
648         * In other words, other session can continue to play during the preparation of this
649         * session. This method can be used to speed up the start of the playback.
650         * Once the preparation is done, the session will change its playback state to
651         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
652         * start playback. If the preparation is not needed, {@link #playFromUri} can be directly
653         * called without this method.
654         *
655         * @param uri The URI of the requested media.
656         * @param extras Optional extras that can include extra information about the media item
657         *               to be prepared.
658         */
659        public abstract void prepareFromUri(Uri uri, Bundle extras);
660
661        /**
662         * Request that the player start its playback at its current position.
663         */
664        public abstract void play();
665
666        /**
667         * Request that the player start playback for a specific {@link Uri}.
668         *
669         * @param mediaId The uri of the requested media.
670         * @param extras Optional extras that can include extra information
671         *            about the media item to be played.
672         */
673        public abstract void playFromMediaId(String mediaId, Bundle extras);
674
675        /**
676         * Request that the player start playback for a specific search query.
677         * An empty or null query should be treated as a request to play any
678         * music.
679         *
680         * @param query The search query.
681         * @param extras Optional extras that can include extra information
682         *            about the query.
683         */
684        public abstract void playFromSearch(String query, Bundle extras);
685
686        /**
687         * Request that the player start playback for a specific {@link Uri}.
688         *
689         * @param uri  The URI of the requested media.
690         * @param extras Optional extras that can include extra information about the media item
691         *               to be played.
692         */
693        public abstract void playFromUri(Uri uri, Bundle extras);
694
695        /**
696         * Play an item with a specific id in the play queue. If you specify an
697         * id that is not in the play queue, the behavior is undefined.
698         */
699        public abstract void skipToQueueItem(long id);
700
701        /**
702         * Request that the player pause its playback and stay at its current
703         * position.
704         */
705        public abstract void pause();
706
707        /**
708         * Request that the player stop its playback; it may clear its state in
709         * whatever way is appropriate.
710         */
711        public abstract void stop();
712
713        /**
714         * Move to a new location in the media stream.
715         *
716         * @param pos Position to move to, in milliseconds.
717         */
718        public abstract void seekTo(long pos);
719
720        /**
721         * Start fast forwarding. If playback is already fast forwarding this
722         * may increase the rate.
723         */
724        public abstract void fastForward();
725
726        /**
727         * Skip to the next item.
728         */
729        public abstract void skipToNext();
730
731        /**
732         * Start rewinding. If playback is already rewinding this may increase
733         * the rate.
734         */
735        public abstract void rewind();
736
737        /**
738         * Skip to the previous item.
739         */
740        public abstract void skipToPrevious();
741
742        /**
743         * Rate the current content. This will cause the rating to be set for
744         * the current user. The Rating type must match the type returned by
745         * {@link #getRatingType()}.
746         *
747         * @param rating The rating to set for the current content
748         */
749        public abstract void setRating(RatingCompat rating);
750
751        /**
752         * Send a custom action for the {@link MediaSessionCompat} to perform.
753         *
754         * @param customAction The action to perform.
755         * @param args Optional arguments to supply to the
756         *            {@link MediaSessionCompat} for this custom action.
757         */
758        public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
759                Bundle args);
760
761        /**
762         * Send the id and args from a custom action for the
763         * {@link MediaSessionCompat} to perform.
764         *
765         * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
766         *      Bundle args)
767         * @param action The action identifier of the
768         *            {@link PlaybackStateCompat.CustomAction} as specified by
769         *            the {@link MediaSessionCompat}.
770         * @param args Optional arguments to supply to the
771         *            {@link MediaSessionCompat} for this custom action.
772         */
773        public abstract void sendCustomAction(String action, Bundle args);
774    }
775
776    /**
777     * Holds information about the way volume is handled for this session.
778     */
779    public static final class PlaybackInfo {
780        /**
781         * The session uses local playback.
782         */
783        public static final int PLAYBACK_TYPE_LOCAL = 1;
784        /**
785         * The session uses remote playback.
786         */
787        public static final int PLAYBACK_TYPE_REMOTE = 2;
788
789        private final int mPlaybackType;
790        // TODO update audio stream with AudioAttributes support version
791        private final int mAudioStream;
792        private final int mVolumeControl;
793        private final int mMaxVolume;
794        private final int mCurrentVolume;
795
796        PlaybackInfo(int type, int stream, int control, int max, int current) {
797            mPlaybackType = type;
798            mAudioStream = stream;
799            mVolumeControl = control;
800            mMaxVolume = max;
801            mCurrentVolume = current;
802        }
803
804        /**
805         * Get the type of volume handling, either local or remote. One of:
806         * <ul>
807         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
808         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
809         * </ul>
810         *
811         * @return The type of volume handling this session is using.
812         */
813        public int getPlaybackType() {
814            return mPlaybackType;
815        }
816
817        /**
818         * Get the stream this is currently controlling volume on. When the volume
819         * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
820         * have meaning and should be ignored.
821         *
822         * @return The stream this session is playing on.
823         */
824        public int getAudioStream() {
825            // TODO switch to AudioAttributesCompat when it is added.
826            return mAudioStream;
827        }
828
829        /**
830         * Get the type of volume control that can be used. One of:
831         * <ul>
832         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
833         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
834         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
835         * </ul>
836         *
837         * @return The type of volume control that may be used with this
838         *         session.
839         */
840        public int getVolumeControl() {
841            return mVolumeControl;
842        }
843
844        /**
845         * Get the maximum volume that may be set for this session.
846         *
847         * @return The maximum allowed volume where this session is playing.
848         */
849        public int getMaxVolume() {
850            return mMaxVolume;
851        }
852
853        /**
854         * Get the current volume for this session.
855         *
856         * @return The current volume where this session is playing.
857         */
858        public int getCurrentVolume() {
859            return mCurrentVolume;
860        }
861    }
862
863    interface MediaControllerImpl {
864        void registerCallback(Callback callback, Handler handler);
865
866        void unregisterCallback(Callback callback);
867        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
868        TransportControls getTransportControls();
869        PlaybackStateCompat getPlaybackState();
870        MediaMetadataCompat getMetadata();
871
872        List<MediaSessionCompat.QueueItem> getQueue();
873        CharSequence getQueueTitle();
874        Bundle getExtras();
875        int getRatingType();
876        long getFlags();
877        PlaybackInfo getPlaybackInfo();
878        PendingIntent getSessionActivity();
879
880        void setVolumeTo(int value, int flags);
881        void adjustVolume(int direction, int flags);
882        void sendCommand(String command, Bundle params, ResultReceiver cb);
883
884        String getPackageName();
885        Object getMediaController();
886    }
887
888    static class MediaControllerImplBase implements MediaControllerImpl {
889        private MediaSessionCompat.Token mToken;
890        private IMediaSession mBinder;
891        private TransportControls mTransportControls;
892
893        public MediaControllerImplBase(MediaSessionCompat.Token token) {
894            mToken = token;
895            mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
896        }
897
898        @Override
899        public void registerCallback(Callback callback, Handler handler) {
900            if (callback == null) {
901                throw new IllegalArgumentException("callback may not be null.");
902            }
903            try {
904                mBinder.asBinder().linkToDeath(callback, 0);
905                mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
906                callback.setHandler(handler);
907                callback.mRegistered = true;
908            } catch (RemoteException e) {
909                Log.e(TAG, "Dead object in registerCallback. " + e);
910                callback.onSessionDestroyed();
911            }
912        }
913
914        @Override
915        public void unregisterCallback(Callback callback) {
916            if (callback == null) {
917                throw new IllegalArgumentException("callback may not be null.");
918            }
919            try {
920                mBinder.unregisterCallbackListener(
921                        (IMediaControllerCallback) callback.mCallbackObj);
922                mBinder.asBinder().unlinkToDeath(callback, 0);
923                callback.mRegistered = false;
924            } catch (RemoteException e) {
925                Log.e(TAG, "Dead object in unregisterCallback. " + e);
926            }
927        }
928
929        @Override
930        public boolean dispatchMediaButtonEvent(KeyEvent event) {
931            if (event == null) {
932                throw new IllegalArgumentException("event may not be null.");
933            }
934            try {
935                mBinder.sendMediaButton(event);
936            } catch (RemoteException e) {
937                Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e);
938            }
939            return false;
940        }
941
942        @Override
943        public TransportControls getTransportControls() {
944            if (mTransportControls == null) {
945                mTransportControls = new TransportControlsBase(mBinder);
946            }
947
948            return mTransportControls;
949        }
950
951        @Override
952        public PlaybackStateCompat getPlaybackState() {
953            try {
954                return mBinder.getPlaybackState();
955            } catch (RemoteException e) {
956                Log.e(TAG, "Dead object in getPlaybackState. " + e);
957            }
958            return null;
959        }
960
961        @Override
962        public MediaMetadataCompat getMetadata() {
963            try {
964                return mBinder.getMetadata();
965            } catch (RemoteException e) {
966                Log.e(TAG, "Dead object in getMetadata. " + e);
967            }
968            return null;
969        }
970
971        @Override
972        public List<MediaSessionCompat.QueueItem> getQueue() {
973            try {
974                return mBinder.getQueue();
975            } catch (RemoteException e) {
976                Log.e(TAG, "Dead object in getQueue. " + e);
977            }
978            return null;
979        }
980
981        @Override
982        public CharSequence getQueueTitle() {
983            try {
984                return mBinder.getQueueTitle();
985            } catch (RemoteException e) {
986                Log.e(TAG, "Dead object in getQueueTitle. " + e);
987            }
988            return null;
989        }
990
991        @Override
992        public Bundle getExtras() {
993            try {
994                return mBinder.getExtras();
995            } catch (RemoteException e) {
996                Log.e(TAG, "Dead object in getExtras. " + e);
997            }
998            return null;
999        }
1000
1001        @Override
1002        public int getRatingType() {
1003            try {
1004                return mBinder.getRatingType();
1005            } catch (RemoteException e) {
1006                Log.e(TAG, "Dead object in getRatingType. " + e);
1007            }
1008            return 0;
1009        }
1010
1011        @Override
1012        public long getFlags() {
1013            try {
1014                return mBinder.getFlags();
1015            } catch (RemoteException e) {
1016                Log.e(TAG, "Dead object in getFlags. " + e);
1017            }
1018            return 0;
1019        }
1020
1021        @Override
1022        public PlaybackInfo getPlaybackInfo() {
1023            try {
1024                ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
1025                PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
1026                        info.controlType, info.maxVolume, info.currentVolume);
1027                return pi;
1028            } catch (RemoteException e) {
1029                Log.e(TAG, "Dead object in getPlaybackInfo. " + e);
1030            }
1031            return null;
1032        }
1033
1034        @Override
1035        public PendingIntent getSessionActivity() {
1036            try {
1037                return mBinder.getLaunchPendingIntent();
1038            } catch (RemoteException e) {
1039                Log.e(TAG, "Dead object in getSessionActivity. " + e);
1040            }
1041            return null;
1042        }
1043
1044        @Override
1045        public void setVolumeTo(int value, int flags) {
1046            try {
1047                mBinder.setVolumeTo(value, flags, null);
1048            } catch (RemoteException e) {
1049                Log.e(TAG, "Dead object in setVolumeTo. " + e);
1050            }
1051        }
1052
1053        @Override
1054        public void adjustVolume(int direction, int flags) {
1055            try {
1056                mBinder.adjustVolume(direction, flags, null);
1057            } catch (RemoteException e) {
1058                Log.e(TAG, "Dead object in adjustVolume. " + e);
1059            }
1060        }
1061
1062        @Override
1063        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1064            try {
1065                mBinder.sendCommand(command, params,
1066                        new MediaSessionCompat.ResultReceiverWrapper(cb));
1067            } catch (RemoteException e) {
1068                Log.e(TAG, "Dead object in sendCommand. " + e);
1069            }
1070        }
1071
1072        @Override
1073        public String getPackageName() {
1074            try {
1075                return mBinder.getPackageName();
1076            } catch (RemoteException e) {
1077                Log.e(TAG, "Dead object in getPackageName. " + e);
1078            }
1079            return null;
1080        }
1081
1082        @Override
1083        public Object getMediaController() {
1084            return null;
1085        }
1086    }
1087
1088    static class TransportControlsBase extends TransportControls {
1089        private IMediaSession mBinder;
1090
1091        public TransportControlsBase(IMediaSession binder) {
1092            mBinder = binder;
1093        }
1094
1095        @Override
1096        public void prepare() {
1097            try {
1098                mBinder.prepare();
1099            } catch (RemoteException e) {
1100                Log.e(TAG, "Dead object in prepare. " + e);
1101            }
1102        }
1103
1104        @Override
1105        public void prepareFromMediaId(String mediaId, Bundle extras) {
1106            try {
1107                mBinder.prepareFromMediaId(mediaId, extras);
1108            } catch (RemoteException e) {
1109                Log.e(TAG, "Dead object in prepareFromMediaId. " + e);
1110            }
1111        }
1112
1113        @Override
1114        public void prepareFromSearch(String query, Bundle extras) {
1115            try {
1116                mBinder.prepareFromSearch(query, extras);
1117            } catch (RemoteException e) {
1118                Log.e(TAG, "Dead object in prepareFromSearch. " + e);
1119            }
1120        }
1121
1122        @Override
1123        public void prepareFromUri(Uri uri, Bundle extras) {
1124            try {
1125                mBinder.prepareFromUri(uri, extras);
1126            } catch (RemoteException e) {
1127                Log.e(TAG, "Dead object in prepareFromUri. " + e);
1128            }
1129        }
1130
1131        @Override
1132        public void play() {
1133            try {
1134                mBinder.play();
1135            } catch (RemoteException e) {
1136                Log.e(TAG, "Dead object in play. " + e);
1137            }
1138        }
1139
1140        @Override
1141        public void playFromMediaId(String mediaId, Bundle extras) {
1142            try {
1143                mBinder.playFromMediaId(mediaId, extras);
1144            } catch (RemoteException e) {
1145                Log.e(TAG, "Dead object in playFromMediaId. " + e);
1146            }
1147        }
1148
1149        @Override
1150        public void playFromSearch(String query, Bundle extras) {
1151            try {
1152                mBinder.playFromSearch(query, extras);
1153            } catch (RemoteException e) {
1154                Log.e(TAG, "Dead object in playFromSearch. " + e);
1155            }
1156        }
1157
1158        @Override
1159        public void playFromUri(Uri uri, Bundle extras) {
1160            try {
1161                mBinder.playFromUri(uri, extras);
1162            } catch (RemoteException e) {
1163                Log.e(TAG, "Dead object in playFromUri. " + e);
1164            }
1165        }
1166
1167        @Override
1168        public void skipToQueueItem(long id) {
1169            try {
1170                mBinder.skipToQueueItem(id);
1171            } catch (RemoteException e) {
1172                Log.e(TAG, "Dead object in skipToQueueItem. " + e);
1173            }
1174        }
1175
1176        @Override
1177        public void pause() {
1178            try {
1179                mBinder.pause();
1180            } catch (RemoteException e) {
1181                Log.e(TAG, "Dead object in pause. " + e);
1182            }
1183        }
1184
1185        @Override
1186        public void stop() {
1187            try {
1188                mBinder.stop();
1189            } catch (RemoteException e) {
1190                Log.e(TAG, "Dead object in stop. " + e);
1191            }
1192        }
1193
1194        @Override
1195        public void seekTo(long pos) {
1196            try {
1197                mBinder.seekTo(pos);
1198            } catch (RemoteException e) {
1199                Log.e(TAG, "Dead object in seekTo. " + e);
1200            }
1201        }
1202
1203        @Override
1204        public void fastForward() {
1205            try {
1206                mBinder.fastForward();
1207            } catch (RemoteException e) {
1208                Log.e(TAG, "Dead object in fastForward. " + e);
1209            }
1210        }
1211
1212        @Override
1213        public void skipToNext() {
1214            try {
1215                mBinder.next();
1216            } catch (RemoteException e) {
1217                Log.e(TAG, "Dead object in skipToNext. " + e);
1218            }
1219        }
1220
1221        @Override
1222        public void rewind() {
1223            try {
1224                mBinder.rewind();
1225            } catch (RemoteException e) {
1226                Log.e(TAG, "Dead object in rewind. " + e);
1227            }
1228        }
1229
1230        @Override
1231        public void skipToPrevious() {
1232            try {
1233                mBinder.previous();
1234            } catch (RemoteException e) {
1235                Log.e(TAG, "Dead object in skipToPrevious. " + e);
1236            }
1237        }
1238
1239        @Override
1240        public void setRating(RatingCompat rating) {
1241            try {
1242                mBinder.rate(rating);
1243            } catch (RemoteException e) {
1244                Log.e(TAG, "Dead object in setRating. " + e);
1245            }
1246        }
1247
1248        @Override
1249        public void sendCustomAction(CustomAction customAction, Bundle args) {
1250            sendCustomAction(customAction.getAction(), args);
1251        }
1252
1253        @Override
1254        public void sendCustomAction(String action, Bundle args) {
1255            try {
1256                mBinder.sendCustomAction(action, args);
1257            } catch (RemoteException e) {
1258                Log.e(TAG, "Dead object in sendCustomAction. " + e);
1259            }
1260        }
1261    }
1262
1263    static class MediaControllerImplApi21 implements MediaControllerImpl {
1264        protected final Object mControllerObj;
1265
1266        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
1267            mControllerObj = MediaControllerCompatApi21.fromToken(context,
1268                    session.getSessionToken().getToken());
1269        }
1270
1271        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
1272                throws RemoteException {
1273            mControllerObj = MediaControllerCompatApi21.fromToken(context,
1274                    sessionToken.getToken());
1275            if (mControllerObj == null) throw new RemoteException();
1276        }
1277
1278        @Override
1279        public void registerCallback(Callback callback, Handler handler) {
1280            MediaControllerCompatApi21.registerCallback(mControllerObj, callback.mCallbackObj, handler);
1281        }
1282
1283        @Override
1284        public void unregisterCallback(Callback callback) {
1285            MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
1286        }
1287
1288        @Override
1289        public boolean dispatchMediaButtonEvent(KeyEvent event) {
1290            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
1291        }
1292
1293        @Override
1294        public TransportControls getTransportControls() {
1295            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1296            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
1297        }
1298
1299        @Override
1300        public PlaybackStateCompat getPlaybackState() {
1301            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
1302            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
1303        }
1304
1305        @Override
1306        public MediaMetadataCompat getMetadata() {
1307            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
1308            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
1309        }
1310
1311        @Override
1312        public List<MediaSessionCompat.QueueItem> getQueue() {
1313            List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
1314            return queueObjs != null ? MediaSessionCompat.QueueItem.fromQueueItemList(queueObjs)
1315                    : null;
1316        }
1317
1318        @Override
1319        public CharSequence getQueueTitle() {
1320            return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
1321        }
1322
1323        @Override
1324        public Bundle getExtras() {
1325            return MediaControllerCompatApi21.getExtras(mControllerObj);
1326        }
1327
1328        @Override
1329        public int getRatingType() {
1330            return MediaControllerCompatApi21.getRatingType(mControllerObj);
1331        }
1332
1333        @Override
1334        public long getFlags() {
1335            return MediaControllerCompatApi21.getFlags(mControllerObj);
1336        }
1337
1338        @Override
1339        public PlaybackInfo getPlaybackInfo() {
1340            Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
1341            return volumeInfoObj != null ? new PlaybackInfo(
1342                    MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
1343                    MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
1344                    MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
1345                    MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
1346                    MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
1347        }
1348
1349        @Override
1350        public PendingIntent getSessionActivity() {
1351            return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
1352        }
1353
1354        @Override
1355        public void setVolumeTo(int value, int flags) {
1356            MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
1357        }
1358
1359        @Override
1360        public void adjustVolume(int direction, int flags) {
1361            MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
1362        }
1363
1364        @Override
1365        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1366            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
1367        }
1368
1369        @Override
1370        public String getPackageName() {
1371            return MediaControllerCompatApi21.getPackageName(mControllerObj);
1372        }
1373
1374        @Override
1375        public Object getMediaController() {
1376            return mControllerObj;
1377        }
1378    }
1379
1380    static class TransportControlsApi21 extends TransportControls {
1381        protected final Object mControlsObj;
1382
1383        public TransportControlsApi21(Object controlsObj) {
1384            mControlsObj = controlsObj;
1385        }
1386
1387        @Override
1388        public void prepare() {
1389            sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
1390        }
1391
1392        @Override
1393        public void prepareFromMediaId(String mediaId, Bundle extras) {
1394            Bundle bundle = new Bundle();
1395            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
1396            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1397            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
1398        }
1399
1400        @Override
1401        public void prepareFromSearch(String query, Bundle extras) {
1402            Bundle bundle = new Bundle();
1403            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
1404            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1405            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
1406        }
1407
1408        @Override
1409        public void prepareFromUri(Uri uri, Bundle extras) {
1410            Bundle bundle = new Bundle();
1411            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1412            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1413            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
1414        }
1415
1416        @Override
1417        public void play() {
1418            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
1419        }
1420
1421        @Override
1422        public void pause() {
1423            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
1424        }
1425
1426        @Override
1427        public void stop() {
1428            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
1429        }
1430
1431        @Override
1432        public void seekTo(long pos) {
1433            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
1434        }
1435
1436        @Override
1437        public void fastForward() {
1438            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
1439        }
1440
1441        @Override
1442        public void rewind() {
1443            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
1444        }
1445
1446        @Override
1447        public void skipToNext() {
1448            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
1449        }
1450
1451        @Override
1452        public void skipToPrevious() {
1453            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
1454        }
1455
1456        @Override
1457        public void setRating(RatingCompat rating) {
1458            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
1459                    rating != null ? rating.getRating() : null);
1460        }
1461
1462        @Override
1463        public void playFromMediaId(String mediaId, Bundle extras) {
1464            MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
1465                    extras);
1466        }
1467
1468        @Override
1469        public void playFromSearch(String query, Bundle extras) {
1470            MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
1471                    extras);
1472        }
1473
1474        @Override
1475        public void playFromUri(Uri uri, Bundle extras) {
1476            if (uri == null || Uri.EMPTY.equals(uri)) {
1477                throw new IllegalArgumentException(
1478                        "You must specify a non-empty Uri for playFromUri.");
1479            }
1480            Bundle bundle = new Bundle();
1481            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1482            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1483            sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
1484        }
1485
1486        @Override
1487        public void skipToQueueItem(long id) {
1488            MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
1489        }
1490
1491        @Override
1492        public void sendCustomAction(CustomAction customAction, Bundle args) {
1493            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
1494                    customAction.getAction(), args);
1495        }
1496
1497        @Override
1498        public void sendCustomAction(String action, Bundle args) {
1499            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
1500                    args);
1501        }
1502    }
1503
1504    static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
1505
1506        public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
1507            super(context, session);
1508        }
1509
1510        public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
1511                throws RemoteException {
1512            super(context, sessionToken);
1513        }
1514
1515        @Override
1516        public TransportControls getTransportControls() {
1517            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1518            return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
1519        }
1520    }
1521
1522    static class TransportControlsApi23 extends TransportControlsApi21 {
1523
1524        public TransportControlsApi23(Object controlsObj) {
1525            super(controlsObj);
1526        }
1527
1528        @Override
1529        public void playFromUri(Uri uri, Bundle extras) {
1530            MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
1531                    extras);
1532        }
1533    }
1534
1535    static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
1536
1537        public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
1538            super(context, session);
1539        }
1540
1541        public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
1542                throws RemoteException {
1543            super(context, sessionToken);
1544        }
1545
1546        @Override
1547        public TransportControls getTransportControls() {
1548            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1549            return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
1550        }
1551    }
1552
1553    static class TransportControlsApi24 extends TransportControlsApi23 {
1554
1555        public TransportControlsApi24(Object controlsObj) {
1556            super(controlsObj);
1557        }
1558
1559        @Override
1560        public void prepare() {
1561            MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
1562        }
1563
1564        @Override
1565        public void prepareFromMediaId(String mediaId, Bundle extras) {
1566            MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
1567                    mControlsObj, mediaId, extras);
1568        }
1569
1570        @Override
1571        public void prepareFromSearch(String query, Bundle extras) {
1572            MediaControllerCompatApi24.TransportControls.prepareFromSearch(
1573                    mControlsObj, query, extras);
1574        }
1575
1576        @Override
1577        public void prepareFromUri(Uri uri, Bundle extras) {
1578            MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
1579        }
1580    }
1581
1582}
1583