1
2/*
3 * Copyright (C) 2014 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package android.support.v4.media.session;
19
20import android.app.Activity;
21import android.app.PendingIntent;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.media.AudioManager;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Looper;
30import android.os.Message;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.os.RemoteCallbackList;
34import android.os.RemoteException;
35import android.os.ResultReceiver;
36import android.os.SystemClock;
37import android.support.v4.media.MediaDescriptionCompat;
38import android.support.v4.media.MediaMetadataCompat;
39import android.support.v4.media.RatingCompat;
40import android.support.v4.media.VolumeProviderCompat;
41import android.text.TextUtils;
42import android.util.Log;
43import android.view.KeyEvent;
44
45import java.lang.ref.WeakReference;
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * Allows interaction with media controllers, volume keys, media buttons, and
51 * transport controls.
52 * <p>
53 * A MediaSession should be created when an app wants to publish media playback
54 * information or handle media keys. In general an app only needs one session
55 * for all playback, though multiple sessions can be created to provide finer
56 * grain controls of media.
57 * <p>
58 * Once a session is created the owner of the session may pass its
59 * {@link #getSessionToken() session token} to other processes to allow them to
60 * create a {@link MediaControllerCompat} to interact with the session.
61 * <p>
62 * To receive commands, media keys, and other events a {@link Callback} must be
63 * set with {@link #setCallback(Callback)}.
64 * <p>
65 * When an app is finished performing playback it must call {@link #release()}
66 * to clean up the session and notify any controllers.
67 * <p>
68 * MediaSessionCompat objects are not thread safe and all calls should be made
69 * from the same thread.
70 * <p>
71 * This is a helper for accessing features in
72 * {@link android.media.session.MediaSession} introduced after API level 4 in a
73 * backwards compatible fashion.
74 */
75public class MediaSessionCompat {
76    private final MediaSessionImpl mImpl;
77    private final MediaControllerCompat mController;
78    private final ArrayList<OnActiveChangeListener>
79            mActiveListeners = new ArrayList<OnActiveChangeListener>();
80
81    /**
82     * Set this flag on the session to indicate that it can handle media button
83     * events.
84     */
85    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
86
87    /**
88     * Set this flag on the session to indicate that it handles transport
89     * control commands through its {@link Callback}.
90     */
91    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
92
93    /**
94     * Creates a new session.
95     *
96     * @param context The context.
97     * @param tag A short name for debugging purposes.
98     * @param mediaButtonEventReceiver The component name for your receiver.
99     *            This must be non-null to support platform versions earlier
100     *            than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
101     * @param mbrIntent The PendingIntent for your receiver component that
102     *            handles media button events. This is optional and will be used
103     *            on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
104     *            later instead of the component name.
105     */
106    public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver,
107            PendingIntent mbrIntent) {
108        if (context == null) {
109            throw new IllegalArgumentException("context must not be null");
110        }
111        if (TextUtils.isEmpty(tag)) {
112            throw new IllegalArgumentException("tag must not be null or empty");
113        }
114
115        if (android.os.Build.VERSION.SDK_INT >= 21) {
116            mImpl = new MediaSessionImplApi21(context, tag);
117            mImpl.setMediaButtonReceiver(mbrIntent);
118        } else {
119            mImpl = new MediaSessionImplBase(context, tag, mediaButtonEventReceiver, mbrIntent);
120        }
121        mController = new MediaControllerCompat(context, this);
122    }
123
124    private MediaSessionCompat(Context context, MediaSessionImpl impl) {
125        mImpl = impl;
126        mController = new MediaControllerCompat(context, this);
127    }
128
129    /**
130     * Add a callback to receive updates on for the MediaSession. This includes
131     * media button and volume events. The caller's thread will be used to post
132     * events.
133     *
134     * @param callback The callback object
135     */
136    public void setCallback(Callback callback) {
137        setCallback(callback, null);
138    }
139
140    /**
141     * Set the callback to receive updates for the MediaSession. This includes
142     * media button and volume events. Set the callback to null to stop
143     * receiving events.
144     *
145     * @param callback The callback to receive updates on.
146     * @param handler The handler that events should be posted on.
147     */
148    public void setCallback(Callback callback, Handler handler) {
149        mImpl.setCallback(callback, handler != null ? handler : new Handler());
150    }
151
152    /**
153     * Set an intent for launching UI for this Session. This can be used as a
154     * quick link to an ongoing media screen. The intent should be for an
155     * activity that may be started using
156     * {@link Activity#startActivity(Intent)}.
157     *
158     * @param pi The intent to launch to show UI for this Session.
159     */
160    public void setSessionActivity(PendingIntent pi) {
161        mImpl.setSessionActivity(pi);
162    }
163
164    /**
165     * Set a pending intent for your media button receiver to allow restarting
166     * playback after the session has been stopped. If your app is started in
167     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
168     * the pending intent.
169     * <p>
170     * This method will only work on
171     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
172     * platform versions must include the media button receiver in the
173     * constructor.
174     *
175     * @param mbr The {@link PendingIntent} to send the media button event to.
176     */
177    public void setMediaButtonReceiver(PendingIntent mbr) {
178        mImpl.setMediaButtonReceiver(mbr);
179    }
180
181    /**
182     * Set any flags for the session.
183     *
184     * @param flags The flags to set for this session.
185     */
186    public void setFlags(int flags) {
187        mImpl.setFlags(flags);
188    }
189
190    /**
191     * Set the stream this session is playing on. This will affect the system's
192     * volume handling for this session. If {@link #setPlaybackToRemote} was
193     * previously called it will stop receiving volume commands and the system
194     * will begin sending volume changes to the appropriate stream.
195     * <p>
196     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
197     *
198     * @param stream The {@link AudioManager} stream this session is playing on.
199     */
200    public void setPlaybackToLocal(int stream) {
201        mImpl.setPlaybackToLocal(stream);
202    }
203
204    /**
205     * Configure this session to use remote volume handling. This must be called
206     * to receive volume button events, otherwise the system will adjust the
207     * current stream volume for this session. If {@link #setPlaybackToLocal}
208     * was previously called that stream will stop receiving volume changes for
209     * this session.
210     * <p>
211     * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
212     * this will only allow an app to handle volume commands sent directly to
213     * the session by a {@link MediaControllerCompat}. System routing of volume
214     * keys will not use the volume provider.
215     *
216     * @param volumeProvider The provider that will handle volume changes. May
217     *            not be null.
218     */
219    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
220        if (volumeProvider == null) {
221            throw new IllegalArgumentException("volumeProvider may not be null!");
222        }
223        mImpl.setPlaybackToRemote(volumeProvider);
224    }
225
226    /**
227     * Set if this session is currently active and ready to receive commands. If
228     * set to false your session's controller may not be discoverable. You must
229     * set the session to active before it can start receiving media button
230     * events or transport commands.
231     * <p>
232     * On platforms earlier than
233     * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
234     * {@link #setMediaButtonReceiver(PendingIntent)} must be called before
235     * setting this to true.
236     *
237     * @param active Whether this session is active or not.
238     */
239    public void setActive(boolean active) {
240        mImpl.setActive(active);
241        for (OnActiveChangeListener listener : mActiveListeners) {
242            listener.onActiveChanged();
243        }
244    }
245
246    /**
247     * Get the current active state of this session.
248     *
249     * @return True if the session is active, false otherwise.
250     */
251    public boolean isActive() {
252        return mImpl.isActive();
253    }
254
255    /**
256     * Send a proprietary event to all MediaControllers listening to this
257     * Session. It's up to the Controller/Session owner to determine the meaning
258     * of any events.
259     *
260     * @param event The name of the event to send
261     * @param extras Any extras included with the event
262     */
263    public void sendSessionEvent(String event, Bundle extras) {
264        if (TextUtils.isEmpty(event)) {
265            throw new IllegalArgumentException("event cannot be null or empty");
266        }
267        mImpl.sendSessionEvent(event, extras);
268    }
269
270    /**
271     * This must be called when an app has finished performing playback. If
272     * playback is expected to start again shortly the session can be left open,
273     * but it must be released if your activity or service is being destroyed.
274     */
275    public void release() {
276        mImpl.release();
277    }
278
279    /**
280     * Retrieve a token object that can be used by apps to create a
281     * {@link MediaControllerCompat} for interacting with this session. The
282     * owner of the session is responsible for deciding how to distribute these
283     * tokens.
284     * <p>
285     * On platform versions before
286     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
287     * used within your app as there is no way to guarantee other apps are using
288     * the same version of the support library.
289     *
290     * @return A token that can be used to create a media controller for this
291     *         session.
292     */
293    public Token getSessionToken() {
294        return mImpl.getSessionToken();
295    }
296
297    /**
298     * Get a controller for this session. This is a convenience method to avoid
299     * having to cache your own controller in process.
300     *
301     * @return A controller for this session.
302     */
303    public MediaControllerCompat getController() {
304        return mController;
305    }
306
307    /**
308     * Update the current playback state.
309     *
310     * @param state The current state of playback
311     */
312    public void setPlaybackState(PlaybackStateCompat state) {
313        mImpl.setPlaybackState(state);
314    }
315
316    /**
317     * Update the current metadata. New metadata can be created using
318     * {@link android.media.MediaMetadata.Builder}.
319     *
320     * @param metadata The new metadata
321     */
322    public void setMetadata(MediaMetadataCompat metadata) {
323        mImpl.setMetadata(metadata);
324    }
325
326    /**
327     * Update the list of items in the play queue. It is an ordered list and
328     * should contain the current item, and previous or upcoming items if they
329     * exist. Specify null if there is no current play queue.
330     * <p>
331     * The queue should be of reasonable size. If the play queue is unbounded
332     * within your app, it is better to send a reasonable amount in a sliding
333     * window instead.
334     *
335     * @param queue A list of items in the play queue.
336     */
337    public void setQueue(List<QueueItem> queue) {
338        mImpl.setQueue(queue);
339    }
340
341    /**
342     * Set the title of the play queue. The UI should display this title along
343     * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
344     * name.
345     *
346     * @param title The title of the play queue.
347     */
348    public void setQueueTitle(CharSequence title) {
349        mImpl.setQueueTitle(title);
350    }
351
352    /**
353     * Set the style of rating used by this session. Apps trying to set the
354     * rating should use this style. Must be one of the following:
355     * <ul>
356     * <li>{@link RatingCompat#RATING_NONE}</li>
357     * <li>{@link RatingCompat#RATING_3_STARS}</li>
358     * <li>{@link RatingCompat#RATING_4_STARS}</li>
359     * <li>{@link RatingCompat#RATING_5_STARS}</li>
360     * <li>{@link RatingCompat#RATING_HEART}</li>
361     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
362     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
363     * </ul>
364     */
365    public void setRatingType(int type) {
366        mImpl.setRatingType(type);
367    }
368
369    /**
370     * Set some extras that can be associated with the
371     * {@link MediaSessionCompat}. No assumptions should be made as to how a
372     * {@link MediaControllerCompat} will handle these extras. Keys should be
373     * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
374     *
375     * @param extras The extras associated with the session.
376     */
377    public void setExtras(Bundle extras) {
378        mImpl.setExtras(extras);
379    }
380
381    /**
382     * Gets the underlying framework {@link android.media.session.MediaSession}
383     * object.
384     * <p>
385     * This method is only supported on API 21+.
386     * </p>
387     *
388     * @return The underlying {@link android.media.session.MediaSession} object,
389     *         or null if none.
390     */
391    public Object getMediaSession() {
392        return mImpl.getMediaSession();
393    }
394
395    /**
396     * Gets the underlying framework {@link android.media.RemoteControlClient}
397     * object.
398     * <p>
399     * This method is only supported on APIs 14-20. On API 21+
400     * {@link #getMediaSession()} should be used instead.
401     *
402     * @return The underlying {@link android.media.RemoteControlClient} object,
403     *         or null if none.
404     */
405    public Object getRemoteControlClient() {
406        return mImpl.getRemoteControlClient();
407    }
408
409    /**
410     * Adds a listener to be notified when the active status of this session
411     * changes. This is primarily used by the support library and should not be
412     * needed by apps.
413     *
414     * @param listener The listener to add.
415     */
416    public void addOnActiveChangeListener(OnActiveChangeListener listener) {
417        if (listener == null) {
418            throw new IllegalArgumentException("Listener may not be null");
419        }
420        mActiveListeners.add(listener);
421    }
422
423    /**
424     * Stops the listener from being notified when the active status of this
425     * session changes.
426     *
427     * @param listener The listener to remove.
428     */
429    public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
430        if (listener == null) {
431            throw new IllegalArgumentException("Listener may not be null");
432        }
433        mActiveListeners.remove(listener);
434    }
435
436    /**
437     * Obtain a compat wrapper for an existing MediaSession.
438     *
439     * @param mediaSession The {@link android.media.session.MediaSession} to
440     *            wrap.
441     * @return A compat wrapper for the provided session.
442     */
443    public static MediaSessionCompat obtain(Context context, Object mediaSession) {
444        return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
445    }
446
447    /**
448     * Receives transport controls, media buttons, and commands from controllers
449     * and the system. The callback may be set using {@link #setCallback}.
450     */
451    public abstract static class Callback {
452        final Object mCallbackObj;
453
454        public Callback() {
455            if (android.os.Build.VERSION.SDK_INT >= 21) {
456                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
457            } else {
458                mCallbackObj = null;
459            }
460        }
461
462        /**
463         * Called when a controller has sent a custom command to this session.
464         * The owner of the session may handle custom commands but is not
465         * required to.
466         *
467         * @param command The command name.
468         * @param extras Optional parameters for the command, may be null.
469         * @param cb A result receiver to which a result may be sent by the command, may be null.
470         */
471        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
472        }
473
474        /**
475         * Override to handle media button events.
476         *
477         * @param mediaButtonEvent The media button event intent.
478         * @return True if the event was handled, false otherwise.
479         */
480        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
481            return false;
482        }
483
484        /**
485         * Override to handle requests to begin playback.
486         */
487        public void onPlay() {
488        }
489
490        /**
491         * Override to handle requests to play a specific mediaId that was
492         * provided by your app.
493         */
494        public void onPlayFromMediaId(String mediaId, Bundle extras) {
495        }
496
497        /**
498         * Override to handle requests to begin playback from a search query. An
499         * empty query indicates that the app may play any music. The
500         * implementation should attempt to make a smart choice about what to
501         * play.
502         */
503        public void onPlayFromSearch(String query, Bundle extras) {
504        }
505
506        /**
507         * Override to handle requests to play an item with a given id from the
508         * play queue.
509         */
510        public void onSkipToQueueItem(long id) {
511        }
512
513        /**
514         * Override to handle requests to pause playback.
515         */
516        public void onPause() {
517        }
518
519        /**
520         * Override to handle requests to skip to the next media item.
521         */
522        public void onSkipToNext() {
523        }
524
525        /**
526         * Override to handle requests to skip to the previous media item.
527         */
528        public void onSkipToPrevious() {
529        }
530
531        /**
532         * Override to handle requests to fast forward.
533         */
534        public void onFastForward() {
535        }
536
537        /**
538         * Override to handle requests to rewind.
539         */
540        public void onRewind() {
541        }
542
543        /**
544         * Override to handle requests to stop playback.
545         */
546        public void onStop() {
547        }
548
549        /**
550         * Override to handle requests to seek to a specific position in ms.
551         *
552         * @param pos New position to move to, in milliseconds.
553         */
554        public void onSeekTo(long pos) {
555        }
556
557        /**
558         * Override to handle the item being rated.
559         *
560         * @param rating
561         */
562        public void onSetRating(RatingCompat rating) {
563        }
564
565        /**
566         * Called when a {@link MediaControllerCompat} wants a
567         * {@link PlaybackStateCompat.CustomAction} to be performed.
568         *
569         * @param action The action that was originally sent in the
570         *            {@link PlaybackStateCompat.CustomAction}.
571         * @param extras Optional extras specified by the
572         *            {@link MediaControllerCompat}.
573         */
574        public void onCustomAction(String action, Bundle extras) {
575        }
576
577        private class StubApi21 implements MediaSessionCompatApi21.Callback {
578
579            @Override
580            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
581                Callback.this.onCommand(command, extras, cb);
582            }
583
584            @Override
585            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
586                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
587            }
588
589            @Override
590            public void onPlay() {
591                Callback.this.onPlay();
592            }
593
594            @Override
595            public void onPlayFromMediaId(String mediaId, Bundle extras) {
596                Callback.this.onPlayFromMediaId(mediaId, extras);
597            }
598
599            @Override
600            public void onPlayFromSearch(String search, Bundle extras) {
601                Callback.this.onPlayFromSearch(search, extras);
602            }
603
604            @Override
605            public void onSkipToQueueItem(long id) {
606                Callback.this.onSkipToQueueItem(id);
607            }
608
609            @Override
610            public void onPause() {
611                Callback.this.onPause();
612            }
613
614            @Override
615            public void onSkipToNext() {
616                Callback.this.onSkipToNext();
617            }
618
619            @Override
620            public void onSkipToPrevious() {
621                Callback.this.onSkipToPrevious();
622            }
623
624            @Override
625            public void onFastForward() {
626                Callback.this.onFastForward();
627            }
628
629            @Override
630            public void onRewind() {
631                Callback.this.onRewind();
632            }
633
634            @Override
635            public void onStop() {
636                Callback.this.onStop();
637            }
638
639            @Override
640            public void onSeekTo(long pos) {
641                Callback.this.onSeekTo(pos);
642            }
643
644            @Override
645            public void onSetRating(Object ratingObj) {
646                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
647            }
648
649            @Override
650            public void onCustomAction(String action, Bundle extras) {
651                Callback.this.onCustomAction(action, extras);
652            }
653        }
654    }
655
656    /**
657     * Represents an ongoing session. This may be passed to apps by the session
658     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
659     * the session.
660     */
661    public static final class Token implements Parcelable {
662        private final Object mInner;
663
664        Token(Object inner) {
665            mInner = inner;
666        }
667
668        /**
669         * Creates a compat Token from a framework
670         * {@link android.media.session.MediaSession.Token} object.
671         * <p>
672         * This method is only supported on
673         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
674         * </p>
675         *
676         * @param token The framework token object.
677         * @return A compat Token for use with {@link MediaControllerCompat}.
678         */
679        public static Token fromToken(Object token) {
680            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
681                return null;
682            }
683            return new Token(MediaSessionCompatApi21.verifyToken(token));
684        }
685
686        @Override
687        public int describeContents() {
688            return 0;
689        }
690
691        @Override
692        public void writeToParcel(Parcel dest, int flags) {
693            if (android.os.Build.VERSION.SDK_INT >= 21) {
694                dest.writeParcelable((Parcelable) mInner, flags);
695            } else {
696                dest.writeStrongBinder((IBinder) mInner);
697            }
698        }
699
700        /**
701         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
702         * <p>
703         * This method is only supported on API 21+.
704         * </p>
705         *
706         * @return The underlying {@link android.media.session.MediaSession.Token} object,
707         * or null if none.
708         */
709        public Object getToken() {
710            return mInner;
711        }
712
713        public static final Parcelable.Creator<Token> CREATOR
714                = new Parcelable.Creator<Token>() {
715            @Override
716            public Token createFromParcel(Parcel in) {
717                Object inner;
718                if (android.os.Build.VERSION.SDK_INT >= 21) {
719                    inner = in.readParcelable(null);
720                } else {
721                    inner = in.readStrongBinder();
722                }
723                return new Token(inner);
724            }
725
726            @Override
727            public Token[] newArray(int size) {
728                return new Token[size];
729            }
730        };
731    }
732
733    /**
734     * A single item that is part of the play queue. It contains a description
735     * of the item and its id in the queue.
736     */
737    public static final class QueueItem implements Parcelable {
738        /**
739         * This id is reserved. No items can be explicitly asigned this id.
740         */
741        public static final int UNKNOWN_ID = -1;
742
743        private final MediaDescriptionCompat mDescription;
744        private final long mId;
745
746        private Object mItem;
747
748        /**
749         * Create a new {@link MediaSessionCompat.QueueItem}.
750         *
751         * @param description The {@link MediaDescriptionCompat} for this item.
752         * @param id An identifier for this item. It must be unique within the
753         *            play queue and cannot be {@link #UNKNOWN_ID}.
754         */
755        public QueueItem(MediaDescriptionCompat description, long id) {
756            this(null, description, id);
757        }
758
759        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
760            if (description == null) {
761                throw new IllegalArgumentException("Description cannot be null.");
762            }
763            if (id == UNKNOWN_ID) {
764                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
765            }
766            mDescription = description;
767            mId = id;
768            mItem = queueItem;
769        }
770
771        private QueueItem(Parcel in) {
772            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
773            mId = in.readLong();
774        }
775
776        /**
777         * Get the description for this item.
778         */
779        public MediaDescriptionCompat getDescription() {
780            return mDescription;
781        }
782
783        /**
784         * Get the queue id for this item.
785         */
786        public long getQueueId() {
787            return mId;
788        }
789
790        @Override
791        public void writeToParcel(Parcel dest, int flags) {
792            mDescription.writeToParcel(dest, flags);
793            dest.writeLong(mId);
794        }
795
796        @Override
797        public int describeContents() {
798            return 0;
799        }
800
801        /**
802         * Get the underlying
803         * {@link android.media.session.MediaSession.QueueItem}.
804         * <p>
805         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
806         * is returned.
807         *
808         * @return The underlying
809         *         {@link android.media.session.MediaSession.QueueItem} or null.
810         */
811        public Object getQueueItem() {
812            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
813                return mItem;
814            }
815            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
816                    mId);
817            return mItem;
818        }
819
820        /**
821         * Obtain a compat wrapper for an existing QueueItem.
822         *
823         * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to
824         *            wrap.
825         * @return A compat wrapper for the provided item.
826         */
827        public static QueueItem obtain(Object queueItem) {
828            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
829            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
830                    descriptionObj);
831            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
832            return new QueueItem(queueItem, description, id);
833        }
834
835        public static final Creator<MediaSessionCompat.QueueItem>
836                CREATOR = new Creator<MediaSessionCompat.QueueItem>() {
837
838                        @Override
839                    public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
840                        return new MediaSessionCompat.QueueItem(p);
841                    }
842
843                        @Override
844                    public MediaSessionCompat.QueueItem[] newArray(int size) {
845                        return new MediaSessionCompat.QueueItem[size];
846                    }
847                };
848
849        @Override
850        public String toString() {
851            return "MediaSession.QueueItem {" +
852                    "Description=" + mDescription +
853                    ", Id=" + mId + " }";
854        }
855    }
856
857    /**
858     * This is a wrapper for {@link ResultReceiver} for sending over aidl
859     * interfaces. The framework version was not exposed to aidls until
860     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
861     */
862    static final class ResultReceiverWrapper implements Parcelable {
863        private ResultReceiver mResultReceiver;
864
865        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
866            mResultReceiver = resultReceiver;
867        }
868
869        ResultReceiverWrapper(Parcel in) {
870            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
871        }
872
873        public static final Creator<ResultReceiverWrapper>
874                CREATOR = new Creator<ResultReceiverWrapper>() {
875            @Override
876            public ResultReceiverWrapper createFromParcel(Parcel p) {
877                return new ResultReceiverWrapper(p);
878            }
879
880            @Override
881            public ResultReceiverWrapper[] newArray(int size) {
882                return new ResultReceiverWrapper[size];
883            }
884        };
885
886        @Override
887        public int describeContents() {
888            return 0;
889        }
890
891        @Override
892        public void writeToParcel(Parcel dest, int flags) {
893            mResultReceiver.writeToParcel(dest, flags);
894        }
895    }
896
897    public interface OnActiveChangeListener {
898        void onActiveChanged();
899    }
900
901    interface MediaSessionImpl {
902        void setCallback(Callback callback, Handler handler);
903        void setFlags(int flags);
904        void setPlaybackToLocal(int stream);
905        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
906        void setActive(boolean active);
907        boolean isActive();
908        void sendSessionEvent(String event, Bundle extras);
909        void release();
910        Token getSessionToken();
911        void setPlaybackState(PlaybackStateCompat state);
912        void setMetadata(MediaMetadataCompat metadata);
913
914        void setSessionActivity(PendingIntent pi);
915
916        void setMediaButtonReceiver(PendingIntent mbr);
917        void setQueue(List<QueueItem> queue);
918        void setQueueTitle(CharSequence title);
919
920        void setRatingType(int type);
921        void setExtras(Bundle extras);
922
923        Object getMediaSession();
924
925        Object getRemoteControlClient();
926    }
927
928    // TODO: compatibility implementation
929    static class MediaSessionImplBase implements MediaSessionImpl {
930        private final Context mContext;
931        private final ComponentName mComponentName;
932        private final PendingIntent mMediaButtonEventReceiver;
933        private final Object mRccObj;
934        private final MediaSessionStub mStub;
935        private final Token mToken;
936        private final MessageHandler mHandler;
937        private final String mPackageName;
938        private final String mTag;
939        private final AudioManager mAudioManager;
940
941        private final Object mLock = new Object();
942        private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
943                = new RemoteCallbackList<IMediaControllerCallback>();
944
945        private boolean mDestroyed = false;
946        private boolean mIsActive = false;
947        private boolean mIsRccRegistered = false;
948        private boolean mIsMbrRegistered = false;
949        private Callback mCallback;
950
951        private int mFlags;
952
953        private MediaMetadataCompat mMetadata;
954        private PlaybackStateCompat mState;
955        private PendingIntent mSessionActivity;
956        private List<QueueItem> mQueue;
957        private CharSequence mQueueTitle;
958        private int mRatingType;
959        private Bundle mExtras;
960
961        private int mVolumeType;
962        private int mLocalStream;
963        private VolumeProviderCompat mVolumeProvider;
964
965        private VolumeProviderCompat.Callback mVolumeCallback
966                = new VolumeProviderCompat.Callback() {
967            @Override
968            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
969                if (mVolumeProvider != volumeProvider) {
970                    return;
971                }
972                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
973                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
974                        volumeProvider.getCurrentVolume());
975                sendVolumeInfoChanged(info);
976            }
977        };
978
979        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
980                PendingIntent mbr) {
981            if (mbrComponent == null) {
982                throw new IllegalArgumentException(
983                        "MediaButtonReceiver component may not be null.");
984            }
985            if (mbr == null) {
986                // construct a PendingIntent for the media button
987                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
988                // the associated intent will be handled by the component being
989                // registered
990                mediaButtonIntent.setComponent(mbrComponent);
991                mbr = PendingIntent.getBroadcast(context,
992                        0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
993            }
994            mContext = context;
995            mPackageName = context.getPackageName();
996            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
997            mTag = tag;
998            mComponentName = mbrComponent;
999            mMediaButtonEventReceiver = mbr;
1000            mStub = new MediaSessionStub();
1001            mToken = new Token(mStub);
1002            mHandler = new MessageHandler(Looper.myLooper());
1003
1004            mRatingType = RatingCompat.RATING_NONE;
1005            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1006            mLocalStream = AudioManager.STREAM_MUSIC;
1007            if (android.os.Build.VERSION.SDK_INT >= 14) {
1008                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr);
1009            } else {
1010                mRccObj = null;
1011            }
1012        }
1013
1014        @Override
1015        public void setCallback(final Callback callback, Handler handler) {
1016            if (callback == mCallback) {
1017                return;
1018            }
1019            if (callback == null || android.os.Build.VERSION.SDK_INT < 18) {
1020                // There's nothing to register on API < 18 since media buttons
1021                // all go through the media button receiver
1022                if (android.os.Build.VERSION.SDK_INT >= 18) {
1023                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1024                }
1025                if (android.os.Build.VERSION.SDK_INT >= 19) {
1026                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1027                }
1028            } else {
1029                if (handler == null) {
1030                    handler = new Handler();
1031                }
1032                MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() {
1033                    @Override
1034                    public void onStop() {
1035                        callback.onStop();
1036                    }
1037
1038                    @Override
1039                    public void onSkipToPrevious() {
1040                        callback.onSkipToPrevious();
1041                    }
1042
1043                    @Override
1044                    public void onSkipToNext() {
1045                        callback.onSkipToNext();
1046                    }
1047
1048                    @Override
1049                    public void onSetRating(Object ratingObj) {
1050                        callback.onSetRating(RatingCompat.fromRating(ratingObj));
1051                    }
1052
1053                    @Override
1054                    public void onSeekTo(long pos) {
1055                        callback.onSeekTo(pos);
1056                    }
1057
1058                    @Override
1059                    public void onRewind() {
1060                        callback.onRewind();
1061                    }
1062
1063                    @Override
1064                    public void onPlay() {
1065                        callback.onPlay();
1066                    }
1067
1068                    @Override
1069                    public void onPause() {
1070                        callback.onPause();
1071                    }
1072
1073                    @Override
1074                    public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
1075                        return callback.onMediaButtonEvent(mediaButtonIntent);
1076                    }
1077
1078                    @Override
1079                    public void onFastForward() {
1080                        callback.onFastForward();
1081                    }
1082
1083                    @Override
1084                    public void onCommand(String command, Bundle extras, ResultReceiver cb) {
1085                        callback.onCommand(command, extras, cb);
1086                    }
1087                };
1088                if (android.os.Build.VERSION.SDK_INT >= 18) {
1089                    Object onPositionUpdateObj = MediaSessionCompatApi18
1090                            .createPlaybackPositionUpdateListener(cb14);
1091                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1092                            onPositionUpdateObj);
1093                }
1094                if (android.os.Build.VERSION.SDK_INT >= 19) {
1095                    Object onMetadataUpdateObj = MediaSessionCompatApi19
1096                            .createMetadataUpdateListener(cb14);
1097                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1098                            onMetadataUpdateObj);
1099                }
1100            }
1101            mCallback = callback;
1102        }
1103
1104        @Override
1105        public void setFlags(int flags) {
1106            synchronized (mLock) {
1107                mFlags = flags;
1108            }
1109            update();
1110        }
1111
1112        @Override
1113        public void setPlaybackToLocal(int stream) {
1114            if (mVolumeProvider != null) {
1115                mVolumeProvider.setCallback(null);
1116            }
1117            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1118            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1119                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1120                    mAudioManager.getStreamMaxVolume(mLocalStream),
1121                    mAudioManager.getStreamVolume(mLocalStream));
1122            sendVolumeInfoChanged(info);
1123        }
1124
1125        @Override
1126        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1127            if (volumeProvider == null) {
1128                throw new IllegalArgumentException("volumeProvider may not be null");
1129            }
1130            if (mVolumeProvider != null) {
1131                mVolumeProvider.setCallback(null);
1132            }
1133            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1134            mVolumeProvider = volumeProvider;
1135            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1136                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1137                    mVolumeProvider.getCurrentVolume());
1138            sendVolumeInfoChanged(info);
1139
1140            volumeProvider.setCallback(mVolumeCallback);
1141        }
1142
1143        @Override
1144        public void setActive(boolean active) {
1145            if (active == mIsActive) {
1146                return;
1147            }
1148            mIsActive = active;
1149            if (update()) {
1150                setMetadata(mMetadata);
1151                setPlaybackState(mState);
1152            }
1153        }
1154
1155        @Override
1156        public boolean isActive() {
1157            return mIsActive;
1158        }
1159
1160        @Override
1161        public void sendSessionEvent(String event, Bundle extras) {
1162            sendEvent(event, extras);
1163        }
1164
1165        @Override
1166        public void release() {
1167            mIsActive = false;
1168            mDestroyed = true;
1169            update();
1170            sendSessionDestroyed();
1171        }
1172
1173        @Override
1174        public Token getSessionToken() {
1175            return mToken;
1176        }
1177
1178        @Override
1179        public void setPlaybackState(PlaybackStateCompat state) {
1180            synchronized (mLock) {
1181                mState = state;
1182            }
1183            sendState(state);
1184            if (!mIsActive) {
1185                // Don't set the state until after the RCC is registered
1186                return;
1187            }
1188            if (state == null) {
1189                if (android.os.Build.VERSION.SDK_INT >= 14) {
1190                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1191                }
1192            } else {
1193                if (android.os.Build.VERSION.SDK_INT >= 18) {
1194                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1195                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1196                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1197                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
1198                }
1199            }
1200        }
1201
1202        @Override
1203        public void setMetadata(MediaMetadataCompat metadata) {
1204            synchronized (mLock) {
1205                mMetadata = metadata;
1206            }
1207            sendMetadata(metadata);
1208            if (!mIsActive) {
1209                // Don't set metadata until after the rcc has been registered
1210                return;
1211            }
1212            if (android.os.Build.VERSION.SDK_INT >= 19) {
1213                boolean canRate = mState != null
1214                        && (mState.getActions() & PlaybackStateCompat.ACTION_SET_RATING) != 0;
1215                MediaSessionCompatApi19.setMetadata(mRccObj,
1216                        metadata == null ? null : metadata.getBundle(), canRate);
1217            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1218                MediaSessionCompatApi14.setMetadata(mRccObj,
1219                        metadata == null ? null : metadata.getBundle());
1220            }
1221        }
1222
1223        @Override
1224        public void setSessionActivity(PendingIntent pi) {
1225            synchronized (mLock) {
1226                mSessionActivity = pi;
1227            }
1228        }
1229
1230        @Override
1231        public void setMediaButtonReceiver(PendingIntent mbr) {
1232            // Do nothing, changing this is not supported before API 21.
1233        }
1234
1235        @Override
1236        public void setQueue(List<QueueItem> queue) {
1237            mQueue = queue;
1238            sendQueue(queue);
1239        }
1240
1241        @Override
1242        public void setQueueTitle(CharSequence title) {
1243            mQueueTitle = title;
1244            sendQueueTitle(title);
1245        }
1246
1247        @Override
1248        public Object getMediaSession() {
1249            return null;
1250        }
1251
1252        @Override
1253        public Object getRemoteControlClient() {
1254            return mRccObj;
1255        }
1256
1257        @Override
1258        public void setRatingType(int type) {
1259            mRatingType = type;
1260        }
1261
1262        @Override
1263        public void setExtras(Bundle extras) {
1264            mExtras = extras;
1265        }
1266
1267        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
1268        private boolean update() {
1269            boolean registeredRcc = false;
1270            if (mIsActive) {
1271                // On API 8+ register a MBR if it's supported, unregister it
1272                // if support was removed.
1273                if (android.os.Build.VERSION.SDK_INT >= 8) {
1274                    if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1275                        if (android.os.Build.VERSION.SDK_INT >= 18) {
1276                            MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1277                                    mMediaButtonEventReceiver);
1278                        } else {
1279                            MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext,
1280                                    mComponentName);
1281                        }
1282                        mIsMbrRegistered = true;
1283                    } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1284                        if (android.os.Build.VERSION.SDK_INT >= 18) {
1285                            MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1286                                    mMediaButtonEventReceiver);
1287                        } else {
1288                            MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
1289                                    mComponentName);
1290                        }
1291                        mIsMbrRegistered = false;
1292                    }
1293                }
1294                // On API 14+ register a RCC if it's supported, unregister it if
1295                // not.
1296                if (android.os.Build.VERSION.SDK_INT >= 14) {
1297                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1298                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1299                        mIsRccRegistered = true;
1300                        registeredRcc = true;
1301                    } else if (mIsRccRegistered
1302                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1303                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1304                        mIsRccRegistered = false;
1305                    }
1306                }
1307            } else {
1308                // When inactive remove any registered components.
1309                if (mIsMbrRegistered) {
1310                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1311                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1312                                mMediaButtonEventReceiver);
1313                    } else {
1314                        MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
1315                                mComponentName);
1316                    }
1317                    mIsMbrRegistered = false;
1318                }
1319                if (mIsRccRegistered) {
1320                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1321                    mIsRccRegistered = false;
1322                }
1323            }
1324            return registeredRcc;
1325        }
1326
1327        private void adjustVolume(int direction, int flags) {
1328            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1329                if (mVolumeProvider != null) {
1330                    mVolumeProvider.onAdjustVolume(direction);
1331                }
1332            } else {
1333                mAudioManager.adjustStreamVolume(direction, mLocalStream, flags);
1334            }
1335        }
1336
1337        private void setVolumeTo(int value, int flags) {
1338            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1339                if (mVolumeProvider != null) {
1340                    mVolumeProvider.onSetVolumeTo(value);
1341                }
1342            } else {
1343                mAudioManager.setStreamVolume(mLocalStream, value, flags);
1344            }
1345        }
1346
1347        private PlaybackStateCompat getStateWithUpdatedPosition() {
1348            PlaybackStateCompat state;
1349            long duration = -1;
1350            synchronized (mLock) {
1351                state = mState;
1352                if (mMetadata != null
1353                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1354                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1355                }
1356            }
1357
1358            PlaybackStateCompat result = null;
1359            if (state != null) {
1360                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1361                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1362                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1363                    long updateTime = state.getLastPositionUpdateTime();
1364                    long currentTime = SystemClock.elapsedRealtime();
1365                    if (updateTime > 0) {
1366                        long position = (long) (state.getPlaybackSpeed()
1367                                * (currentTime - updateTime)) + state.getPosition();
1368                        if (duration >= 0 && position > duration) {
1369                            position = duration;
1370                        } else if (position < 0) {
1371                            position = 0;
1372                        }
1373                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1374                                state);
1375                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1376                                currentTime);
1377                        result = builder.build();
1378                    }
1379                }
1380            }
1381            return result == null ? state : result;
1382        }
1383
1384        private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1385            int size = mControllerCallbacks.beginBroadcast();
1386            for (int i = size - 1; i >= 0; i--) {
1387                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1388                try {
1389                    cb.onVolumeInfoChanged(info);
1390                } catch (RemoteException e) {
1391                }
1392            }
1393            mControllerCallbacks.finishBroadcast();
1394        }
1395
1396        private void sendSessionDestroyed() {
1397            int size = mControllerCallbacks.beginBroadcast();
1398            for (int i = size - 1; i >= 0; i--) {
1399                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1400                try {
1401                    cb.onSessionDestroyed();;
1402                } catch (RemoteException e) {
1403                }
1404            }
1405            mControllerCallbacks.finishBroadcast();
1406            mControllerCallbacks.kill();
1407        }
1408
1409        private void sendEvent(String event, Bundle extras) {
1410            int size = mControllerCallbacks.beginBroadcast();
1411            for (int i = size - 1; i >= 0; i--) {
1412                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1413                try {
1414                    cb.onEvent(event, extras);
1415                } catch (RemoteException e) {
1416                }
1417            }
1418            mControllerCallbacks.finishBroadcast();
1419        }
1420
1421        private void sendState(PlaybackStateCompat state) {
1422            int size = mControllerCallbacks.beginBroadcast();
1423            for (int i = size - 1; i >= 0; i--) {
1424                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1425                try {
1426                    cb.onPlaybackStateChanged(state);
1427                } catch (RemoteException e) {
1428                }
1429            }
1430            mControllerCallbacks.finishBroadcast();
1431        }
1432
1433        private void sendMetadata(MediaMetadataCompat metadata) {
1434            int size = mControllerCallbacks.beginBroadcast();
1435            for (int i = size - 1; i >= 0; i--) {
1436                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1437                try {
1438                    cb.onMetadataChanged(metadata);
1439                } catch (RemoteException e) {
1440                }
1441            }
1442            mControllerCallbacks.finishBroadcast();
1443        }
1444
1445        private void sendQueue(List<QueueItem> queue) {
1446            int size = mControllerCallbacks.beginBroadcast();
1447            for (int i = size - 1; i >= 0; i--) {
1448                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1449                try {
1450                    cb.onQueueChanged(queue);
1451                } catch (RemoteException e) {
1452                }
1453            }
1454            mControllerCallbacks.finishBroadcast();
1455        }
1456
1457        private void sendQueueTitle(CharSequence queueTitle) {
1458            int size = mControllerCallbacks.beginBroadcast();
1459            for (int i = size - 1; i >= 0; i--) {
1460                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1461                try {
1462                    cb.onQueueTitleChanged(queueTitle);
1463                } catch (RemoteException e) {
1464                }
1465            }
1466            mControllerCallbacks.finishBroadcast();
1467        }
1468
1469        class MediaSessionStub extends IMediaSession.Stub {
1470            @Override
1471            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
1472                mHandler.post(MessageHandler.MSG_COMMAND,
1473                        new Command(command, args, cb.mResultReceiver));
1474            }
1475
1476            @Override
1477            public boolean sendMediaButton(KeyEvent mediaButton) {
1478                boolean handlesMediaButtons =
1479                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
1480                if (handlesMediaButtons) {
1481                    mHandler.post(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
1482                }
1483                return handlesMediaButtons;
1484            }
1485
1486            @Override
1487            public void registerCallbackListener(IMediaControllerCallback cb) {
1488                // If this session is already destroyed tell the caller and
1489                // don't add them.
1490                if (mDestroyed) {
1491                    try {
1492                        cb.onSessionDestroyed();
1493                    } catch (Exception e) {
1494                        // ignored
1495                    }
1496                    return;
1497                }
1498                mControllerCallbacks.register(cb);
1499            }
1500
1501            @Override
1502            public void unregisterCallbackListener(IMediaControllerCallback cb) {
1503                mControllerCallbacks.unregister(cb);
1504            }
1505
1506            @Override
1507            public String getPackageName() {
1508                // mPackageName is final so doesn't need synchronize block
1509                return mPackageName;
1510            }
1511
1512            @Override
1513            public String getTag() {
1514                // mTag is final so doesn't need synchronize block
1515                return mTag;
1516            }
1517
1518            @Override
1519            public PendingIntent getLaunchPendingIntent() {
1520                synchronized (mLock) {
1521                    return mSessionActivity;
1522                }
1523            }
1524
1525            @Override
1526            public long getFlags() {
1527                synchronized (mLock) {
1528                    return mFlags;
1529                }
1530            }
1531
1532            @Override
1533            public ParcelableVolumeInfo getVolumeAttributes() {
1534                int controlType;
1535                int max;
1536                int current;
1537                int stream;
1538                int volumeType;
1539                synchronized (mLock) {
1540                    volumeType = mVolumeType;
1541                    stream = mLocalStream;
1542                    VolumeProviderCompat vp = mVolumeProvider;
1543                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1544                        controlType = vp.getVolumeControl();
1545                        max = vp.getMaxVolume();
1546                        current = vp.getCurrentVolume();
1547                    } else {
1548                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
1549                        max = mAudioManager.getStreamMaxVolume(stream);
1550                        current = mAudioManager.getStreamVolume(stream);
1551                    }
1552                }
1553                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
1554            }
1555
1556            @Override
1557            public void adjustVolume(int direction, int flags, String packageName) {
1558                MediaSessionImplBase.this.adjustVolume(direction, flags);
1559            }
1560
1561            @Override
1562            public void setVolumeTo(int value, int flags, String packageName) {
1563                MediaSessionImplBase.this.setVolumeTo(value, flags);
1564            }
1565
1566            @Override
1567            public void play() throws RemoteException {
1568                mHandler.post(MessageHandler.MSG_PLAY);
1569            }
1570
1571            @Override
1572            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1573                mHandler.post(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
1574            }
1575
1576            @Override
1577            public void playFromSearch(String query, Bundle extras) throws RemoteException {
1578                mHandler.post(MessageHandler.MSG_PLAY_SEARCH, query, extras);
1579            }
1580
1581            @Override
1582            public void skipToQueueItem(long id) {
1583                mHandler.post(MessageHandler.MSG_SKIP_TO_ITEM, id);
1584            }
1585
1586            @Override
1587            public void pause() throws RemoteException {
1588                mHandler.post(MessageHandler.MSG_PAUSE);
1589            }
1590
1591            @Override
1592            public void stop() throws RemoteException {
1593                mHandler.post(MessageHandler.MSG_STOP);
1594            }
1595
1596            @Override
1597            public void next() throws RemoteException {
1598                mHandler.post(MessageHandler.MSG_NEXT);
1599            }
1600
1601            @Override
1602            public void previous() throws RemoteException {
1603                mHandler.post(MessageHandler.MSG_PREVIOUS);
1604            }
1605
1606            @Override
1607            public void fastForward() throws RemoteException {
1608                mHandler.post(MessageHandler.MSG_FAST_FORWARD);
1609            }
1610
1611            @Override
1612            public void rewind() throws RemoteException {
1613                mHandler.post(MessageHandler.MSG_REWIND);
1614            }
1615
1616            @Override
1617            public void seekTo(long pos) throws RemoteException {
1618                mHandler.post(MessageHandler.MSG_SEEK_TO, pos);
1619            }
1620
1621            @Override
1622            public void rate(RatingCompat rating) throws RemoteException {
1623                mHandler.post(MessageHandler.MSG_RATE, rating);
1624            }
1625
1626            @Override
1627            public void sendCustomAction(String action, Bundle args)
1628                    throws RemoteException {
1629                mHandler.post(MessageHandler.MSG_CUSTOM_ACTION, action, args);
1630            }
1631
1632            @Override
1633            public MediaMetadataCompat getMetadata() {
1634                return mMetadata;
1635            }
1636
1637            @Override
1638            public PlaybackStateCompat getPlaybackState() {
1639                return getStateWithUpdatedPosition();
1640            }
1641
1642            @Override
1643            public List<QueueItem> getQueue() {
1644                synchronized (mLock) {
1645                    return mQueue;
1646                }
1647            }
1648
1649            @Override
1650            public CharSequence getQueueTitle() {
1651                return mQueueTitle;
1652            }
1653
1654            @Override
1655            public Bundle getExtras() {
1656                synchronized (mLock) {
1657                    return mExtras;
1658                }
1659            }
1660
1661            @Override
1662            public int getRatingType() {
1663                return mRatingType;
1664            }
1665
1666            @Override
1667            public boolean isTransportControlEnabled() {
1668                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
1669            }
1670        }
1671
1672        private static final class Command {
1673            public final String command;
1674            public final Bundle extras;
1675            public final ResultReceiver stub;
1676
1677            public Command(String command, Bundle extras, ResultReceiver stub) {
1678                this.command = command;
1679                this.extras = extras;
1680                this.stub = stub;
1681            }
1682        }
1683
1684        private class MessageHandler extends Handler {
1685
1686            private static final int MSG_PLAY = 1;
1687            private static final int MSG_PLAY_MEDIA_ID = 2;
1688            private static final int MSG_PLAY_SEARCH = 3;
1689            private static final int MSG_SKIP_TO_ITEM = 4;
1690            private static final int MSG_PAUSE = 5;
1691            private static final int MSG_STOP = 6;
1692            private static final int MSG_NEXT = 7;
1693            private static final int MSG_PREVIOUS = 8;
1694            private static final int MSG_FAST_FORWARD = 9;
1695            private static final int MSG_REWIND = 10;
1696            private static final int MSG_SEEK_TO = 11;
1697            private static final int MSG_RATE = 12;
1698            private static final int MSG_CUSTOM_ACTION = 13;
1699            private static final int MSG_MEDIA_BUTTON = 14;
1700            private static final int MSG_COMMAND = 15;
1701            private static final int MSG_ADJUST_VOLUME = 16;
1702            private static final int MSG_SET_VOLUME = 17;
1703
1704            public MessageHandler(Looper looper) {
1705                super(looper);
1706            }
1707
1708            public void post(int what, Object obj, Bundle bundle) {
1709                Message msg = obtainMessage(what, obj);
1710                msg.setData(bundle);
1711                msg.sendToTarget();
1712            }
1713
1714            public void post(int what, Object obj) {
1715                obtainMessage(what, obj).sendToTarget();
1716            }
1717
1718            public void post(int what) {
1719                post(what, null);
1720            }
1721
1722            public void post(int what, Object obj, int arg1) {
1723                obtainMessage(what, arg1, 0, obj).sendToTarget();
1724            }
1725
1726            @Override
1727            public void handleMessage(Message msg) {
1728                if (mCallback == null) {
1729                    return;
1730                }
1731                switch (msg.what) {
1732                    case MSG_PLAY:
1733                        mCallback.onPlay();
1734                        break;
1735                    case MSG_PLAY_MEDIA_ID:
1736                        mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
1737                        break;
1738                    case MSG_PLAY_SEARCH:
1739                        mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
1740                        break;
1741                    case MSG_SKIP_TO_ITEM:
1742                        mCallback.onSkipToQueueItem((Long) msg.obj);
1743                        break;
1744                    case MSG_PAUSE:
1745                        mCallback.onPause();
1746                        break;
1747                    case MSG_STOP:
1748                        mCallback.onStop();
1749                        break;
1750                    case MSG_NEXT:
1751                        mCallback.onSkipToNext();
1752                        break;
1753                    case MSG_PREVIOUS:
1754                        mCallback.onSkipToPrevious();
1755                        break;
1756                    case MSG_FAST_FORWARD:
1757                        mCallback.onFastForward();
1758                        break;
1759                    case MSG_REWIND:
1760                        mCallback.onRewind();
1761                        break;
1762                    case MSG_SEEK_TO:
1763                        mCallback.onSeekTo((Long) msg.obj);
1764                        break;
1765                    case MSG_RATE:
1766                        mCallback.onSetRating((RatingCompat) msg.obj);
1767                        break;
1768                    case MSG_CUSTOM_ACTION:
1769                        mCallback.onCustomAction((String) msg.obj, msg.getData());
1770                        break;
1771                    case MSG_MEDIA_BUTTON:
1772                        mCallback.onMediaButtonEvent((Intent) msg.obj);
1773                        break;
1774                    case MSG_COMMAND:
1775                        Command cmd = (Command) msg.obj;
1776                        mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
1777                        break;
1778                    case MSG_ADJUST_VOLUME:
1779                        adjustVolume((int) msg.obj, 0);
1780                        break;
1781                    case MSG_SET_VOLUME:
1782                        setVolumeTo((int) msg.obj, 0);
1783                        break;
1784                }
1785            }
1786        }
1787    }
1788
1789    static class MediaSessionImplApi21 implements MediaSessionImpl {
1790        private final Object mSessionObj;
1791        private final Token mToken;
1792
1793        private PendingIntent mMediaButtonIntent;
1794
1795        public MediaSessionImplApi21(Context context, String tag) {
1796            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
1797            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
1798        }
1799
1800        public MediaSessionImplApi21(Object mediaSession) {
1801            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
1802            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
1803        }
1804
1805        @Override
1806        public void setCallback(Callback callback, Handler handler) {
1807            MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler);
1808        }
1809
1810        @Override
1811        public void setFlags(int flags) {
1812            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
1813        }
1814
1815        @Override
1816        public void setPlaybackToLocal(int stream) {
1817            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
1818        }
1819
1820        @Override
1821        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1822            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
1823                    volumeProvider.getVolumeProvider());
1824        }
1825
1826        @Override
1827        public void setActive(boolean active) {
1828            MediaSessionCompatApi21.setActive(mSessionObj, active);
1829        }
1830
1831        @Override
1832        public boolean isActive() {
1833            return MediaSessionCompatApi21.isActive(mSessionObj);
1834        }
1835
1836        @Override
1837        public void sendSessionEvent(String event, Bundle extras) {
1838            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
1839        }
1840
1841        @Override
1842        public void release() {
1843            MediaSessionCompatApi21.release(mSessionObj);
1844        }
1845
1846        @Override
1847        public Token getSessionToken() {
1848            return mToken;
1849        }
1850
1851        @Override
1852        public void setPlaybackState(PlaybackStateCompat state) {
1853            MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState());
1854        }
1855
1856        @Override
1857        public void setMetadata(MediaMetadataCompat metadata) {
1858            MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata());
1859        }
1860
1861        @Override
1862        public void setSessionActivity(PendingIntent pi) {
1863            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
1864        }
1865
1866        @Override
1867        public void setMediaButtonReceiver(PendingIntent mbr) {
1868            mMediaButtonIntent = mbr;
1869            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
1870        }
1871
1872        @Override
1873        public void setQueue(List<QueueItem> queue) {
1874            List<Object> queueObjs = null;
1875            if (queue != null) {
1876                queueObjs = new ArrayList<Object>();
1877                for (QueueItem item : queue) {
1878                    queueObjs.add(item.getQueueItem());
1879                }
1880            }
1881            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
1882        }
1883
1884        @Override
1885        public void setQueueTitle(CharSequence title) {
1886            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
1887        }
1888
1889        @Override
1890        public void setRatingType(int type) {
1891            if (android.os.Build.VERSION.SDK_INT < 22) {
1892                // TODO figure out 21 implementation
1893            } else {
1894                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
1895            }
1896        }
1897
1898        @Override
1899        public void setExtras(Bundle extras) {
1900            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
1901        }
1902
1903        @Override
1904        public Object getMediaSession() {
1905            return mSessionObj;
1906        }
1907
1908        @Override
1909        public Object getRemoteControlClient() {
1910            return null;
1911        }
1912    }
1913}
1914