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