MediaControllerCompat.java revision 203a34227e8fa3bd16721fb7ff450fb6feba7c50
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    private 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        private MessageHandler mHandler;
347
348        private 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            @Override
445            public void onSessionDestroyed() {
446                Callback.this.onSessionDestroyed();
447            }
448
449            @Override
450            public void onSessionEvent(String event, Bundle extras) {
451                Callback.this.onSessionEvent(event, extras);
452            }
453
454            @Override
455            public void onPlaybackStateChanged(Object stateObj) {
456                Callback.this.onPlaybackStateChanged(
457                        PlaybackStateCompat.fromPlaybackState(stateObj));
458            }
459
460            @Override
461            public void onMetadataChanged(Object metadataObj) {
462                Callback.this.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
463            }
464
465            @Override
466            public void onQueueChanged(List<?> queue) {
467                Callback.this.onQueueChanged(QueueItem.fromQueueItemList(queue));
468            }
469
470            @Override
471            public void onQueueTitleChanged(CharSequence title) {
472                Callback.this.onQueueTitleChanged(title);
473            }
474
475            @Override
476            public void onExtrasChanged(Bundle extras) {
477                Callback.this.onExtrasChanged(extras);
478            }
479
480            @Override
481            public void onAudioInfoChanged(
482                    int type, int stream, int control, int max, int current) {
483                Callback.this.onAudioInfoChanged(
484                        new PlaybackInfo(type, stream, control, max, current));
485            }
486        }
487
488        private class StubCompat extends IMediaControllerCallback.Stub {
489
490            @Override
491            public void onEvent(String event, Bundle extras) throws RemoteException {
492                mHandler.post(MessageHandler.MSG_EVENT, event, extras);
493            }
494
495            @Override
496            public void onSessionDestroyed() throws RemoteException {
497                mHandler.post(MessageHandler.MSG_DESTROYED, null, null);
498            }
499
500            @Override
501            public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
502                mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
503            }
504
505            @Override
506            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
507                mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
508            }
509
510            @Override
511            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
512                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
513            }
514
515            @Override
516            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
517                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
518            }
519
520            @Override
521            public void onExtrasChanged(Bundle extras) throws RemoteException {
522                mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
523            }
524
525            @Override
526            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
527                PlaybackInfo pi = null;
528                if (info != null) {
529                    pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
530                            info.maxVolume, info.currentVolume);
531                }
532                mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
533            }
534        }
535
536        private class MessageHandler extends Handler {
537            private static final int MSG_EVENT = 1;
538            private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
539            private static final int MSG_UPDATE_METADATA = 3;
540            private static final int MSG_UPDATE_VOLUME = 4;
541            private static final int MSG_UPDATE_QUEUE = 5;
542            private static final int MSG_UPDATE_QUEUE_TITLE = 6;
543            private static final int MSG_UPDATE_EXTRAS = 7;
544            private static final int MSG_DESTROYED = 8;
545
546            public MessageHandler(Looper looper) {
547                super(looper);
548            }
549
550            @Override
551            public void handleMessage(Message msg) {
552                if (!mRegistered) {
553                    return;
554                }
555                switch (msg.what) {
556                    case MSG_EVENT:
557                        onSessionEvent((String) msg.obj, msg.getData());
558                        break;
559                    case MSG_UPDATE_PLAYBACK_STATE:
560                        onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
561                        break;
562                    case MSG_UPDATE_METADATA:
563                        onMetadataChanged((MediaMetadataCompat) msg.obj);
564                        break;
565                    case MSG_UPDATE_QUEUE:
566                        onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj);
567                        break;
568                    case MSG_UPDATE_QUEUE_TITLE:
569                        onQueueTitleChanged((CharSequence) msg.obj);
570                        break;
571                    case MSG_UPDATE_EXTRAS:
572                        onExtrasChanged((Bundle) msg.obj);
573                        break;
574                    case MSG_UPDATE_VOLUME:
575                        onAudioInfoChanged((PlaybackInfo) msg.obj);
576                        break;
577                    case MSG_DESTROYED:
578                        onSessionDestroyed();
579                        break;
580                }
581            }
582
583            public void post(int what, Object obj, Bundle data) {
584                Message msg = obtainMessage(what, obj);
585                msg.setData(data);
586                msg.sendToTarget();
587            }
588        }
589    }
590
591    /**
592     * Interface for controlling media playback on a session. This allows an app
593     * to send media transport commands to the session.
594     */
595    public static abstract class TransportControls {
596        TransportControls() {
597        }
598
599        /**
600         * Request that the player prepare its playback without audio focus. In other words, other
601         * session can continue to play during the preparation of this session. This method can be
602         * used to speed up the start of the playback. Once the preparation is done, the session
603         * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards,
604         * {@link #play} can be called to start playback. If the preparation is not needed,
605         * {@link #play} can be directly called without this method.
606         */
607        public abstract void prepare();
608
609        /**
610         * Request that the player prepare playback for a specific media id. In other words, other
611         * session can continue to play during the preparation of this session. This method can be
612         * used to speed up the start of the playback. Once the preparation is
613         * done, the session will change its playback state to
614         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
615         * start playback. If the preparation is not needed, {@link #playFromMediaId} can
616         * be directly called without this method.
617         *
618         * @param mediaId The id of the requested media.
619         * @param extras Optional extras that can include extra information about the media item
620         *               to be prepared.
621         */
622        public abstract void prepareFromMediaId(String mediaId, Bundle extras);
623
624        /**
625         * Request that the player prepare playback for a specific search query.
626         * An empty or null query should be treated as a request to prepare any
627         * music. In other words, other session can continue to play during
628         * the preparation of this session. This method can be used to speed up the start of the
629         * playback. Once the preparation is done, the session will change its playback state to
630         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
631         * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly
632         * called without this method.
633         *
634         * @param query The search query.
635         * @param extras Optional extras that can include extra information
636         *               about the query.
637         */
638        public abstract void prepareFromSearch(String query, Bundle extras);
639
640        /**
641         * Request that the player prepare playback for a specific {@link Uri}.
642         * In other words, other session can continue to play during the preparation of this
643         * session. This method can be used to speed up the start of the playback.
644         * Once the preparation is done, the session will change its playback state to
645         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
646         * start playback. If the preparation is not needed, {@link #playFromUri} can be directly
647         * called without this method.
648         *
649         * @param uri The URI of the requested media.
650         * @param extras Optional extras that can include extra information about the media item
651         *               to be prepared.
652         */
653        public abstract void prepareFromUri(Uri uri, Bundle extras);
654
655        /**
656         * Request that the player start its playback at its current position.
657         */
658        public abstract void play();
659
660        /**
661         * Request that the player start playback for a specific {@link Uri}.
662         *
663         * @param mediaId The uri of the requested media.
664         * @param extras Optional extras that can include extra information
665         *            about the media item to be played.
666         */
667        public abstract void playFromMediaId(String mediaId, Bundle extras);
668
669        /**
670         * Request that the player start playback for a specific search query.
671         * An empty or null query should be treated as a request to play any
672         * music.
673         *
674         * @param query The search query.
675         * @param extras Optional extras that can include extra information
676         *            about the query.
677         */
678        public abstract void playFromSearch(String query, Bundle extras);
679
680        /**
681         * Request that the player start playback for a specific {@link Uri}.
682         *
683         * @param uri  The URI of the requested media.
684         * @param extras Optional extras that can include extra information about the media item
685         *               to be played.
686         */
687        public abstract void playFromUri(Uri uri, Bundle extras);
688
689        /**
690         * Play an item with a specific id in the play queue. If you specify an
691         * id that is not in the play queue, the behavior is undefined.
692         */
693        public abstract void skipToQueueItem(long id);
694
695        /**
696         * Request that the player pause its playback and stay at its current
697         * position.
698         */
699        public abstract void pause();
700
701        /**
702         * Request that the player stop its playback; it may clear its state in
703         * whatever way is appropriate.
704         */
705        public abstract void stop();
706
707        /**
708         * Move to a new location in the media stream.
709         *
710         * @param pos Position to move to, in milliseconds.
711         */
712        public abstract void seekTo(long pos);
713
714        /**
715         * Start fast forwarding. If playback is already fast forwarding this
716         * may increase the rate.
717         */
718        public abstract void fastForward();
719
720        /**
721         * Skip to the next item.
722         */
723        public abstract void skipToNext();
724
725        /**
726         * Start rewinding. If playback is already rewinding this may increase
727         * the rate.
728         */
729        public abstract void rewind();
730
731        /**
732         * Skip to the previous item.
733         */
734        public abstract void skipToPrevious();
735
736        /**
737         * Rate the current content. This will cause the rating to be set for
738         * the current user. The Rating type must match the type returned by
739         * {@link #getRatingType()}.
740         *
741         * @param rating The rating to set for the current content
742         */
743        public abstract void setRating(RatingCompat rating);
744
745        /**
746         * Send a custom action for the {@link MediaSessionCompat} to perform.
747         *
748         * @param customAction The action to perform.
749         * @param args Optional arguments to supply to the
750         *            {@link MediaSessionCompat} for this custom action.
751         */
752        public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
753                Bundle args);
754
755        /**
756         * Send the id and args from a custom action for the
757         * {@link MediaSessionCompat} to perform.
758         *
759         * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
760         *      Bundle args)
761         * @param action The action identifier of the
762         *            {@link PlaybackStateCompat.CustomAction} as specified by
763         *            the {@link MediaSessionCompat}.
764         * @param args Optional arguments to supply to the
765         *            {@link MediaSessionCompat} for this custom action.
766         */
767        public abstract void sendCustomAction(String action, Bundle args);
768    }
769
770    /**
771     * Holds information about the way volume is handled for this session.
772     */
773    public static final class PlaybackInfo {
774        /**
775         * The session uses local playback.
776         */
777        public static final int PLAYBACK_TYPE_LOCAL = 1;
778        /**
779         * The session uses remote playback.
780         */
781        public static final int PLAYBACK_TYPE_REMOTE = 2;
782
783        private final int mPlaybackType;
784        // TODO update audio stream with AudioAttributes support version
785        private final int mAudioStream;
786        private final int mVolumeControl;
787        private final int mMaxVolume;
788        private final int mCurrentVolume;
789
790        PlaybackInfo(int type, int stream, int control, int max, int current) {
791            mPlaybackType = type;
792            mAudioStream = stream;
793            mVolumeControl = control;
794            mMaxVolume = max;
795            mCurrentVolume = current;
796        }
797
798        /**
799         * Get the type of volume handling, either local or remote. One of:
800         * <ul>
801         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
802         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
803         * </ul>
804         *
805         * @return The type of volume handling this session is using.
806         */
807        public int getPlaybackType() {
808            return mPlaybackType;
809        }
810
811        /**
812         * Get the stream this is currently controlling volume on. When the volume
813         * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
814         * have meaning and should be ignored.
815         *
816         * @return The stream this session is playing on.
817         */
818        public int getAudioStream() {
819            // TODO switch to AudioAttributesCompat when it is added.
820            return mAudioStream;
821        }
822
823        /**
824         * Get the type of volume control that can be used. One of:
825         * <ul>
826         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
827         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
828         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
829         * </ul>
830         *
831         * @return The type of volume control that may be used with this
832         *         session.
833         */
834        public int getVolumeControl() {
835            return mVolumeControl;
836        }
837
838        /**
839         * Get the maximum volume that may be set for this session.
840         *
841         * @return The maximum allowed volume where this session is playing.
842         */
843        public int getMaxVolume() {
844            return mMaxVolume;
845        }
846
847        /**
848         * Get the current volume for this session.
849         *
850         * @return The current volume where this session is playing.
851         */
852        public int getCurrentVolume() {
853            return mCurrentVolume;
854        }
855    }
856
857    interface MediaControllerImpl {
858        void registerCallback(Callback callback, Handler handler);
859
860        void unregisterCallback(Callback callback);
861        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
862        TransportControls getTransportControls();
863        PlaybackStateCompat getPlaybackState();
864        MediaMetadataCompat getMetadata();
865
866        List<MediaSessionCompat.QueueItem> getQueue();
867        CharSequence getQueueTitle();
868        Bundle getExtras();
869        int getRatingType();
870        long getFlags();
871        PlaybackInfo getPlaybackInfo();
872        PendingIntent getSessionActivity();
873
874        void setVolumeTo(int value, int flags);
875        void adjustVolume(int direction, int flags);
876        void sendCommand(String command, Bundle params, ResultReceiver cb);
877
878        String getPackageName();
879        Object getMediaController();
880    }
881
882    static class MediaControllerImplBase implements MediaControllerImpl {
883        private MediaSessionCompat.Token mToken;
884        private IMediaSession mBinder;
885        private TransportControls mTransportControls;
886
887        public MediaControllerImplBase(MediaSessionCompat.Token token) {
888            mToken = token;
889            mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
890        }
891
892        @Override
893        public void registerCallback(Callback callback, Handler handler) {
894            if (callback == null) {
895                throw new IllegalArgumentException("callback may not be null.");
896            }
897            try {
898                mBinder.asBinder().linkToDeath(callback, 0);
899                mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
900                callback.setHandler(handler);
901                callback.mRegistered = true;
902            } catch (RemoteException e) {
903                Log.e(TAG, "Dead object in registerCallback. " + e);
904                callback.onSessionDestroyed();
905            }
906        }
907
908        @Override
909        public void unregisterCallback(Callback callback) {
910            if (callback == null) {
911                throw new IllegalArgumentException("callback may not be null.");
912            }
913            try {
914                mBinder.unregisterCallbackListener(
915                        (IMediaControllerCallback) callback.mCallbackObj);
916                mBinder.asBinder().unlinkToDeath(callback, 0);
917                callback.mRegistered = false;
918            } catch (RemoteException e) {
919                Log.e(TAG, "Dead object in unregisterCallback. " + e);
920            }
921        }
922
923        @Override
924        public boolean dispatchMediaButtonEvent(KeyEvent event) {
925            if (event == null) {
926                throw new IllegalArgumentException("event may not be null.");
927            }
928            try {
929                mBinder.sendMediaButton(event);
930            } catch (RemoteException e) {
931                Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e);
932            }
933            return false;
934        }
935
936        @Override
937        public TransportControls getTransportControls() {
938            if (mTransportControls == null) {
939                mTransportControls = new TransportControlsBase(mBinder);
940            }
941
942            return mTransportControls;
943        }
944
945        @Override
946        public PlaybackStateCompat getPlaybackState() {
947            try {
948                return mBinder.getPlaybackState();
949            } catch (RemoteException e) {
950                Log.e(TAG, "Dead object in getPlaybackState. " + e);
951            }
952            return null;
953        }
954
955        @Override
956        public MediaMetadataCompat getMetadata() {
957            try {
958                return mBinder.getMetadata();
959            } catch (RemoteException e) {
960                Log.e(TAG, "Dead object in getMetadata. " + e);
961            }
962            return null;
963        }
964
965        @Override
966        public List<MediaSessionCompat.QueueItem> getQueue() {
967            try {
968                return mBinder.getQueue();
969            } catch (RemoteException e) {
970                Log.e(TAG, "Dead object in getQueue. " + e);
971            }
972            return null;
973        }
974
975        @Override
976        public CharSequence getQueueTitle() {
977            try {
978                return mBinder.getQueueTitle();
979            } catch (RemoteException e) {
980                Log.e(TAG, "Dead object in getQueueTitle. " + e);
981            }
982            return null;
983        }
984
985        @Override
986        public Bundle getExtras() {
987            try {
988                return mBinder.getExtras();
989            } catch (RemoteException e) {
990                Log.e(TAG, "Dead object in getExtras. " + e);
991            }
992            return null;
993        }
994
995        @Override
996        public int getRatingType() {
997            try {
998                return mBinder.getRatingType();
999            } catch (RemoteException e) {
1000                Log.e(TAG, "Dead object in getRatingType. " + e);
1001            }
1002            return 0;
1003        }
1004
1005        @Override
1006        public long getFlags() {
1007            try {
1008                return mBinder.getFlags();
1009            } catch (RemoteException e) {
1010                Log.e(TAG, "Dead object in getFlags. " + e);
1011            }
1012            return 0;
1013        }
1014
1015        @Override
1016        public PlaybackInfo getPlaybackInfo() {
1017            try {
1018                ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
1019                PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
1020                        info.controlType, info.maxVolume, info.currentVolume);
1021                return pi;
1022            } catch (RemoteException e) {
1023                Log.e(TAG, "Dead object in getPlaybackInfo. " + e);
1024            }
1025            return null;
1026        }
1027
1028        @Override
1029        public PendingIntent getSessionActivity() {
1030            try {
1031                return mBinder.getLaunchPendingIntent();
1032            } catch (RemoteException e) {
1033                Log.e(TAG, "Dead object in getSessionActivity. " + e);
1034            }
1035            return null;
1036        }
1037
1038        @Override
1039        public void setVolumeTo(int value, int flags) {
1040            try {
1041                mBinder.setVolumeTo(value, flags, null);
1042            } catch (RemoteException e) {
1043                Log.e(TAG, "Dead object in setVolumeTo. " + e);
1044            }
1045        }
1046
1047        @Override
1048        public void adjustVolume(int direction, int flags) {
1049            try {
1050                mBinder.adjustVolume(direction, flags, null);
1051            } catch (RemoteException e) {
1052                Log.e(TAG, "Dead object in adjustVolume. " + e);
1053            }
1054        }
1055
1056        @Override
1057        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1058            try {
1059                mBinder.sendCommand(command, params,
1060                        new MediaSessionCompat.ResultReceiverWrapper(cb));
1061            } catch (RemoteException e) {
1062                Log.e(TAG, "Dead object in sendCommand. " + e);
1063            }
1064        }
1065
1066        @Override
1067        public String getPackageName() {
1068            try {
1069                return mBinder.getPackageName();
1070            } catch (RemoteException e) {
1071                Log.e(TAG, "Dead object in getPackageName. " + e);
1072            }
1073            return null;
1074        }
1075
1076        @Override
1077        public Object getMediaController() {
1078            return null;
1079        }
1080    }
1081
1082    static class TransportControlsBase extends TransportControls {
1083        private IMediaSession mBinder;
1084
1085        public TransportControlsBase(IMediaSession binder) {
1086            mBinder = binder;
1087        }
1088
1089        @Override
1090        public void prepare() {
1091            try {
1092                mBinder.prepare();
1093            } catch (RemoteException e) {
1094                Log.e(TAG, "Dead object in prepare. " + e);
1095            }
1096        }
1097
1098        @Override
1099        public void prepareFromMediaId(String mediaId, Bundle extras) {
1100            try {
1101                mBinder.prepareFromMediaId(mediaId, extras);
1102            } catch (RemoteException e) {
1103                Log.e(TAG, "Dead object in prepareFromMediaId. " + e);
1104            }
1105        }
1106
1107        @Override
1108        public void prepareFromSearch(String query, Bundle extras) {
1109            try {
1110                mBinder.prepareFromSearch(query, extras);
1111            } catch (RemoteException e) {
1112                Log.e(TAG, "Dead object in prepareFromSearch. " + e);
1113            }
1114        }
1115
1116        @Override
1117        public void prepareFromUri(Uri uri, Bundle extras) {
1118            try {
1119                mBinder.prepareFromUri(uri, extras);
1120            } catch (RemoteException e) {
1121                Log.e(TAG, "Dead object in prepareFromUri. " + e);
1122            }
1123        }
1124
1125        @Override
1126        public void play() {
1127            try {
1128                mBinder.play();
1129            } catch (RemoteException e) {
1130                Log.e(TAG, "Dead object in play. " + e);
1131            }
1132        }
1133
1134        @Override
1135        public void playFromMediaId(String mediaId, Bundle extras) {
1136            try {
1137                mBinder.playFromMediaId(mediaId, extras);
1138            } catch (RemoteException e) {
1139                Log.e(TAG, "Dead object in playFromMediaId. " + e);
1140            }
1141        }
1142
1143        @Override
1144        public void playFromSearch(String query, Bundle extras) {
1145            try {
1146                mBinder.playFromSearch(query, extras);
1147            } catch (RemoteException e) {
1148                Log.e(TAG, "Dead object in playFromSearch. " + e);
1149            }
1150        }
1151
1152        @Override
1153        public void playFromUri(Uri uri, Bundle extras) {
1154            try {
1155                mBinder.playFromUri(uri, extras);
1156            } catch (RemoteException e) {
1157                Log.e(TAG, "Dead object in playFromUri. " + e);
1158            }
1159        }
1160
1161        @Override
1162        public void skipToQueueItem(long id) {
1163            try {
1164                mBinder.skipToQueueItem(id);
1165            } catch (RemoteException e) {
1166                Log.e(TAG, "Dead object in skipToQueueItem. " + e);
1167            }
1168        }
1169
1170        @Override
1171        public void pause() {
1172            try {
1173                mBinder.pause();
1174            } catch (RemoteException e) {
1175                Log.e(TAG, "Dead object in pause. " + e);
1176            }
1177        }
1178
1179        @Override
1180        public void stop() {
1181            try {
1182                mBinder.stop();
1183            } catch (RemoteException e) {
1184                Log.e(TAG, "Dead object in stop. " + e);
1185            }
1186        }
1187
1188        @Override
1189        public void seekTo(long pos) {
1190            try {
1191                mBinder.seekTo(pos);
1192            } catch (RemoteException e) {
1193                Log.e(TAG, "Dead object in seekTo. " + e);
1194            }
1195        }
1196
1197        @Override
1198        public void fastForward() {
1199            try {
1200                mBinder.fastForward();
1201            } catch (RemoteException e) {
1202                Log.e(TAG, "Dead object in fastForward. " + e);
1203            }
1204        }
1205
1206        @Override
1207        public void skipToNext() {
1208            try {
1209                mBinder.next();
1210            } catch (RemoteException e) {
1211                Log.e(TAG, "Dead object in skipToNext. " + e);
1212            }
1213        }
1214
1215        @Override
1216        public void rewind() {
1217            try {
1218                mBinder.rewind();
1219            } catch (RemoteException e) {
1220                Log.e(TAG, "Dead object in rewind. " + e);
1221            }
1222        }
1223
1224        @Override
1225        public void skipToPrevious() {
1226            try {
1227                mBinder.previous();
1228            } catch (RemoteException e) {
1229                Log.e(TAG, "Dead object in skipToPrevious. " + e);
1230            }
1231        }
1232
1233        @Override
1234        public void setRating(RatingCompat rating) {
1235            try {
1236                mBinder.rate(rating);
1237            } catch (RemoteException e) {
1238                Log.e(TAG, "Dead object in setRating. " + e);
1239            }
1240        }
1241
1242        @Override
1243        public void sendCustomAction(CustomAction customAction, Bundle args) {
1244            sendCustomAction(customAction.getAction(), args);
1245        }
1246
1247        @Override
1248        public void sendCustomAction(String action, Bundle args) {
1249            try {
1250                mBinder.sendCustomAction(action, args);
1251            } catch (RemoteException e) {
1252                Log.e(TAG, "Dead object in sendCustomAction. " + e);
1253            }
1254        }
1255    }
1256
1257    static class MediaControllerImplApi21 implements MediaControllerImpl {
1258        protected final Object mControllerObj;
1259
1260        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
1261            mControllerObj = MediaControllerCompatApi21.fromToken(context,
1262                    session.getSessionToken().getToken());
1263        }
1264
1265        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
1266                throws RemoteException {
1267            mControllerObj = MediaControllerCompatApi21.fromToken(context,
1268                    sessionToken.getToken());
1269            if (mControllerObj == null) throw new RemoteException();
1270        }
1271
1272        @Override
1273        public void registerCallback(Callback callback, Handler handler) {
1274            MediaControllerCompatApi21.registerCallback(mControllerObj, callback.mCallbackObj, handler);
1275        }
1276
1277        @Override
1278        public void unregisterCallback(Callback callback) {
1279            MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
1280        }
1281
1282        @Override
1283        public boolean dispatchMediaButtonEvent(KeyEvent event) {
1284            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
1285        }
1286
1287        @Override
1288        public TransportControls getTransportControls() {
1289            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1290            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
1291        }
1292
1293        @Override
1294        public PlaybackStateCompat getPlaybackState() {
1295            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
1296            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
1297        }
1298
1299        @Override
1300        public MediaMetadataCompat getMetadata() {
1301            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
1302            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
1303        }
1304
1305        @Override
1306        public List<MediaSessionCompat.QueueItem> getQueue() {
1307            List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
1308            return queueObjs != null ? MediaSessionCompat.QueueItem.fromQueueItemList(queueObjs)
1309                    : null;
1310        }
1311
1312        @Override
1313        public CharSequence getQueueTitle() {
1314            return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
1315        }
1316
1317        @Override
1318        public Bundle getExtras() {
1319            return MediaControllerCompatApi21.getExtras(mControllerObj);
1320        }
1321
1322        @Override
1323        public int getRatingType() {
1324            return MediaControllerCompatApi21.getRatingType(mControllerObj);
1325        }
1326
1327        @Override
1328        public long getFlags() {
1329            return MediaControllerCompatApi21.getFlags(mControllerObj);
1330        }
1331
1332        @Override
1333        public PlaybackInfo getPlaybackInfo() {
1334            Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
1335            return volumeInfoObj != null ? new PlaybackInfo(
1336                    MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
1337                    MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
1338                    MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
1339                    MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
1340                    MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
1341        }
1342
1343        @Override
1344        public PendingIntent getSessionActivity() {
1345            return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
1346        }
1347
1348        @Override
1349        public void setVolumeTo(int value, int flags) {
1350            MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
1351        }
1352
1353        @Override
1354        public void adjustVolume(int direction, int flags) {
1355            MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
1356        }
1357
1358        @Override
1359        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1360            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
1361        }
1362
1363        @Override
1364        public String getPackageName() {
1365            return MediaControllerCompatApi21.getPackageName(mControllerObj);
1366        }
1367
1368        @Override
1369        public Object getMediaController() {
1370            return mControllerObj;
1371        }
1372    }
1373
1374    static class TransportControlsApi21 extends TransportControls {
1375        protected final Object mControlsObj;
1376
1377        public TransportControlsApi21(Object controlsObj) {
1378            mControlsObj = controlsObj;
1379        }
1380
1381        @Override
1382        public void prepare() {
1383            sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
1384        }
1385
1386        @Override
1387        public void prepareFromMediaId(String mediaId, Bundle extras) {
1388            Bundle bundle = new Bundle();
1389            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
1390            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1391            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
1392        }
1393
1394        @Override
1395        public void prepareFromSearch(String query, Bundle extras) {
1396            Bundle bundle = new Bundle();
1397            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
1398            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1399            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
1400        }
1401
1402        @Override
1403        public void prepareFromUri(Uri uri, Bundle extras) {
1404            Bundle bundle = new Bundle();
1405            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1406            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1407            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
1408        }
1409
1410        @Override
1411        public void play() {
1412            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
1413        }
1414
1415        @Override
1416        public void pause() {
1417            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
1418        }
1419
1420        @Override
1421        public void stop() {
1422            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
1423        }
1424
1425        @Override
1426        public void seekTo(long pos) {
1427            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
1428        }
1429
1430        @Override
1431        public void fastForward() {
1432            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
1433        }
1434
1435        @Override
1436        public void rewind() {
1437            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
1438        }
1439
1440        @Override
1441        public void skipToNext() {
1442            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
1443        }
1444
1445        @Override
1446        public void skipToPrevious() {
1447            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
1448        }
1449
1450        @Override
1451        public void setRating(RatingCompat rating) {
1452            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
1453                    rating != null ? rating.getRating() : null);
1454        }
1455
1456        @Override
1457        public void playFromMediaId(String mediaId, Bundle extras) {
1458            MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
1459                    extras);
1460        }
1461
1462        @Override
1463        public void playFromSearch(String query, Bundle extras) {
1464            MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
1465                    extras);
1466        }
1467
1468        @Override
1469        public void playFromUri(Uri uri, Bundle extras) {
1470            if (uri == null || Uri.EMPTY.equals(uri)) {
1471                throw new IllegalArgumentException(
1472                        "You must specify a non-empty Uri for playFromUri.");
1473            }
1474            Bundle bundle = new Bundle();
1475            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1476            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1477            sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
1478        }
1479
1480        @Override
1481        public void skipToQueueItem(long id) {
1482            MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
1483        }
1484
1485        @Override
1486        public void sendCustomAction(CustomAction customAction, Bundle args) {
1487            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
1488                    customAction.getAction(), args);
1489        }
1490
1491        @Override
1492        public void sendCustomAction(String action, Bundle args) {
1493            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
1494                    args);
1495        }
1496    }
1497
1498    static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
1499
1500        public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
1501            super(context, session);
1502        }
1503
1504        public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
1505                throws RemoteException {
1506            super(context, sessionToken);
1507        }
1508
1509        @Override
1510        public TransportControls getTransportControls() {
1511            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1512            return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
1513        }
1514    }
1515
1516    static class TransportControlsApi23 extends TransportControlsApi21 {
1517
1518        public TransportControlsApi23(Object controlsObj) {
1519            super(controlsObj);
1520        }
1521
1522        @Override
1523        public void playFromUri(Uri uri, Bundle extras) {
1524            MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
1525                    extras);
1526        }
1527    }
1528
1529    static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
1530
1531        public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
1532            super(context, session);
1533        }
1534
1535        public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
1536                throws RemoteException {
1537            super(context, sessionToken);
1538        }
1539
1540        @Override
1541        public TransportControls getTransportControls() {
1542            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1543            return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
1544        }
1545    }
1546
1547    static class TransportControlsApi24 extends TransportControlsApi23 {
1548
1549        public TransportControlsApi24(Object controlsObj) {
1550            super(controlsObj);
1551        }
1552
1553        @Override
1554        public void prepare() {
1555            MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
1556        }
1557
1558        @Override
1559        public void prepareFromMediaId(String mediaId, Bundle extras) {
1560            MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
1561                    mControlsObj, mediaId, extras);
1562        }
1563
1564        @Override
1565        public void prepareFromSearch(String query, Bundle extras) {
1566            MediaControllerCompatApi24.TransportControls.prepareFromSearch(
1567                    mControlsObj, query, extras);
1568        }
1569
1570        @Override
1571        public void prepareFromUri(Uri uri, Bundle extras) {
1572            MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
1573        }
1574    }
1575
1576}
1577