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