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