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