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.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.graphics.Bitmap;
27import android.media.AudioManager;
28import android.net.Uri;
29import android.os.Build;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.Message;
35import android.os.Parcel;
36import android.os.Parcelable;
37import android.os.RemoteCallbackList;
38import android.os.RemoteException;
39import android.os.ResultReceiver;
40import android.os.SystemClock;
41import android.support.annotation.IntDef;
42import android.support.annotation.RestrictTo;
43import android.support.v4.media.MediaDescriptionCompat;
44import android.support.v4.media.MediaMetadataCompat;
45import android.support.v4.media.RatingCompat;
46import android.support.v4.media.VolumeProviderCompat;
47import android.text.TextUtils;
48import android.util.Log;
49import android.util.TypedValue;
50import android.view.KeyEvent;
51
52import java.lang.annotation.Retention;
53import java.lang.annotation.RetentionPolicy;
54import java.util.ArrayList;
55import java.util.List;
56
57import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
58
59/**
60 * Allows interaction with media controllers, volume keys, media buttons, and
61 * transport controls.
62 * <p>
63 * A MediaSession should be created when an app wants to publish media playback
64 * information or handle media keys. In general an app only needs one session
65 * for all playback, though multiple sessions can be created to provide finer
66 * grain controls of media.
67 * <p>
68 * Once a session is created the owner of the session may pass its
69 * {@link #getSessionToken() session token} to other processes to allow them to
70 * create a {@link MediaControllerCompat} to interact with the session.
71 * <p>
72 * To receive commands, media keys, and other events a {@link Callback} must be
73 * set with {@link #setCallback(Callback)}.
74 * <p>
75 * When an app is finished performing playback it must call {@link #release()}
76 * to clean up the session and notify any controllers.
77 * <p>
78 * MediaSessionCompat objects are not thread safe and all calls should be made
79 * from the same thread.
80 * <p>
81 * This is a helper for accessing features in
82 * {@link android.media.session.MediaSession} introduced after API level 4 in a
83 * backwards compatible fashion.
84 */
85public class MediaSessionCompat {
86    static final String TAG = "MediaSessionCompat";
87
88    private final MediaSessionImpl mImpl;
89    private final MediaControllerCompat mController;
90    private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
91
92    /**
93     * @hide
94     */
95    @RestrictTo(GROUP_ID)
96    @IntDef(flag=true, value={FLAG_HANDLES_MEDIA_BUTTONS, FLAG_HANDLES_TRANSPORT_CONTROLS})
97    @Retention(RetentionPolicy.SOURCE)
98    public @interface SessionFlags {}
99
100    /**
101     * Set this flag on the session to indicate that it can handle media button
102     * events.
103     */
104    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
105
106    /**
107     * Set this flag on the session to indicate that it handles transport
108     * control commands through its {@link Callback}.
109     */
110    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
111
112    /**
113     * Custom action to invoke playFromUri() for the forward compatibility.
114     */
115    static final String ACTION_PLAY_FROM_URI =
116            "android.support.v4.media.session.action.PLAY_FROM_URI";
117
118    /**
119     * Custom action to invoke prepare() for the forward compatibility.
120     */
121    static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
122
123    /**
124     * Custom action to invoke prepareFromMediaId() for the forward compatibility.
125     */
126    static final String ACTION_PREPARE_FROM_MEDIA_ID =
127            "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
128
129    /**
130     * Custom action to invoke prepareFromSearch() for the forward compatibility.
131     */
132    static final String ACTION_PREPARE_FROM_SEARCH =
133            "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
134
135    /**
136     * Custom action to invoke prepareFromUri() for the forward compatibility.
137     */
138    static final String ACTION_PREPARE_FROM_URI =
139            "android.support.v4.media.session.action.PREPARE_FROM_URI";
140
141    /**
142     * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
143     */
144    static final String ACTION_ARGUMENT_MEDIA_ID =
145            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
146
147    /**
148     * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
149     */
150    static final String ACTION_ARGUMENT_QUERY =
151            "android.support.v4.media.session.action.ARGUMENT_QUERY";
152
153    /**
154     * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
155     * indicating URI to play.
156     */
157    static final String ACTION_ARGUMENT_URI =
158            "android.support.v4.media.session.action.ARGUMENT_URI";
159
160    /**
161     * Argument for use with various actions indicating extra bundle.
162     */
163    static final String ACTION_ARGUMENT_EXTRAS =
164            "android.support.v4.media.session.action.ARGUMENT_EXTRAS";
165
166    // Maximum size of the bitmap in dp.
167    private static final int MAX_BITMAP_SIZE_IN_DP = 320;
168
169    // Maximum size of the bitmap in px. It shouldn't be changed.
170    static int sMaxBitmapSize;
171
172    /**
173     * Creates a new session. You must call {@link #release()} when finished with the session.
174     * <p>
175     * The session will automatically be registered with the system but will not be published
176     * until {@link #setActive(boolean) setActive(true)} is called.
177     * </p><p>
178     * For API 20 or earlier, note that a media button receiver is required for handling
179     * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
180     * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more
181     * details.
182     * </p>
183     * @param context The context to use to create the session.
184     * @param tag A short name for debugging purposes.
185     */
186    public MediaSessionCompat(Context context, String tag) {
187        this(context, tag, null, null);
188    }
189
190    /**
191     * Creates a new session with a specified media button receiver (a component name and/or
192     * a pending intent). You must call {@link #release()} when finished with the session.
193     * <p>
194     * The session will automatically be registered with the system but will not be published
195     * until {@link #setActive(boolean) setActive(true)} is called. Note that {@code mbrComponent}
196     * and {@code mrbIntent} are only used for API 20 or earlier. If you  want to set a media button
197     * receiver in API 21 or later, call {@link #setMediaButtonReceiver}.
198     * </p><p>
199     * For API 20 or earlier, the new session will use the given {@code mbrComponent}.
200     * If null, this will attempt to find an appropriate {@link BroadcastReceiver} that handles
201     * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest. See {@link MediaButtonReceiver} for
202     * more details.
203     * </p>
204     * @param context The context to use to create the session.
205     * @param tag A short name for debugging purposes.
206     * @param mbrComponent The component name for your media button receiver.
207     * @param mbrIntent The PendingIntent for your receiver component that handles
208     *            media button events. This is optional and will be used on between
209     *            {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
210     *            {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the
211     *            component name.
212     */
213    public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent,
214            PendingIntent mbrIntent) {
215        if (context == null) {
216            throw new IllegalArgumentException("context must not be null");
217        }
218        if (TextUtils.isEmpty(tag)) {
219            throw new IllegalArgumentException("tag must not be null or empty");
220        }
221
222        if (android.os.Build.VERSION.SDK_INT >= 21) {
223            mImpl = new MediaSessionImplApi21(context, tag);
224        } else {
225            mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent);
226        }
227        mController = new MediaControllerCompat(context, this);
228
229        if (sMaxBitmapSize == 0) {
230            sMaxBitmapSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
231                    MAX_BITMAP_SIZE_IN_DP, context.getResources().getDisplayMetrics());
232        }
233    }
234
235    private MediaSessionCompat(Context context, MediaSessionImpl impl) {
236        mImpl = impl;
237        mController = new MediaControllerCompat(context, this);
238    }
239
240    /**
241     * Add a callback to receive updates on for the MediaSession. This includes
242     * media button and volume events. The caller's thread will be used to post
243     * events.
244     *
245     * @param callback The callback object
246     */
247    public void setCallback(Callback callback) {
248        setCallback(callback, null);
249    }
250
251    /**
252     * Set the callback to receive updates for the MediaSession. This includes
253     * media button and volume events. Set the callback to null to stop
254     * receiving events.
255     *
256     * @param callback The callback to receive updates on.
257     * @param handler The handler that events should be posted on.
258     */
259    public void setCallback(Callback callback, Handler handler) {
260        mImpl.setCallback(callback, handler != null ? handler : new Handler());
261    }
262
263    /**
264     * Set an intent for launching UI for this Session. This can be used as a
265     * quick link to an ongoing media screen. The intent should be for an
266     * activity that may be started using
267     * {@link Activity#startActivity(Intent)}.
268     *
269     * @param pi The intent to launch to show UI for this Session.
270     */
271    public void setSessionActivity(PendingIntent pi) {
272        mImpl.setSessionActivity(pi);
273    }
274
275    /**
276     * Set a pending intent for your media button receiver to allow restarting
277     * playback after the session has been stopped. If your app is started in
278     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
279     * the pending intent.
280     * <p>
281     * This method will only work on
282     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
283     * platform versions must include the media button receiver in the
284     * constructor.
285     *
286     * @param mbr The {@link PendingIntent} to send the media button event to.
287     */
288    public void setMediaButtonReceiver(PendingIntent mbr) {
289        mImpl.setMediaButtonReceiver(mbr);
290    }
291
292    /**
293     * Set any flags for the session.
294     *
295     * @param flags The flags to set for this session.
296     */
297    public void setFlags(@SessionFlags int flags) {
298        mImpl.setFlags(flags);
299    }
300
301    /**
302     * Set the stream this session is playing on. This will affect the system's
303     * volume handling for this session. If {@link #setPlaybackToRemote} was
304     * previously called it will stop receiving volume commands and the system
305     * will begin sending volume changes to the appropriate stream.
306     * <p>
307     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
308     *
309     * @param stream The {@link AudioManager} stream this session is playing on.
310     */
311    public void setPlaybackToLocal(int stream) {
312        mImpl.setPlaybackToLocal(stream);
313    }
314
315    /**
316     * Configure this session to use remote volume handling. This must be called
317     * to receive volume button events, otherwise the system will adjust the
318     * current stream volume for this session. If {@link #setPlaybackToLocal}
319     * was previously called that stream will stop receiving volume changes for
320     * this session.
321     * <p>
322     * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
323     * this will only allow an app to handle volume commands sent directly to
324     * the session by a {@link MediaControllerCompat}. System routing of volume
325     * keys will not use the volume provider.
326     *
327     * @param volumeProvider The provider that will handle volume changes. May
328     *            not be null.
329     */
330    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
331        if (volumeProvider == null) {
332            throw new IllegalArgumentException("volumeProvider may not be null!");
333        }
334        mImpl.setPlaybackToRemote(volumeProvider);
335    }
336
337    /**
338     * Set if this session is currently active and ready to receive commands. If
339     * set to false your session's controller may not be discoverable. You must
340     * set the session to active before it can start receiving media button
341     * events or transport commands.
342     * <p>
343     * On platforms earlier than
344     * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
345     * a media button event receiver should be set via the constructor to
346     * receive media button events.
347     *
348     * @param active Whether this session is active or not.
349     */
350    public void setActive(boolean active) {
351        mImpl.setActive(active);
352        for (OnActiveChangeListener listener : mActiveListeners) {
353            listener.onActiveChanged();
354        }
355    }
356
357    /**
358     * Get the current active state of this session.
359     *
360     * @return True if the session is active, false otherwise.
361     */
362    public boolean isActive() {
363        return mImpl.isActive();
364    }
365
366    /**
367     * Send a proprietary event to all MediaControllers listening to this
368     * Session. It's up to the Controller/Session owner to determine the meaning
369     * of any events.
370     *
371     * @param event The name of the event to send
372     * @param extras Any extras included with the event
373     */
374    public void sendSessionEvent(String event, Bundle extras) {
375        if (TextUtils.isEmpty(event)) {
376            throw new IllegalArgumentException("event cannot be null or empty");
377        }
378        mImpl.sendSessionEvent(event, extras);
379    }
380
381    /**
382     * This must be called when an app has finished performing playback. If
383     * playback is expected to start again shortly the session can be left open,
384     * but it must be released if your activity or service is being destroyed.
385     */
386    public void release() {
387        mImpl.release();
388    }
389
390    /**
391     * Retrieve a token object that can be used by apps to create a
392     * {@link MediaControllerCompat} for interacting with this session. The
393     * owner of the session is responsible for deciding how to distribute these
394     * tokens.
395     * <p>
396     * On platform versions before
397     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
398     * used within your app as there is no way to guarantee other apps are using
399     * the same version of the support library.
400     *
401     * @return A token that can be used to create a media controller for this
402     *         session.
403     */
404    public Token getSessionToken() {
405        return mImpl.getSessionToken();
406    }
407
408    /**
409     * Get a controller for this session. This is a convenience method to avoid
410     * having to cache your own controller in process.
411     *
412     * @return A controller for this session.
413     */
414    public MediaControllerCompat getController() {
415        return mController;
416    }
417
418    /**
419     * Update the current playback state.
420     *
421     * @param state The current state of playback
422     */
423    public void setPlaybackState(PlaybackStateCompat state) {
424        mImpl.setPlaybackState(state);
425    }
426
427    /**
428     * Update the current metadata. New metadata can be created using
429     * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
430     * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
431     *
432     * @param metadata The new metadata
433     * @see android.support.v4.media.MediaMetadataCompat.Builder#putBitmap
434     */
435    public void setMetadata(MediaMetadataCompat metadata) {
436        mImpl.setMetadata(metadata);
437    }
438
439    /**
440     * Update the list of items in the play queue. It is an ordered list and
441     * should contain the current item, and previous or upcoming items if they
442     * exist. Specify null if there is no current play queue.
443     * <p>
444     * The queue should be of reasonable size. If the play queue is unbounded
445     * within your app, it is better to send a reasonable amount in a sliding
446     * window instead.
447     *
448     * @param queue A list of items in the play queue.
449     */
450    public void setQueue(List<QueueItem> queue) {
451        mImpl.setQueue(queue);
452    }
453
454    /**
455     * Set the title of the play queue. The UI should display this title along
456     * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
457     * name.
458     *
459     * @param title The title of the play queue.
460     */
461    public void setQueueTitle(CharSequence title) {
462        mImpl.setQueueTitle(title);
463    }
464
465    /**
466     * Set the style of rating used by this session. Apps trying to set the
467     * rating should use this style. Must be one of the following:
468     * <ul>
469     * <li>{@link RatingCompat#RATING_NONE}</li>
470     * <li>{@link RatingCompat#RATING_3_STARS}</li>
471     * <li>{@link RatingCompat#RATING_4_STARS}</li>
472     * <li>{@link RatingCompat#RATING_5_STARS}</li>
473     * <li>{@link RatingCompat#RATING_HEART}</li>
474     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
475     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
476     * </ul>
477     */
478    public void setRatingType(@RatingCompat.Style int type) {
479        mImpl.setRatingType(type);
480    }
481
482    /**
483     * Set some extras that can be associated with the
484     * {@link MediaSessionCompat}. No assumptions should be made as to how a
485     * {@link MediaControllerCompat} will handle these extras. Keys should be
486     * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
487     *
488     * @param extras The extras associated with the session.
489     */
490    public void setExtras(Bundle extras) {
491        mImpl.setExtras(extras);
492    }
493
494    /**
495     * Gets the underlying framework {@link android.media.session.MediaSession}
496     * object.
497     * <p>
498     * This method is only supported on API 21+.
499     * </p>
500     *
501     * @return The underlying {@link android.media.session.MediaSession} object,
502     *         or null if none.
503     */
504    public Object getMediaSession() {
505        return mImpl.getMediaSession();
506    }
507
508    /**
509     * Gets the underlying framework {@link android.media.RemoteControlClient}
510     * object.
511     * <p>
512     * This method is only supported on APIs 14-20. On API 21+
513     * {@link #getMediaSession()} should be used instead.
514     *
515     * @return The underlying {@link android.media.RemoteControlClient} object,
516     *         or null if none.
517     */
518    public Object getRemoteControlClient() {
519        return mImpl.getRemoteControlClient();
520    }
521
522    /**
523     * Returns the name of the package that sent the last media button, transport control, or
524     * command from controllers and the system. This is only valid while in a request callback, such
525     * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
526     *
527     * @hide
528     */
529    @RestrictTo(GROUP_ID)
530    public String getCallingPackage() {
531        return mImpl.getCallingPackage();
532    }
533
534    /**
535     * Adds a listener to be notified when the active status of this session
536     * changes. This is primarily used by the support library and should not be
537     * needed by apps.
538     *
539     * @param listener The listener to add.
540     */
541    public void addOnActiveChangeListener(OnActiveChangeListener listener) {
542        if (listener == null) {
543            throw new IllegalArgumentException("Listener may not be null");
544        }
545        mActiveListeners.add(listener);
546    }
547
548    /**
549     * Stops the listener from being notified when the active status of this
550     * session changes.
551     *
552     * @param listener The listener to remove.
553     */
554    public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
555        if (listener == null) {
556            throw new IllegalArgumentException("Listener may not be null");
557        }
558        mActiveListeners.remove(listener);
559    }
560
561    /**
562     * Creates an instance from a framework {@link android.media.session.MediaSession} object.
563     * <p>
564     * This method is only supported on API 21+. On API 20 and below, it returns null.
565     * </p>
566     *
567     * @param context The context to use to create the session.
568     * @param mediaSession A {@link android.media.session.MediaSession} object.
569     * @return An equivalent {@link MediaSessionCompat} object, or null if none.
570     * @deprecated Use {@link #fromMediaSession(Context, Object)} instead.
571     */
572    @Deprecated
573    public static MediaSessionCompat obtain(Context context, Object mediaSession) {
574        return fromMediaSession(context, mediaSession);
575    }
576
577    /**
578     * Creates an instance from a framework {@link android.media.session.MediaSession} object.
579     * <p>
580     * This method is only supported on API 21+. On API 20 and below, it returns null.
581     * </p>
582     *
583     * @param context The context to use to create the session.
584     * @param mediaSession A {@link android.media.session.MediaSession} object.
585     * @return An equivalent {@link MediaSessionCompat} object, or null if none.
586     */
587    public static MediaSessionCompat fromMediaSession(Context context, Object mediaSession) {
588        if (context == null || mediaSession == null || Build.VERSION.SDK_INT < 21) {
589            return null;
590        }
591        return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
592    }
593
594    /**
595     * Receives transport controls, media buttons, and commands from controllers
596     * and the system. The callback may be set using {@link #setCallback}.
597     */
598    public abstract static class Callback {
599        final Object mCallbackObj;
600
601        public Callback() {
602            if (android.os.Build.VERSION.SDK_INT >= 24) {
603                mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
604            } else if (android.os.Build.VERSION.SDK_INT >= 23) {
605                mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
606            } else if (android.os.Build.VERSION.SDK_INT >= 21) {
607                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
608            } else {
609                mCallbackObj = null;
610            }
611        }
612
613        /**
614         * Called when a controller has sent a custom command to this session.
615         * The owner of the session may handle custom commands but is not
616         * required to.
617         *
618         * @param command The command name.
619         * @param extras Optional parameters for the command, may be null.
620         * @param cb A result receiver to which a result may be sent by the command, may be null.
621         */
622        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
623        }
624
625        /**
626         * Override to handle media button events.
627         *
628         * @param mediaButtonEvent The media button event intent.
629         * @return True if the event was handled, false otherwise.
630         */
631        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
632            return false;
633        }
634
635        /**
636         * Override to handle requests to prepare playback. During the preparation, a session
637         * should not hold audio focus in order to allow other session play seamlessly.
638         * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
639         * after the preparation is done.
640         */
641        public void onPrepare() {
642        }
643
644        /**
645         * Override to handle requests to prepare for playing a specific mediaId that was provided
646         * by your app. During the preparation, a session should not hold audio focus in order to
647         * allow other session play seamlessly. The state of playback should be updated to
648         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
649         * of the prepared content should start in the implementation of {@link #onPlay}. Override
650         * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
651         */
652        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
653        }
654
655        /**
656         * Override to handle requests to prepare playback from a search query. An
657         * empty query indicates that the app may prepare any music. The
658         * implementation should attempt to make a smart choice about what to
659         * play. During the preparation, a session should not hold audio focus in order to allow
660         * other session play seamlessly. The state of playback should be updated to
661         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
662         * The playback of the prepared content should start in the implementation of
663         * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
664         * starting playback without preparation.
665         */
666        public void onPrepareFromSearch(String query, Bundle extras) {
667        }
668
669        /**
670         * Override to handle requests to prepare a specific media item represented by a URI.
671         * During the preparation, a session should not hold audio focus in order to allow other
672         * session play seamlessly. The state of playback should be updated to
673         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
674         * the prepared content should start in the implementation of {@link #onPlay}. Override
675         * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
676         */
677        public void onPrepareFromUri(Uri uri, Bundle extras) {
678        }
679
680        /**
681         * Override to handle requests to begin playback.
682         */
683        public void onPlay() {
684        }
685
686        /**
687         * Override to handle requests to play a specific mediaId that was
688         * provided by your app.
689         */
690        public void onPlayFromMediaId(String mediaId, Bundle extras) {
691        }
692
693        /**
694         * Override to handle requests to begin playback from a search query. An
695         * empty query indicates that the app may play any music. The
696         * implementation should attempt to make a smart choice about what to
697         * play.
698         */
699        public void onPlayFromSearch(String query, Bundle extras) {
700        }
701
702        /**
703         * Override to handle requests to play a specific media item represented by a URI.
704         */
705        public void onPlayFromUri(Uri uri, Bundle extras) {
706        }
707
708        /**
709         * Override to handle requests to play an item with a given id from the
710         * play queue.
711         */
712        public void onSkipToQueueItem(long id) {
713        }
714
715        /**
716         * Override to handle requests to pause playback.
717         */
718        public void onPause() {
719        }
720
721        /**
722         * Override to handle requests to skip to the next media item.
723         */
724        public void onSkipToNext() {
725        }
726
727        /**
728         * Override to handle requests to skip to the previous media item.
729         */
730        public void onSkipToPrevious() {
731        }
732
733        /**
734         * Override to handle requests to fast forward.
735         */
736        public void onFastForward() {
737        }
738
739        /**
740         * Override to handle requests to rewind.
741         */
742        public void onRewind() {
743        }
744
745        /**
746         * Override to handle requests to stop playback.
747         */
748        public void onStop() {
749        }
750
751        /**
752         * Override to handle requests to seek to a specific position in ms.
753         *
754         * @param pos New position to move to, in milliseconds.
755         */
756        public void onSeekTo(long pos) {
757        }
758
759        /**
760         * Override to handle the item being rated.
761         *
762         * @param rating
763         */
764        public void onSetRating(RatingCompat rating) {
765        }
766
767        /**
768         * Called when a {@link MediaControllerCompat} wants a
769         * {@link PlaybackStateCompat.CustomAction} to be performed.
770         *
771         * @param action The action that was originally sent in the
772         *            {@link PlaybackStateCompat.CustomAction}.
773         * @param extras Optional extras specified by the
774         *            {@link MediaControllerCompat}.
775         */
776        public void onCustomAction(String action, Bundle extras) {
777        }
778
779        private class StubApi21 implements MediaSessionCompatApi21.Callback {
780
781            StubApi21() {
782            }
783
784            @Override
785            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
786                Callback.this.onCommand(command, extras, cb);
787            }
788
789            @Override
790            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
791                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
792            }
793
794            @Override
795            public void onPlay() {
796                Callback.this.onPlay();
797            }
798
799            @Override
800            public void onPlayFromMediaId(String mediaId, Bundle extras) {
801                Callback.this.onPlayFromMediaId(mediaId, extras);
802            }
803
804            @Override
805            public void onPlayFromSearch(String search, Bundle extras) {
806                Callback.this.onPlayFromSearch(search, extras);
807            }
808
809            @Override
810            public void onSkipToQueueItem(long id) {
811                Callback.this.onSkipToQueueItem(id);
812            }
813
814            @Override
815            public void onPause() {
816                Callback.this.onPause();
817            }
818
819            @Override
820            public void onSkipToNext() {
821                Callback.this.onSkipToNext();
822            }
823
824            @Override
825            public void onSkipToPrevious() {
826                Callback.this.onSkipToPrevious();
827            }
828
829            @Override
830            public void onFastForward() {
831                Callback.this.onFastForward();
832            }
833
834            @Override
835            public void onRewind() {
836                Callback.this.onRewind();
837            }
838
839            @Override
840            public void onStop() {
841                Callback.this.onStop();
842            }
843
844            @Override
845            public void onSeekTo(long pos) {
846                Callback.this.onSeekTo(pos);
847            }
848
849            @Override
850            public void onSetRating(Object ratingObj) {
851                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
852            }
853
854            @Override
855            public void onCustomAction(String action, Bundle extras) {
856                if (action.equals(ACTION_PLAY_FROM_URI)) {
857                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
858                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
859                    Callback.this.onPlayFromUri(uri, bundle);
860                } else if (action.equals(ACTION_PREPARE)) {
861                    Callback.this.onPrepare();
862                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
863                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
864                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
865                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
866                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
867                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
868                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
869                    Callback.this.onPrepareFromSearch(query, bundle);
870                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
871                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
872                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
873                    Callback.this.onPrepareFromUri(uri, bundle);
874                } else {
875                    Callback.this.onCustomAction(action, extras);
876                }
877            }
878        }
879
880        private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
881
882            StubApi23() {
883            }
884
885            @Override
886            public void onPlayFromUri(Uri uri, Bundle extras) {
887                Callback.this.onPlayFromUri(uri, extras);
888            }
889        }
890
891        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
892
893            StubApi24() {
894            }
895
896            @Override
897            public void onPrepare() {
898                Callback.this.onPrepare();
899            }
900
901            @Override
902            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
903                Callback.this.onPrepareFromMediaId(mediaId, extras);
904            }
905
906            @Override
907            public void onPrepareFromSearch(String query, Bundle extras) {
908                Callback.this.onPrepareFromSearch(query, extras);
909            }
910
911            @Override
912            public void onPrepareFromUri(Uri uri, Bundle extras) {
913                Callback.this.onPrepareFromUri(uri, extras);
914            }
915        }
916    }
917
918    /**
919     * Represents an ongoing session. This may be passed to apps by the session
920     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
921     * the session.
922     */
923    public static final class Token implements Parcelable {
924        private final Object mInner;
925
926        Token(Object inner) {
927            mInner = inner;
928        }
929
930        /**
931         * Creates a compat Token from a framework
932         * {@link android.media.session.MediaSession.Token} object.
933         * <p>
934         * This method is only supported on
935         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
936         * </p>
937         *
938         * @param token The framework token object.
939         * @return A compat Token for use with {@link MediaControllerCompat}.
940         */
941        public static Token fromToken(Object token) {
942            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
943                return null;
944            }
945            return new Token(MediaSessionCompatApi21.verifyToken(token));
946        }
947
948        @Override
949        public int describeContents() {
950            return 0;
951        }
952
953        @Override
954        public void writeToParcel(Parcel dest, int flags) {
955            if (android.os.Build.VERSION.SDK_INT >= 21) {
956                dest.writeParcelable((Parcelable) mInner, flags);
957            } else {
958                dest.writeStrongBinder((IBinder) mInner);
959            }
960        }
961
962        @Override
963        public int hashCode() {
964            if (mInner == null) {
965                return 0;
966            }
967            return mInner.hashCode();
968        }
969
970        @Override
971        public boolean equals(Object obj) {
972            if (this == obj) {
973                return true;
974            }
975            if (!(obj instanceof Token)) {
976                return false;
977            }
978
979            Token other = (Token) obj;
980            if (mInner == null) {
981                return other.mInner == null;
982            }
983            if (other.mInner == null) {
984                return false;
985            }
986            return mInner.equals(other.mInner);
987        }
988
989        /**
990         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
991         * <p>
992         * This method is only supported on API 21+.
993         * </p>
994         *
995         * @return The underlying {@link android.media.session.MediaSession.Token} object,
996         * or null if none.
997         */
998        public Object getToken() {
999            return mInner;
1000        }
1001
1002        public static final Parcelable.Creator<Token> CREATOR
1003                = new Parcelable.Creator<Token>() {
1004            @Override
1005            public Token createFromParcel(Parcel in) {
1006                Object inner;
1007                if (android.os.Build.VERSION.SDK_INT >= 21) {
1008                    inner = in.readParcelable(null);
1009                } else {
1010                    inner = in.readStrongBinder();
1011                }
1012                return new Token(inner);
1013            }
1014
1015            @Override
1016            public Token[] newArray(int size) {
1017                return new Token[size];
1018            }
1019        };
1020    }
1021
1022    /**
1023     * A single item that is part of the play queue. It contains a description
1024     * of the item and its id in the queue.
1025     */
1026    public static final class QueueItem implements Parcelable {
1027        /**
1028         * This id is reserved. No items can be explicitly assigned this id.
1029         */
1030        public static final int UNKNOWN_ID = -1;
1031
1032        private final MediaDescriptionCompat mDescription;
1033        private final long mId;
1034
1035        private Object mItem;
1036
1037        /**
1038         * Create a new {@link MediaSessionCompat.QueueItem}.
1039         *
1040         * @param description The {@link MediaDescriptionCompat} for this item.
1041         * @param id An identifier for this item. It must be unique within the
1042         *            play queue and cannot be {@link #UNKNOWN_ID}.
1043         */
1044        public QueueItem(MediaDescriptionCompat description, long id) {
1045            this(null, description, id);
1046        }
1047
1048        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
1049            if (description == null) {
1050                throw new IllegalArgumentException("Description cannot be null.");
1051            }
1052            if (id == UNKNOWN_ID) {
1053                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1054            }
1055            mDescription = description;
1056            mId = id;
1057            mItem = queueItem;
1058        }
1059
1060        QueueItem(Parcel in) {
1061            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
1062            mId = in.readLong();
1063        }
1064
1065        /**
1066         * Get the description for this item.
1067         */
1068        public MediaDescriptionCompat getDescription() {
1069            return mDescription;
1070        }
1071
1072        /**
1073         * Get the queue id for this item.
1074         */
1075        public long getQueueId() {
1076            return mId;
1077        }
1078
1079        @Override
1080        public void writeToParcel(Parcel dest, int flags) {
1081            mDescription.writeToParcel(dest, flags);
1082            dest.writeLong(mId);
1083        }
1084
1085        @Override
1086        public int describeContents() {
1087            return 0;
1088        }
1089
1090        /**
1091         * Get the underlying
1092         * {@link android.media.session.MediaSession.QueueItem}.
1093         * <p>
1094         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1095         * is returned.
1096         *
1097         * @return The underlying
1098         *         {@link android.media.session.MediaSession.QueueItem} or null.
1099         */
1100        public Object getQueueItem() {
1101            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1102                return mItem;
1103            }
1104            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1105                    mId);
1106            return mItem;
1107        }
1108
1109        /**
1110         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1111         * object.
1112         * <p>
1113         * This method is only supported on API 21+. On API 20 and below, it returns null.
1114         * </p>
1115         *
1116         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1117         * @return An equivalent {@link QueueItem} object, or null if none.
1118         * @deprecated Use {@link #fromQueueItem(Object)} instead.
1119         */
1120        @Deprecated
1121        public static QueueItem obtain(Object queueItem) {
1122            return fromQueueItem(queueItem);
1123        }
1124
1125        /**
1126         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1127         * object.
1128         * <p>
1129         * This method is only supported on API 21+. On API 20 and below, it returns null.
1130         * </p>
1131         *
1132         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1133         * @return An equivalent {@link QueueItem} object, or null if none.
1134         */
1135        public static QueueItem fromQueueItem(Object queueItem) {
1136            if (queueItem == null || Build.VERSION.SDK_INT < 21) {
1137                return null;
1138            }
1139            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1140            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1141                    descriptionObj);
1142            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1143            return new QueueItem(queueItem, description, id);
1144        }
1145
1146        /**
1147         * Creates a list of {@link QueueItem} objects from a framework
1148         * {@link android.media.session.MediaSession.QueueItem} object list.
1149         * <p>
1150         * This method is only supported on API 21+. On API 20 and below, it returns null.
1151         * </p>
1152         *
1153         * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects.
1154         * @return An equivalent list of {@link QueueItem} objects, or null if none.
1155         */
1156        public static List<QueueItem> fromQueueItemList(List<?> itemList) {
1157            if (itemList == null || Build.VERSION.SDK_INT < 21) {
1158                return null;
1159            }
1160            List<QueueItem> items = new ArrayList<>();
1161            for (Object itemObj : itemList) {
1162                items.add(fromQueueItem(itemObj));
1163            }
1164            return items;
1165        }
1166
1167        public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1168                = new Creator<MediaSessionCompat.QueueItem>() {
1169
1170            @Override
1171            public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1172                return new MediaSessionCompat.QueueItem(p);
1173            }
1174
1175            @Override
1176            public MediaSessionCompat.QueueItem[] newArray(int size) {
1177                return new MediaSessionCompat.QueueItem[size];
1178            }
1179        };
1180
1181        @Override
1182        public String toString() {
1183            return "MediaSession.QueueItem {" +
1184                    "Description=" + mDescription +
1185                    ", Id=" + mId + " }";
1186        }
1187    }
1188
1189    /**
1190     * This is a wrapper for {@link ResultReceiver} for sending over aidl
1191     * interfaces. The framework version was not exposed to aidls until
1192     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1193     */
1194    static final class ResultReceiverWrapper implements Parcelable {
1195        private ResultReceiver mResultReceiver;
1196
1197        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1198            mResultReceiver = resultReceiver;
1199        }
1200
1201        ResultReceiverWrapper(Parcel in) {
1202            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1203        }
1204
1205        public static final Creator<ResultReceiverWrapper>
1206                CREATOR = new Creator<ResultReceiverWrapper>() {
1207            @Override
1208            public ResultReceiverWrapper createFromParcel(Parcel p) {
1209                return new ResultReceiverWrapper(p);
1210            }
1211
1212            @Override
1213            public ResultReceiverWrapper[] newArray(int size) {
1214                return new ResultReceiverWrapper[size];
1215            }
1216        };
1217
1218        @Override
1219        public int describeContents() {
1220            return 0;
1221        }
1222
1223        @Override
1224        public void writeToParcel(Parcel dest, int flags) {
1225            mResultReceiver.writeToParcel(dest, flags);
1226        }
1227    }
1228
1229    public interface OnActiveChangeListener {
1230        void onActiveChanged();
1231    }
1232
1233    interface MediaSessionImpl {
1234        void setCallback(Callback callback, Handler handler);
1235        void setFlags(@SessionFlags int flags);
1236        void setPlaybackToLocal(int stream);
1237        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
1238        void setActive(boolean active);
1239        boolean isActive();
1240        void sendSessionEvent(String event, Bundle extras);
1241        void release();
1242        Token getSessionToken();
1243        void setPlaybackState(PlaybackStateCompat state);
1244        void setMetadata(MediaMetadataCompat metadata);
1245
1246        void setSessionActivity(PendingIntent pi);
1247
1248        void setMediaButtonReceiver(PendingIntent mbr);
1249        void setQueue(List<QueueItem> queue);
1250        void setQueueTitle(CharSequence title);
1251
1252        void setRatingType(@RatingCompat.Style int type);
1253        void setExtras(Bundle extras);
1254
1255        Object getMediaSession();
1256
1257        Object getRemoteControlClient();
1258
1259        String getCallingPackage();
1260    }
1261
1262    static class MediaSessionImplBase implements MediaSessionImpl {
1263        private final Context mContext;
1264        private final ComponentName mMediaButtonReceiverComponentName;
1265        private final PendingIntent mMediaButtonReceiverIntent;
1266        private final Object mRccObj;
1267        private final MediaSessionStub mStub;
1268        private final Token mToken;
1269        final String mPackageName;
1270        final String mTag;
1271        final AudioManager mAudioManager;
1272
1273        final Object mLock = new Object();
1274        final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1275                = new RemoteCallbackList<>();
1276
1277        private MessageHandler mHandler;
1278        boolean mDestroyed = false;
1279        private boolean mIsActive = false;
1280        private boolean mIsRccRegistered = false;
1281        private boolean mIsMbrRegistered = false;
1282        volatile Callback mCallback;
1283
1284        @SessionFlags int mFlags;
1285
1286        MediaMetadataCompat mMetadata;
1287        PlaybackStateCompat mState;
1288        PendingIntent mSessionActivity;
1289        List<QueueItem> mQueue;
1290        CharSequence mQueueTitle;
1291        @RatingCompat.Style int mRatingType;
1292        Bundle mExtras;
1293
1294        int mVolumeType;
1295        int mLocalStream;
1296        VolumeProviderCompat mVolumeProvider;
1297
1298        private VolumeProviderCompat.Callback mVolumeCallback
1299                = new VolumeProviderCompat.Callback() {
1300            @Override
1301            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1302                if (mVolumeProvider != volumeProvider) {
1303                    return;
1304                }
1305                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1306                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1307                        volumeProvider.getCurrentVolume());
1308                sendVolumeInfoChanged(info);
1309            }
1310        };
1311
1312        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1313                PendingIntent mbrIntent) {
1314            if (mbrComponent == null) {
1315                mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context);
1316                if (mbrComponent == null) {
1317                    Log.w(TAG, "Couldn't find a unique registered media button receiver in the "
1318                            + "given context.");
1319                }
1320            }
1321            if (mbrComponent != null && mbrIntent == null) {
1322                // construct a PendingIntent for the media button
1323                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1324                // the associated intent will be handled by the component being registered
1325                mediaButtonIntent.setComponent(mbrComponent);
1326                mbrIntent = PendingIntent.getBroadcast(context,
1327                        0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
1328            }
1329            if (mbrComponent == null) {
1330                throw new IllegalArgumentException(
1331                        "MediaButtonReceiver component may not be null.");
1332            }
1333            mContext = context;
1334            mPackageName = context.getPackageName();
1335            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1336            mTag = tag;
1337            mMediaButtonReceiverComponentName = mbrComponent;
1338            mMediaButtonReceiverIntent = mbrIntent;
1339            mStub = new MediaSessionStub();
1340            mToken = new Token(mStub);
1341
1342            mRatingType = RatingCompat.RATING_NONE;
1343            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1344            mLocalStream = AudioManager.STREAM_MUSIC;
1345            if (android.os.Build.VERSION.SDK_INT >= 14) {
1346                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
1347            } else {
1348                mRccObj = null;
1349            }
1350        }
1351
1352        @Override
1353        public void setCallback(Callback callback, Handler handler) {
1354            mCallback = callback;
1355            if (callback == null) {
1356                // There's nothing to unregister on API < 18 since media buttons
1357                // all go through the media button receiver
1358                if (android.os.Build.VERSION.SDK_INT >= 18) {
1359                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1360                }
1361                if (android.os.Build.VERSION.SDK_INT >= 19) {
1362                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1363                }
1364            } else {
1365                if (handler == null) {
1366                    handler = new Handler();
1367                }
1368                synchronized (mLock) {
1369                    mHandler = new MessageHandler(handler.getLooper());
1370                }
1371                MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
1372                    @Override
1373                    public void onSetRating(Object ratingObj) {
1374                        postToHandler(MessageHandler.MSG_RATE,
1375                                RatingCompat.fromRating(ratingObj));
1376                    }
1377
1378                    @Override
1379                    public void onSeekTo(long pos) {
1380                        postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1381                    }
1382                };
1383                if (android.os.Build.VERSION.SDK_INT >= 18) {
1384                    Object onPositionUpdateObj = MediaSessionCompatApi18
1385                            .createPlaybackPositionUpdateListener(cb19);
1386                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1387                            onPositionUpdateObj);
1388                }
1389                if (android.os.Build.VERSION.SDK_INT >= 19) {
1390                    Object onMetadataUpdateObj = MediaSessionCompatApi19
1391                            .createMetadataUpdateListener(cb19);
1392                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1393                            onMetadataUpdateObj);
1394                }
1395            }
1396        }
1397
1398        void postToHandler(int what) {
1399            postToHandler(what, null);
1400        }
1401
1402        void postToHandler(int what, Object obj) {
1403            postToHandler(what, obj, null);
1404        }
1405
1406        void postToHandler(int what, Object obj, Bundle extras) {
1407            synchronized (mLock) {
1408                if (mHandler != null) {
1409                    mHandler.post(what, obj, extras);
1410                }
1411            }
1412        }
1413
1414        @Override
1415        public void setFlags(@SessionFlags int flags) {
1416            synchronized (mLock) {
1417                mFlags = flags;
1418            }
1419            update();
1420        }
1421
1422        @Override
1423        public void setPlaybackToLocal(int stream) {
1424            if (mVolumeProvider != null) {
1425                mVolumeProvider.setCallback(null);
1426            }
1427            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1428            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1429                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1430                    mAudioManager.getStreamMaxVolume(mLocalStream),
1431                    mAudioManager.getStreamVolume(mLocalStream));
1432            sendVolumeInfoChanged(info);
1433        }
1434
1435        @Override
1436        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1437            if (volumeProvider == null) {
1438                throw new IllegalArgumentException("volumeProvider may not be null");
1439            }
1440            if (mVolumeProvider != null) {
1441                mVolumeProvider.setCallback(null);
1442            }
1443            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1444            mVolumeProvider = volumeProvider;
1445            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1446                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1447                    mVolumeProvider.getCurrentVolume());
1448            sendVolumeInfoChanged(info);
1449
1450            volumeProvider.setCallback(mVolumeCallback);
1451        }
1452
1453        @Override
1454        public void setActive(boolean active) {
1455            if (active == mIsActive) {
1456                return;
1457            }
1458            mIsActive = active;
1459            if (update()) {
1460                setMetadata(mMetadata);
1461                setPlaybackState(mState);
1462            }
1463        }
1464
1465        @Override
1466        public boolean isActive() {
1467            return mIsActive;
1468        }
1469
1470        @Override
1471        public void sendSessionEvent(String event, Bundle extras) {
1472            sendEvent(event, extras);
1473        }
1474
1475        @Override
1476        public void release() {
1477            mIsActive = false;
1478            mDestroyed = true;
1479            update();
1480            sendSessionDestroyed();
1481        }
1482
1483        @Override
1484        public Token getSessionToken() {
1485            return mToken;
1486        }
1487
1488        @Override
1489        public void setPlaybackState(PlaybackStateCompat state) {
1490            synchronized (mLock) {
1491                mState = state;
1492            }
1493            sendState(state);
1494            if (!mIsActive) {
1495                // Don't set the state until after the RCC is registered
1496                return;
1497            }
1498            if (state == null) {
1499                if (android.os.Build.VERSION.SDK_INT >= 14) {
1500                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1501                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0);
1502                }
1503            } else {
1504                // Set state
1505                if (android.os.Build.VERSION.SDK_INT >= 18) {
1506                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1507                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1508                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1509                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
1510                }
1511
1512                // Set transport control flags
1513                if (android.os.Build.VERSION.SDK_INT >= 19) {
1514                    MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions());
1515                } else if (android.os.Build.VERSION.SDK_INT >= 18) {
1516                    MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions());
1517                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1518                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions());
1519                }
1520            }
1521        }
1522
1523        @Override
1524        public void setMetadata(MediaMetadataCompat metadata) {
1525            if (metadata != null) {
1526                // Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the
1527                // metadata if necessary. Bitmaps can be scaled down if they are large.
1528                metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build();
1529            }
1530
1531            synchronized (mLock) {
1532                mMetadata = metadata;
1533            }
1534            sendMetadata(metadata);
1535            if (!mIsActive) {
1536                // Don't set metadata until after the rcc has been registered
1537                return;
1538            }
1539            if (android.os.Build.VERSION.SDK_INT >= 19) {
1540                MediaSessionCompatApi19.setMetadata(mRccObj,
1541                        metadata == null ? null : metadata.getBundle(),
1542                        mState == null ? 0 : mState.getActions());
1543            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1544                MediaSessionCompatApi14.setMetadata(mRccObj,
1545                        metadata == null ? null : metadata.getBundle());
1546            }
1547        }
1548
1549        @Override
1550        public void setSessionActivity(PendingIntent pi) {
1551            synchronized (mLock) {
1552                mSessionActivity = pi;
1553            }
1554        }
1555
1556        @Override
1557        public void setMediaButtonReceiver(PendingIntent mbr) {
1558            // Do nothing, changing this is not supported before API 21.
1559        }
1560
1561        @Override
1562        public void setQueue(List<QueueItem> queue) {
1563            mQueue = queue;
1564            sendQueue(queue);
1565        }
1566
1567        @Override
1568        public void setQueueTitle(CharSequence title) {
1569            mQueueTitle = title;
1570            sendQueueTitle(title);
1571        }
1572
1573        @Override
1574        public Object getMediaSession() {
1575            return null;
1576        }
1577
1578        @Override
1579        public Object getRemoteControlClient() {
1580            return mRccObj;
1581        }
1582
1583        @Override
1584        public String getCallingPackage() {
1585            return null;
1586        }
1587
1588        @Override
1589        public void setRatingType(@RatingCompat.Style int type) {
1590            mRatingType = type;
1591        }
1592
1593        @Override
1594        public void setExtras(Bundle extras) {
1595            mExtras = extras;
1596            sendExtras(extras);
1597        }
1598
1599        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
1600        private boolean update() {
1601            boolean registeredRcc = false;
1602            if (mIsActive) {
1603                // Register a MBR if it's supported, unregister it
1604                // if support was removed.
1605                if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1606                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1607                        MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1608                                mMediaButtonReceiverIntent,
1609                                mMediaButtonReceiverComponentName);
1610                    } else {
1611                        AudioManager am = (AudioManager) mContext.getSystemService(
1612                                Context.AUDIO_SERVICE);
1613                        am.registerMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1614                    }
1615                    mIsMbrRegistered = true;
1616                } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1617                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1618                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1619                                mMediaButtonReceiverIntent,
1620                                mMediaButtonReceiverComponentName);
1621                    } else {
1622                        AudioManager am = (AudioManager) mContext.getSystemService(
1623                                Context.AUDIO_SERVICE);
1624                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1625                    }
1626                    mIsMbrRegistered = false;
1627                }
1628                // On API 14+ register a RCC if it's supported, unregister it if
1629                // not.
1630                if (android.os.Build.VERSION.SDK_INT >= 14) {
1631                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1632                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1633                        mIsRccRegistered = true;
1634                        registeredRcc = true;
1635                    } else if (mIsRccRegistered
1636                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1637                        // RCC keeps the state while the system resets its state internally when
1638                        // we register RCC. Reset the state so that the states in RCC and the system
1639                        // are in sync when we re-register the RCC.
1640                        MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1641                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1642                        mIsRccRegistered = false;
1643                    }
1644                }
1645            } else {
1646                // When inactive remove any registered components.
1647                if (mIsMbrRegistered) {
1648                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1649                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1650                                mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
1651                    } else {
1652                        AudioManager am = (AudioManager) mContext.getSystemService(
1653                                Context.AUDIO_SERVICE);
1654                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1655                    }
1656                    mIsMbrRegistered = false;
1657                }
1658                if (mIsRccRegistered) {
1659                    // RCC keeps the state while the system resets its state internally when
1660                    // we register RCC. Reset the state so that the states in RCC and the system
1661                    // are in sync when we re-register the RCC.
1662                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1663                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1664                    mIsRccRegistered = false;
1665                }
1666            }
1667            return registeredRcc;
1668        }
1669
1670        void adjustVolume(int direction, int flags) {
1671            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1672                if (mVolumeProvider != null) {
1673                    mVolumeProvider.onAdjustVolume(direction);
1674                }
1675            } else {
1676                mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
1677            }
1678        }
1679
1680        void setVolumeTo(int value, int flags) {
1681            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1682                if (mVolumeProvider != null) {
1683                    mVolumeProvider.onSetVolumeTo(value);
1684                }
1685            } else {
1686                mAudioManager.setStreamVolume(mLocalStream, value, flags);
1687            }
1688        }
1689
1690        PlaybackStateCompat getStateWithUpdatedPosition() {
1691            PlaybackStateCompat state;
1692            long duration = -1;
1693            synchronized (mLock) {
1694                state = mState;
1695                if (mMetadata != null
1696                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1697                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1698                }
1699            }
1700
1701            PlaybackStateCompat result = null;
1702            if (state != null) {
1703                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1704                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1705                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1706                    long updateTime = state.getLastPositionUpdateTime();
1707                    long currentTime = SystemClock.elapsedRealtime();
1708                    if (updateTime > 0) {
1709                        long position = (long) (state.getPlaybackSpeed()
1710                                * (currentTime - updateTime)) + state.getPosition();
1711                        if (duration >= 0 && position > duration) {
1712                            position = duration;
1713                        } else if (position < 0) {
1714                            position = 0;
1715                        }
1716                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1717                                state);
1718                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1719                                currentTime);
1720                        result = builder.build();
1721                    }
1722                }
1723            }
1724            return result == null ? state : result;
1725        }
1726
1727        void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1728            int size = mControllerCallbacks.beginBroadcast();
1729            for (int i = size - 1; i >= 0; i--) {
1730                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1731                try {
1732                    cb.onVolumeInfoChanged(info);
1733                } catch (RemoteException e) {
1734                }
1735            }
1736            mControllerCallbacks.finishBroadcast();
1737        }
1738
1739        private void sendSessionDestroyed() {
1740            int size = mControllerCallbacks.beginBroadcast();
1741            for (int i = size - 1; i >= 0; i--) {
1742                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1743                try {
1744                    cb.onSessionDestroyed();
1745                } catch (RemoteException e) {
1746                }
1747            }
1748            mControllerCallbacks.finishBroadcast();
1749            mControllerCallbacks.kill();
1750        }
1751
1752        private void sendEvent(String event, Bundle extras) {
1753            int size = mControllerCallbacks.beginBroadcast();
1754            for (int i = size - 1; i >= 0; i--) {
1755                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1756                try {
1757                    cb.onEvent(event, extras);
1758                } catch (RemoteException e) {
1759                }
1760            }
1761            mControllerCallbacks.finishBroadcast();
1762        }
1763
1764        private void sendState(PlaybackStateCompat state) {
1765            int size = mControllerCallbacks.beginBroadcast();
1766            for (int i = size - 1; i >= 0; i--) {
1767                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1768                try {
1769                    cb.onPlaybackStateChanged(state);
1770                } catch (RemoteException e) {
1771                }
1772            }
1773            mControllerCallbacks.finishBroadcast();
1774        }
1775
1776        private void sendMetadata(MediaMetadataCompat metadata) {
1777            int size = mControllerCallbacks.beginBroadcast();
1778            for (int i = size - 1; i >= 0; i--) {
1779                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1780                try {
1781                    cb.onMetadataChanged(metadata);
1782                } catch (RemoteException e) {
1783                }
1784            }
1785            mControllerCallbacks.finishBroadcast();
1786        }
1787
1788        private void sendQueue(List<QueueItem> queue) {
1789            int size = mControllerCallbacks.beginBroadcast();
1790            for (int i = size - 1; i >= 0; i--) {
1791                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1792                try {
1793                    cb.onQueueChanged(queue);
1794                } catch (RemoteException e) {
1795                }
1796            }
1797            mControllerCallbacks.finishBroadcast();
1798        }
1799
1800        private void sendQueueTitle(CharSequence queueTitle) {
1801            int size = mControllerCallbacks.beginBroadcast();
1802            for (int i = size - 1; i >= 0; i--) {
1803                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1804                try {
1805                    cb.onQueueTitleChanged(queueTitle);
1806                } catch (RemoteException e) {
1807                }
1808            }
1809            mControllerCallbacks.finishBroadcast();
1810        }
1811
1812        private void sendExtras(Bundle extras) {
1813            int size = mControllerCallbacks.beginBroadcast();
1814            for (int i = size - 1; i >= 0; i--) {
1815                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1816                try {
1817                    cb.onExtrasChanged(extras);
1818                } catch (RemoteException e) {
1819                }
1820            }
1821            mControllerCallbacks.finishBroadcast();
1822        }
1823
1824        class MediaSessionStub extends IMediaSession.Stub {
1825            @Override
1826            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
1827                postToHandler(MessageHandler.MSG_COMMAND,
1828                        new Command(command, args, cb.mResultReceiver));
1829            }
1830
1831            @Override
1832            public boolean sendMediaButton(KeyEvent mediaButton) {
1833                boolean handlesMediaButtons =
1834                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
1835                if (handlesMediaButtons) {
1836                    postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
1837                }
1838                return handlesMediaButtons;
1839            }
1840
1841            @Override
1842            public void registerCallbackListener(IMediaControllerCallback cb) {
1843                // If this session is already destroyed tell the caller and
1844                // don't add them.
1845                if (mDestroyed) {
1846                    try {
1847                        cb.onSessionDestroyed();
1848                    } catch (Exception e) {
1849                        // ignored
1850                    }
1851                    return;
1852                }
1853                mControllerCallbacks.register(cb);
1854            }
1855
1856            @Override
1857            public void unregisterCallbackListener(IMediaControllerCallback cb) {
1858                mControllerCallbacks.unregister(cb);
1859            }
1860
1861            @Override
1862            public String getPackageName() {
1863                // mPackageName is final so doesn't need synchronize block
1864                return mPackageName;
1865            }
1866
1867            @Override
1868            public String getTag() {
1869                // mTag is final so doesn't need synchronize block
1870                return mTag;
1871            }
1872
1873            @Override
1874            public PendingIntent getLaunchPendingIntent() {
1875                synchronized (mLock) {
1876                    return mSessionActivity;
1877                }
1878            }
1879
1880            @Override
1881            @SessionFlags
1882            public long getFlags() {
1883                synchronized (mLock) {
1884                    return mFlags;
1885                }
1886            }
1887
1888            @Override
1889            public ParcelableVolumeInfo getVolumeAttributes() {
1890                int controlType;
1891                int max;
1892                int current;
1893                int stream;
1894                int volumeType;
1895                synchronized (mLock) {
1896                    volumeType = mVolumeType;
1897                    stream = mLocalStream;
1898                    VolumeProviderCompat vp = mVolumeProvider;
1899                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1900                        controlType = vp.getVolumeControl();
1901                        max = vp.getMaxVolume();
1902                        current = vp.getCurrentVolume();
1903                    } else {
1904                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
1905                        max = mAudioManager.getStreamMaxVolume(stream);
1906                        current = mAudioManager.getStreamVolume(stream);
1907                    }
1908                }
1909                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
1910            }
1911
1912            @Override
1913            public void adjustVolume(int direction, int flags, String packageName) {
1914                MediaSessionImplBase.this.adjustVolume(direction, flags);
1915            }
1916
1917            @Override
1918            public void setVolumeTo(int value, int flags, String packageName) {
1919                MediaSessionImplBase.this.setVolumeTo(value, flags);
1920            }
1921
1922            @Override
1923            public void prepare() throws RemoteException {
1924                postToHandler(MessageHandler.MSG_PREPARE);
1925            }
1926
1927            @Override
1928            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1929                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
1930            }
1931
1932            @Override
1933            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
1934                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
1935            }
1936
1937            @Override
1938            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
1939                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
1940            }
1941
1942            @Override
1943            public void play() throws RemoteException {
1944                postToHandler(MessageHandler.MSG_PLAY);
1945            }
1946
1947            @Override
1948            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1949                postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
1950            }
1951
1952            @Override
1953            public void playFromSearch(String query, Bundle extras) throws RemoteException {
1954                postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
1955            }
1956
1957            @Override
1958            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1959                postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
1960            }
1961
1962            @Override
1963            public void skipToQueueItem(long id) {
1964                postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
1965            }
1966
1967            @Override
1968            public void pause() throws RemoteException {
1969                postToHandler(MessageHandler.MSG_PAUSE);
1970            }
1971
1972            @Override
1973            public void stop() throws RemoteException {
1974                postToHandler(MessageHandler.MSG_STOP);
1975            }
1976
1977            @Override
1978            public void next() throws RemoteException {
1979                postToHandler(MessageHandler.MSG_NEXT);
1980            }
1981
1982            @Override
1983            public void previous() throws RemoteException {
1984                postToHandler(MessageHandler.MSG_PREVIOUS);
1985            }
1986
1987            @Override
1988            public void fastForward() throws RemoteException {
1989                postToHandler(MessageHandler.MSG_FAST_FORWARD);
1990            }
1991
1992            @Override
1993            public void rewind() throws RemoteException {
1994                postToHandler(MessageHandler.MSG_REWIND);
1995            }
1996
1997            @Override
1998            public void seekTo(long pos) throws RemoteException {
1999                postToHandler(MessageHandler.MSG_SEEK_TO, pos);
2000            }
2001
2002            @Override
2003            public void rate(RatingCompat rating) throws RemoteException {
2004                postToHandler(MessageHandler.MSG_RATE, rating);
2005            }
2006
2007            @Override
2008            public void sendCustomAction(String action, Bundle args)
2009                    throws RemoteException {
2010                postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
2011            }
2012
2013            @Override
2014            public MediaMetadataCompat getMetadata() {
2015                return mMetadata;
2016            }
2017
2018            @Override
2019            public PlaybackStateCompat getPlaybackState() {
2020                return getStateWithUpdatedPosition();
2021            }
2022
2023            @Override
2024            public List<QueueItem> getQueue() {
2025                synchronized (mLock) {
2026                    return mQueue;
2027                }
2028            }
2029
2030            @Override
2031            public CharSequence getQueueTitle() {
2032                return mQueueTitle;
2033            }
2034
2035            @Override
2036            public Bundle getExtras() {
2037                synchronized (mLock) {
2038                    return mExtras;
2039                }
2040            }
2041
2042            @Override
2043            @RatingCompat.Style
2044            public int getRatingType() {
2045                return mRatingType;
2046            }
2047
2048            @Override
2049            public boolean isTransportControlEnabled() {
2050                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
2051            }
2052        }
2053
2054        private static final class Command {
2055            public final String command;
2056            public final Bundle extras;
2057            public final ResultReceiver stub;
2058
2059            public Command(String command, Bundle extras, ResultReceiver stub) {
2060                this.command = command;
2061                this.extras = extras;
2062                this.stub = stub;
2063            }
2064        }
2065
2066        private class MessageHandler extends Handler {
2067
2068            private static final int MSG_COMMAND = 1;
2069            private static final int MSG_ADJUST_VOLUME = 2;
2070            private static final int MSG_PREPARE = 3;
2071            private static final int MSG_PREPARE_MEDIA_ID = 4;
2072            private static final int MSG_PREPARE_SEARCH = 5;
2073            private static final int MSG_PREPARE_URI = 6;
2074            private static final int MSG_PLAY = 7;
2075            private static final int MSG_PLAY_MEDIA_ID = 8;
2076            private static final int MSG_PLAY_SEARCH = 9;
2077            private static final int MSG_PLAY_URI = 10;
2078            private static final int MSG_SKIP_TO_ITEM = 11;
2079            private static final int MSG_PAUSE = 12;
2080            private static final int MSG_STOP = 13;
2081            private static final int MSG_NEXT = 14;
2082            private static final int MSG_PREVIOUS = 15;
2083            private static final int MSG_FAST_FORWARD = 16;
2084            private static final int MSG_REWIND = 17;
2085            private static final int MSG_SEEK_TO = 18;
2086            private static final int MSG_RATE = 19;
2087            private static final int MSG_CUSTOM_ACTION = 20;
2088            private static final int MSG_MEDIA_BUTTON = 21;
2089            private static final int MSG_SET_VOLUME = 22;
2090
2091            // KeyEvent constants only available on API 11+
2092            private static final int KEYCODE_MEDIA_PAUSE = 127;
2093            private static final int KEYCODE_MEDIA_PLAY = 126;
2094
2095            public MessageHandler(Looper looper) {
2096                super(looper);
2097            }
2098
2099            public void post(int what, Object obj, Bundle bundle) {
2100                Message msg = obtainMessage(what, obj);
2101                msg.setData(bundle);
2102                msg.sendToTarget();
2103            }
2104
2105            public void post(int what, Object obj) {
2106                obtainMessage(what, obj).sendToTarget();
2107            }
2108
2109            public void post(int what) {
2110                post(what, null);
2111            }
2112
2113            public void post(int what, Object obj, int arg1) {
2114                obtainMessage(what, arg1, 0, obj).sendToTarget();
2115            }
2116
2117            @Override
2118            public void handleMessage(Message msg) {
2119                MediaSessionCompat.Callback cb = mCallback;
2120                if (cb == null) {
2121                    return;
2122                }
2123                switch (msg.what) {
2124                    case MSG_COMMAND:
2125                        Command cmd = (Command) msg.obj;
2126                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2127                        break;
2128                    case MSG_MEDIA_BUTTON:
2129                        KeyEvent keyEvent = (KeyEvent) msg.obj;
2130                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2131                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2132                        // Let the Callback handle events first before using the default behavior
2133                        if (!cb.onMediaButtonEvent(intent)) {
2134                            onMediaButtonEvent(keyEvent, cb);
2135                        }
2136                        break;
2137                    case MSG_PREPARE:
2138                        cb.onPrepare();
2139                        break;
2140                    case MSG_PREPARE_MEDIA_ID:
2141                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2142                        break;
2143                    case MSG_PREPARE_SEARCH:
2144                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2145                        break;
2146                    case MSG_PREPARE_URI:
2147                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2148                        break;
2149                    case MSG_PLAY:
2150                        cb.onPlay();
2151                        break;
2152                    case MSG_PLAY_MEDIA_ID:
2153                        cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2154                        break;
2155                    case MSG_PLAY_SEARCH:
2156                        cb.onPlayFromSearch((String) msg.obj, msg.getData());
2157                        break;
2158                    case MSG_PLAY_URI:
2159                        cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2160                        break;
2161                    case MSG_SKIP_TO_ITEM:
2162                        cb.onSkipToQueueItem((Long) msg.obj);
2163                        break;
2164                    case MSG_PAUSE:
2165                        cb.onPause();
2166                        break;
2167                    case MSG_STOP:
2168                        cb.onStop();
2169                        break;
2170                    case MSG_NEXT:
2171                        cb.onSkipToNext();
2172                        break;
2173                    case MSG_PREVIOUS:
2174                        cb.onSkipToPrevious();
2175                        break;
2176                    case MSG_FAST_FORWARD:
2177                        cb.onFastForward();
2178                        break;
2179                    case MSG_REWIND:
2180                        cb.onRewind();
2181                        break;
2182                    case MSG_SEEK_TO:
2183                        cb.onSeekTo((Long) msg.obj);
2184                        break;
2185                    case MSG_RATE:
2186                        cb.onSetRating((RatingCompat) msg.obj);
2187                        break;
2188                    case MSG_CUSTOM_ACTION:
2189                        cb.onCustomAction((String) msg.obj, msg.getData());
2190                        break;
2191                    case MSG_ADJUST_VOLUME:
2192                        adjustVolume((int) msg.obj, 0);
2193                        break;
2194                    case MSG_SET_VOLUME:
2195                        setVolumeTo((int) msg.obj, 0);
2196                        break;
2197                }
2198            }
2199
2200            private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2201                if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2202                    return;
2203                }
2204                long validActions = mState == null ? 0 : mState.getActions();
2205                switch (ke.getKeyCode()) {
2206                    // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2207                    case KEYCODE_MEDIA_PLAY:
2208                        if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2209                            cb.onPlay();
2210                        }
2211                        break;
2212                    // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2213                    case KEYCODE_MEDIA_PAUSE:
2214                        if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2215                            cb.onPause();
2216                        }
2217                        break;
2218                    case KeyEvent.KEYCODE_MEDIA_NEXT:
2219                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2220                            cb.onSkipToNext();
2221                        }
2222                        break;
2223                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2224                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2225                            cb.onSkipToPrevious();
2226                        }
2227                        break;
2228                    case KeyEvent.KEYCODE_MEDIA_STOP:
2229                        if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2230                            cb.onStop();
2231                        }
2232                        break;
2233                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2234                        if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2235                            cb.onFastForward();
2236                        }
2237                        break;
2238                    case KeyEvent.KEYCODE_MEDIA_REWIND:
2239                        if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2240                            cb.onRewind();
2241                        }
2242                        break;
2243                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2244                    case KeyEvent.KEYCODE_HEADSETHOOK:
2245                        boolean isPlaying = mState != null
2246                                && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2247                        boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2248                                | PlaybackStateCompat.ACTION_PLAY)) != 0;
2249                        boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2250                                | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2251                        if (isPlaying && canPause) {
2252                            cb.onPause();
2253                        } else if (!isPlaying && canPlay) {
2254                            cb.onPlay();
2255                        }
2256                        break;
2257                }
2258            }
2259        }
2260    }
2261
2262    static class MediaSessionImplApi21 implements MediaSessionImpl {
2263        private final Object mSessionObj;
2264        private final Token mToken;
2265
2266        private PendingIntent mMediaButtonIntent;
2267
2268        public MediaSessionImplApi21(Context context, String tag) {
2269            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
2270            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2271        }
2272
2273        public MediaSessionImplApi21(Object mediaSession) {
2274            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
2275            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2276        }
2277
2278        @Override
2279        public void setCallback(Callback callback, Handler handler) {
2280            MediaSessionCompatApi21.setCallback(mSessionObj,
2281                    callback == null ? null : callback.mCallbackObj, handler);
2282        }
2283
2284        @Override
2285        public void setFlags(@SessionFlags int flags) {
2286            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
2287        }
2288
2289        @Override
2290        public void setPlaybackToLocal(int stream) {
2291            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
2292        }
2293
2294        @Override
2295        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
2296            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
2297                    volumeProvider.getVolumeProvider());
2298        }
2299
2300        @Override
2301        public void setActive(boolean active) {
2302            MediaSessionCompatApi21.setActive(mSessionObj, active);
2303        }
2304
2305        @Override
2306        public boolean isActive() {
2307            return MediaSessionCompatApi21.isActive(mSessionObj);
2308        }
2309
2310        @Override
2311        public void sendSessionEvent(String event, Bundle extras) {
2312            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
2313        }
2314
2315        @Override
2316        public void release() {
2317            MediaSessionCompatApi21.release(mSessionObj);
2318        }
2319
2320        @Override
2321        public Token getSessionToken() {
2322            return mToken;
2323        }
2324
2325        @Override
2326        public void setPlaybackState(PlaybackStateCompat state) {
2327            MediaSessionCompatApi21.setPlaybackState(mSessionObj,
2328                    state == null ? null : state.getPlaybackState());
2329        }
2330
2331        @Override
2332        public void setMetadata(MediaMetadataCompat metadata) {
2333            MediaSessionCompatApi21.setMetadata(mSessionObj,
2334                    metadata == null ? null : metadata.getMediaMetadata());
2335        }
2336
2337        @Override
2338        public void setSessionActivity(PendingIntent pi) {
2339            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
2340        }
2341
2342        @Override
2343        public void setMediaButtonReceiver(PendingIntent mbr) {
2344            mMediaButtonIntent = mbr;
2345            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
2346        }
2347
2348        @Override
2349        public void setQueue(List<QueueItem> queue) {
2350            List<Object> queueObjs = null;
2351            if (queue != null) {
2352                queueObjs = new ArrayList<>();
2353                for (QueueItem item : queue) {
2354                    queueObjs.add(item.getQueueItem());
2355                }
2356            }
2357            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
2358        }
2359
2360        @Override
2361        public void setQueueTitle(CharSequence title) {
2362            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
2363        }
2364
2365        @Override
2366        public void setRatingType(@RatingCompat.Style int type) {
2367            if (android.os.Build.VERSION.SDK_INT < 22) {
2368                // TODO figure out 21 implementation
2369            } else {
2370                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
2371            }
2372        }
2373
2374        @Override
2375        public void setExtras(Bundle extras) {
2376            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
2377        }
2378
2379        @Override
2380        public Object getMediaSession() {
2381            return mSessionObj;
2382        }
2383
2384        @Override
2385        public Object getRemoteControlClient() {
2386            return null;
2387        }
2388
2389        @Override
2390        public String getCallingPackage() {
2391            if (android.os.Build.VERSION.SDK_INT < 24) {
2392                return null;
2393            } else {
2394                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
2395            }
2396        }
2397    }
2398}
2399