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