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