MediaSessionCompat.java revision e48441650b24efca174b0b8b29c6f284f8d01650
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    private 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    private 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            @Override
777            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
778                Callback.this.onCommand(command, extras, cb);
779            }
780
781            @Override
782            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
783                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
784            }
785
786            @Override
787            public void onPlay() {
788                Callback.this.onPlay();
789            }
790
791            @Override
792            public void onPlayFromMediaId(String mediaId, Bundle extras) {
793                Callback.this.onPlayFromMediaId(mediaId, extras);
794            }
795
796            @Override
797            public void onPlayFromSearch(String search, Bundle extras) {
798                Callback.this.onPlayFromSearch(search, extras);
799            }
800
801            @Override
802            public void onSkipToQueueItem(long id) {
803                Callback.this.onSkipToQueueItem(id);
804            }
805
806            @Override
807            public void onPause() {
808                Callback.this.onPause();
809            }
810
811            @Override
812            public void onSkipToNext() {
813                Callback.this.onSkipToNext();
814            }
815
816            @Override
817            public void onSkipToPrevious() {
818                Callback.this.onSkipToPrevious();
819            }
820
821            @Override
822            public void onFastForward() {
823                Callback.this.onFastForward();
824            }
825
826            @Override
827            public void onRewind() {
828                Callback.this.onRewind();
829            }
830
831            @Override
832            public void onStop() {
833                Callback.this.onStop();
834            }
835
836            @Override
837            public void onSeekTo(long pos) {
838                Callback.this.onSeekTo(pos);
839            }
840
841            @Override
842            public void onSetRating(Object ratingObj) {
843                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
844            }
845
846            @Override
847            public void onCustomAction(String action, Bundle extras) {
848                if (action.equals(ACTION_PLAY_FROM_URI)) {
849                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
850                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
851                    Callback.this.onPlayFromUri(uri, bundle);
852                } else if (action.equals(ACTION_PREPARE)) {
853                    Callback.this.onPrepare();
854                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
855                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
856                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
857                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
858                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
859                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
860                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
861                    Callback.this.onPrepareFromSearch(query, bundle);
862                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
863                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
864                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
865                    Callback.this.onPrepareFromUri(uri, bundle);
866                } else {
867                    Callback.this.onCustomAction(action, extras);
868                }
869            }
870        }
871
872        private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
873
874            @Override
875            public void onPlayFromUri(Uri uri, Bundle extras) {
876                Callback.this.onPlayFromUri(uri, extras);
877            }
878        }
879
880        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
881
882            @Override
883            public void onPrepare() {
884                Callback.this.onPrepare();
885            }
886
887            @Override
888            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
889                Callback.this.onPrepareFromMediaId(mediaId, extras);
890            }
891
892            @Override
893            public void onPrepareFromSearch(String query, Bundle extras) {
894                Callback.this.onPrepareFromSearch(query, extras);
895            }
896
897            @Override
898            public void onPrepareFromUri(Uri uri, Bundle extras) {
899                Callback.this.onPrepareFromUri(uri, extras);
900            }
901        }
902    }
903
904    /**
905     * Represents an ongoing session. This may be passed to apps by the session
906     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
907     * the session.
908     */
909    public static final class Token implements Parcelable {
910        private final Object mInner;
911
912        Token(Object inner) {
913            mInner = inner;
914        }
915
916        /**
917         * Creates a compat Token from a framework
918         * {@link android.media.session.MediaSession.Token} object.
919         * <p>
920         * This method is only supported on
921         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
922         * </p>
923         *
924         * @param token The framework token object.
925         * @return A compat Token for use with {@link MediaControllerCompat}.
926         */
927        public static Token fromToken(Object token) {
928            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
929                return null;
930            }
931            return new Token(MediaSessionCompatApi21.verifyToken(token));
932        }
933
934        @Override
935        public int describeContents() {
936            return 0;
937        }
938
939        @Override
940        public void writeToParcel(Parcel dest, int flags) {
941            if (android.os.Build.VERSION.SDK_INT >= 21) {
942                dest.writeParcelable((Parcelable) mInner, flags);
943            } else {
944                dest.writeStrongBinder((IBinder) mInner);
945            }
946        }
947
948        @Override
949        public int hashCode() {
950            if (mInner == null) {
951                return 0;
952            }
953            return mInner.hashCode();
954        }
955
956        @Override
957        public boolean equals(Object obj) {
958            if (this == obj) {
959                return true;
960            }
961            if (!(obj instanceof Token)) {
962                return false;
963            }
964
965            Token other = (Token) obj;
966            if (mInner == null) {
967                return other.mInner == null;
968            }
969            if (other.mInner == null) {
970                return false;
971            }
972            return mInner.equals(other.mInner);
973        }
974
975        /**
976         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
977         * <p>
978         * This method is only supported on API 21+.
979         * </p>
980         *
981         * @return The underlying {@link android.media.session.MediaSession.Token} object,
982         * or null if none.
983         */
984        public Object getToken() {
985            return mInner;
986        }
987
988        public static final Parcelable.Creator<Token> CREATOR
989                = new Parcelable.Creator<Token>() {
990            @Override
991            public Token createFromParcel(Parcel in) {
992                Object inner;
993                if (android.os.Build.VERSION.SDK_INT >= 21) {
994                    inner = in.readParcelable(null);
995                } else {
996                    inner = in.readStrongBinder();
997                }
998                return new Token(inner);
999            }
1000
1001            @Override
1002            public Token[] newArray(int size) {
1003                return new Token[size];
1004            }
1005        };
1006    }
1007
1008    /**
1009     * A single item that is part of the play queue. It contains a description
1010     * of the item and its id in the queue.
1011     */
1012    public static final class QueueItem implements Parcelable {
1013        /**
1014         * This id is reserved. No items can be explicitly assigned this id.
1015         */
1016        public static final int UNKNOWN_ID = -1;
1017
1018        private final MediaDescriptionCompat mDescription;
1019        private final long mId;
1020
1021        private Object mItem;
1022
1023        /**
1024         * Create a new {@link MediaSessionCompat.QueueItem}.
1025         *
1026         * @param description The {@link MediaDescriptionCompat} for this item.
1027         * @param id An identifier for this item. It must be unique within the
1028         *            play queue and cannot be {@link #UNKNOWN_ID}.
1029         */
1030        public QueueItem(MediaDescriptionCompat description, long id) {
1031            this(null, description, id);
1032        }
1033
1034        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
1035            if (description == null) {
1036                throw new IllegalArgumentException("Description cannot be null.");
1037            }
1038            if (id == UNKNOWN_ID) {
1039                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1040            }
1041            mDescription = description;
1042            mId = id;
1043            mItem = queueItem;
1044        }
1045
1046        private QueueItem(Parcel in) {
1047            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
1048            mId = in.readLong();
1049        }
1050
1051        /**
1052         * Get the description for this item.
1053         */
1054        public MediaDescriptionCompat getDescription() {
1055            return mDescription;
1056        }
1057
1058        /**
1059         * Get the queue id for this item.
1060         */
1061        public long getQueueId() {
1062            return mId;
1063        }
1064
1065        @Override
1066        public void writeToParcel(Parcel dest, int flags) {
1067            mDescription.writeToParcel(dest, flags);
1068            dest.writeLong(mId);
1069        }
1070
1071        @Override
1072        public int describeContents() {
1073            return 0;
1074        }
1075
1076        /**
1077         * Get the underlying
1078         * {@link android.media.session.MediaSession.QueueItem}.
1079         * <p>
1080         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1081         * is returned.
1082         *
1083         * @return The underlying
1084         *         {@link android.media.session.MediaSession.QueueItem} or null.
1085         */
1086        public Object getQueueItem() {
1087            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1088                return mItem;
1089            }
1090            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1091                    mId);
1092            return mItem;
1093        }
1094
1095        /**
1096         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1097         * object.
1098         * <p>
1099         * This method is only supported on API 21+. On API 20 and below, it returns null.
1100         * </p>
1101         *
1102         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1103         * @return An equivalent {@link QueueItem} object, or null if none.
1104         * @deprecated Use {@link #fromQueueItem(Object)} instead.
1105         */
1106        @Deprecated
1107        public static QueueItem obtain(Object queueItem) {
1108            return fromQueueItem(queueItem);
1109        }
1110
1111        /**
1112         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1113         * object.
1114         * <p>
1115         * This method is only supported on API 21+. On API 20 and below, it returns null.
1116         * </p>
1117         *
1118         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1119         * @return An equivalent {@link QueueItem} object, or null if none.
1120         */
1121        public static QueueItem fromQueueItem(Object queueItem) {
1122            if (queueItem == null || Build.VERSION.SDK_INT < 21) {
1123                return null;
1124            }
1125            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1126            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1127                    descriptionObj);
1128            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1129            return new QueueItem(queueItem, description, id);
1130        }
1131
1132        /**
1133         * Creates a list of {@link QueueItem} objects from a framework
1134         * {@link android.media.session.MediaSession.QueueItem} object list.
1135         * <p>
1136         * This method is only supported on API 21+. On API 20 and below, it returns null.
1137         * </p>
1138         *
1139         * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects.
1140         * @return An equivalent list of {@link QueueItem} objects, or null if none.
1141         */
1142        public static List<QueueItem> fromQueueItemList(List<?> itemList) {
1143            if (itemList == null || Build.VERSION.SDK_INT < 21) {
1144                return null;
1145            }
1146            List<QueueItem> items = new ArrayList<>();
1147            for (Object itemObj : itemList) {
1148                items.add(fromQueueItem(itemObj));
1149            }
1150            return items;
1151        }
1152
1153        public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1154                = new Creator<MediaSessionCompat.QueueItem>() {
1155
1156            @Override
1157            public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1158                return new MediaSessionCompat.QueueItem(p);
1159            }
1160
1161            @Override
1162            public MediaSessionCompat.QueueItem[] newArray(int size) {
1163                return new MediaSessionCompat.QueueItem[size];
1164            }
1165        };
1166
1167        @Override
1168        public String toString() {
1169            return "MediaSession.QueueItem {" +
1170                    "Description=" + mDescription +
1171                    ", Id=" + mId + " }";
1172        }
1173    }
1174
1175    /**
1176     * This is a wrapper for {@link ResultReceiver} for sending over aidl
1177     * interfaces. The framework version was not exposed to aidls until
1178     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1179     */
1180    static final class ResultReceiverWrapper implements Parcelable {
1181        private ResultReceiver mResultReceiver;
1182
1183        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1184            mResultReceiver = resultReceiver;
1185        }
1186
1187        ResultReceiverWrapper(Parcel in) {
1188            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1189        }
1190
1191        public static final Creator<ResultReceiverWrapper>
1192                CREATOR = new Creator<ResultReceiverWrapper>() {
1193            @Override
1194            public ResultReceiverWrapper createFromParcel(Parcel p) {
1195                return new ResultReceiverWrapper(p);
1196            }
1197
1198            @Override
1199            public ResultReceiverWrapper[] newArray(int size) {
1200                return new ResultReceiverWrapper[size];
1201            }
1202        };
1203
1204        @Override
1205        public int describeContents() {
1206            return 0;
1207        }
1208
1209        @Override
1210        public void writeToParcel(Parcel dest, int flags) {
1211            mResultReceiver.writeToParcel(dest, flags);
1212        }
1213    }
1214
1215    public interface OnActiveChangeListener {
1216        void onActiveChanged();
1217    }
1218
1219    interface MediaSessionImpl {
1220        void setCallback(Callback callback, Handler handler);
1221        void setFlags(@SessionFlags int flags);
1222        void setPlaybackToLocal(int stream);
1223        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
1224        void setActive(boolean active);
1225        boolean isActive();
1226        void sendSessionEvent(String event, Bundle extras);
1227        void release();
1228        Token getSessionToken();
1229        void setPlaybackState(PlaybackStateCompat state);
1230        void setMetadata(MediaMetadataCompat metadata);
1231
1232        void setSessionActivity(PendingIntent pi);
1233
1234        void setMediaButtonReceiver(PendingIntent mbr);
1235        void setQueue(List<QueueItem> queue);
1236        void setQueueTitle(CharSequence title);
1237
1238        void setRatingType(@RatingCompat.Style int type);
1239        void setExtras(Bundle extras);
1240
1241        Object getMediaSession();
1242
1243        Object getRemoteControlClient();
1244
1245        String getCallingPackage();
1246    }
1247
1248    static class MediaSessionImplBase implements MediaSessionImpl {
1249        private final Context mContext;
1250        private final ComponentName mMediaButtonReceiverComponentName;
1251        private final PendingIntent mMediaButtonReceiverIntent;
1252        private final Object mRccObj;
1253        private final MediaSessionStub mStub;
1254        private final Token mToken;
1255        private final String mPackageName;
1256        private final String mTag;
1257        private final AudioManager mAudioManager;
1258
1259        private final Object mLock = new Object();
1260        private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1261                = new RemoteCallbackList<>();
1262
1263        private MessageHandler mHandler;
1264        private boolean mDestroyed = false;
1265        private boolean mIsActive = false;
1266        private boolean mIsRccRegistered = false;
1267        private boolean mIsMbrRegistered = false;
1268        private volatile Callback mCallback;
1269
1270        private @SessionFlags int mFlags;
1271
1272        private MediaMetadataCompat mMetadata;
1273        private PlaybackStateCompat mState;
1274        private PendingIntent mSessionActivity;
1275        private List<QueueItem> mQueue;
1276        private CharSequence mQueueTitle;
1277        private @RatingCompat.Style int mRatingType;
1278        private Bundle mExtras;
1279
1280        private int mVolumeType;
1281        private int mLocalStream;
1282        private VolumeProviderCompat mVolumeProvider;
1283
1284        private VolumeProviderCompat.Callback mVolumeCallback
1285                = new VolumeProviderCompat.Callback() {
1286            @Override
1287            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1288                if (mVolumeProvider != volumeProvider) {
1289                    return;
1290                }
1291                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1292                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1293                        volumeProvider.getCurrentVolume());
1294                sendVolumeInfoChanged(info);
1295            }
1296        };
1297
1298        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1299                PendingIntent mbrIntent) {
1300            if (mbrComponent == null) {
1301                mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context);
1302                if (mbrComponent == null) {
1303                    Log.w(TAG, "Couldn't find a unique registered media button receiver in the "
1304                            + "given context.");
1305                }
1306            }
1307            if (mbrComponent != null && mbrIntent == null) {
1308                // construct a PendingIntent for the media button
1309                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1310                // the associated intent will be handled by the component being registered
1311                mediaButtonIntent.setComponent(mbrComponent);
1312                mbrIntent = PendingIntent.getBroadcast(context,
1313                        0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
1314            }
1315            if (mbrComponent == null) {
1316                throw new IllegalArgumentException(
1317                        "MediaButtonReceiver component may not be null.");
1318            }
1319            mContext = context;
1320            mPackageName = context.getPackageName();
1321            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1322            mTag = tag;
1323            mMediaButtonReceiverComponentName = mbrComponent;
1324            mMediaButtonReceiverIntent = mbrIntent;
1325            mStub = new MediaSessionStub();
1326            mToken = new Token(mStub);
1327
1328            mRatingType = RatingCompat.RATING_NONE;
1329            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1330            mLocalStream = AudioManager.STREAM_MUSIC;
1331            if (android.os.Build.VERSION.SDK_INT >= 14) {
1332                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
1333            } else {
1334                mRccObj = null;
1335            }
1336        }
1337
1338        @Override
1339        public void setCallback(Callback callback, Handler handler) {
1340            mCallback = callback;
1341            if (callback == null) {
1342                // There's nothing to unregister on API < 18 since media buttons
1343                // all go through the media button receiver
1344                if (android.os.Build.VERSION.SDK_INT >= 18) {
1345                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1346                }
1347                if (android.os.Build.VERSION.SDK_INT >= 19) {
1348                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1349                }
1350            } else {
1351                if (handler == null) {
1352                    handler = new Handler();
1353                }
1354                synchronized (mLock) {
1355                    mHandler = new MessageHandler(handler.getLooper());
1356                }
1357                MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
1358                    @Override
1359                    public void onSetRating(Object ratingObj) {
1360                        postToHandler(MessageHandler.MSG_RATE,
1361                                RatingCompat.fromRating(ratingObj));
1362                    }
1363
1364                    @Override
1365                    public void onSeekTo(long pos) {
1366                        postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1367                    }
1368                };
1369                if (android.os.Build.VERSION.SDK_INT >= 18) {
1370                    Object onPositionUpdateObj = MediaSessionCompatApi18
1371                            .createPlaybackPositionUpdateListener(cb19);
1372                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1373                            onPositionUpdateObj);
1374                }
1375                if (android.os.Build.VERSION.SDK_INT >= 19) {
1376                    Object onMetadataUpdateObj = MediaSessionCompatApi19
1377                            .createMetadataUpdateListener(cb19);
1378                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1379                            onMetadataUpdateObj);
1380                }
1381            }
1382        }
1383
1384        private void postToHandler(int what) {
1385            postToHandler(what, null);
1386        }
1387
1388        private void postToHandler(int what, Object obj) {
1389            postToHandler(what, obj, null);
1390        }
1391
1392        private void postToHandler(int what, Object obj, Bundle extras) {
1393            synchronized (mLock) {
1394                if (mHandler != null) {
1395                    mHandler.post(what, obj, extras);
1396                }
1397            }
1398        }
1399
1400        @Override
1401        public void setFlags(@SessionFlags int flags) {
1402            synchronized (mLock) {
1403                mFlags = flags;
1404            }
1405            update();
1406        }
1407
1408        @Override
1409        public void setPlaybackToLocal(int stream) {
1410            if (mVolumeProvider != null) {
1411                mVolumeProvider.setCallback(null);
1412            }
1413            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1414            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1415                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1416                    mAudioManager.getStreamMaxVolume(mLocalStream),
1417                    mAudioManager.getStreamVolume(mLocalStream));
1418            sendVolumeInfoChanged(info);
1419        }
1420
1421        @Override
1422        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1423            if (volumeProvider == null) {
1424                throw new IllegalArgumentException("volumeProvider may not be null");
1425            }
1426            if (mVolumeProvider != null) {
1427                mVolumeProvider.setCallback(null);
1428            }
1429            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1430            mVolumeProvider = volumeProvider;
1431            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1432                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1433                    mVolumeProvider.getCurrentVolume());
1434            sendVolumeInfoChanged(info);
1435
1436            volumeProvider.setCallback(mVolumeCallback);
1437        }
1438
1439        @Override
1440        public void setActive(boolean active) {
1441            if (active == mIsActive) {
1442                return;
1443            }
1444            mIsActive = active;
1445            if (update()) {
1446                setMetadata(mMetadata);
1447                setPlaybackState(mState);
1448            }
1449        }
1450
1451        @Override
1452        public boolean isActive() {
1453            return mIsActive;
1454        }
1455
1456        @Override
1457        public void sendSessionEvent(String event, Bundle extras) {
1458            sendEvent(event, extras);
1459        }
1460
1461        @Override
1462        public void release() {
1463            mIsActive = false;
1464            mDestroyed = true;
1465            update();
1466            sendSessionDestroyed();
1467        }
1468
1469        @Override
1470        public Token getSessionToken() {
1471            return mToken;
1472        }
1473
1474        @Override
1475        public void setPlaybackState(PlaybackStateCompat state) {
1476            synchronized (mLock) {
1477                mState = state;
1478            }
1479            sendState(state);
1480            if (!mIsActive) {
1481                // Don't set the state until after the RCC is registered
1482                return;
1483            }
1484            if (state == null) {
1485                if (android.os.Build.VERSION.SDK_INT >= 14) {
1486                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1487                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0);
1488                }
1489            } else {
1490                // Set state
1491                if (android.os.Build.VERSION.SDK_INT >= 18) {
1492                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1493                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1494                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1495                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
1496                }
1497
1498                // Set transport control flags
1499                if (android.os.Build.VERSION.SDK_INT >= 19) {
1500                    MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions());
1501                } else if (android.os.Build.VERSION.SDK_INT >= 18) {
1502                    MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions());
1503                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1504                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions());
1505                }
1506            }
1507        }
1508
1509        @Override
1510        public void setMetadata(MediaMetadataCompat metadata) {
1511            if (metadata != null) {
1512                // Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the
1513                // metadata if necessary. Bitmaps can be scaled down if they are large.
1514                metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build();
1515            }
1516
1517            synchronized (mLock) {
1518                mMetadata = metadata;
1519            }
1520            sendMetadata(metadata);
1521            if (!mIsActive) {
1522                // Don't set metadata until after the rcc has been registered
1523                return;
1524            }
1525            if (android.os.Build.VERSION.SDK_INT >= 19) {
1526                MediaSessionCompatApi19.setMetadata(mRccObj,
1527                        metadata == null ? null : metadata.getBundle(),
1528                        mState == null ? 0 : mState.getActions());
1529            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1530                MediaSessionCompatApi14.setMetadata(mRccObj,
1531                        metadata == null ? null : metadata.getBundle());
1532            }
1533        }
1534
1535        @Override
1536        public void setSessionActivity(PendingIntent pi) {
1537            synchronized (mLock) {
1538                mSessionActivity = pi;
1539            }
1540        }
1541
1542        @Override
1543        public void setMediaButtonReceiver(PendingIntent mbr) {
1544            // Do nothing, changing this is not supported before API 21.
1545        }
1546
1547        @Override
1548        public void setQueue(List<QueueItem> queue) {
1549            mQueue = queue;
1550            sendQueue(queue);
1551        }
1552
1553        @Override
1554        public void setQueueTitle(CharSequence title) {
1555            mQueueTitle = title;
1556            sendQueueTitle(title);
1557        }
1558
1559        @Override
1560        public Object getMediaSession() {
1561            return null;
1562        }
1563
1564        @Override
1565        public Object getRemoteControlClient() {
1566            return mRccObj;
1567        }
1568
1569        @Override
1570        public String getCallingPackage() {
1571            return null;
1572        }
1573
1574        @Override
1575        public void setRatingType(@RatingCompat.Style int type) {
1576            mRatingType = type;
1577        }
1578
1579        @Override
1580        public void setExtras(Bundle extras) {
1581            mExtras = extras;
1582            sendExtras(extras);
1583        }
1584
1585        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
1586        private boolean update() {
1587            boolean registeredRcc = false;
1588            if (mIsActive) {
1589                // Register a MBR if it's supported, unregister it
1590                // if support was removed.
1591                if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1592                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1593                        MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1594                                mMediaButtonReceiverIntent,
1595                                mMediaButtonReceiverComponentName);
1596                    } else {
1597                        AudioManager am = (AudioManager) mContext.getSystemService(
1598                                Context.AUDIO_SERVICE);
1599                        am.registerMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1600                    }
1601                    mIsMbrRegistered = true;
1602                } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1603                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1604                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1605                                mMediaButtonReceiverIntent,
1606                                mMediaButtonReceiverComponentName);
1607                    } else {
1608                        AudioManager am = (AudioManager) mContext.getSystemService(
1609                                Context.AUDIO_SERVICE);
1610                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1611                    }
1612                    mIsMbrRegistered = false;
1613                }
1614                // On API 14+ register a RCC if it's supported, unregister it if
1615                // not.
1616                if (android.os.Build.VERSION.SDK_INT >= 14) {
1617                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1618                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1619                        mIsRccRegistered = true;
1620                        registeredRcc = true;
1621                    } else if (mIsRccRegistered
1622                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1623                        // RCC keeps the state while the system resets its state internally when
1624                        // we register RCC. Reset the state so that the states in RCC and the system
1625                        // are in sync when we re-register the RCC.
1626                        MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1627                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1628                        mIsRccRegistered = false;
1629                    }
1630                }
1631            } else {
1632                // When inactive remove any registered components.
1633                if (mIsMbrRegistered) {
1634                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1635                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1636                                mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
1637                    } else {
1638                        AudioManager am = (AudioManager) mContext.getSystemService(
1639                                Context.AUDIO_SERVICE);
1640                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1641                    }
1642                    mIsMbrRegistered = false;
1643                }
1644                if (mIsRccRegistered) {
1645                    // RCC keeps the state while the system resets its state internally when
1646                    // we register RCC. Reset the state so that the states in RCC and the system
1647                    // are in sync when we re-register the RCC.
1648                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1649                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1650                    mIsRccRegistered = false;
1651                }
1652            }
1653            return registeredRcc;
1654        }
1655
1656        private void adjustVolume(int direction, int flags) {
1657            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1658                if (mVolumeProvider != null) {
1659                    mVolumeProvider.onAdjustVolume(direction);
1660                }
1661            } else {
1662                mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
1663            }
1664        }
1665
1666        private void setVolumeTo(int value, int flags) {
1667            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1668                if (mVolumeProvider != null) {
1669                    mVolumeProvider.onSetVolumeTo(value);
1670                }
1671            } else {
1672                mAudioManager.setStreamVolume(mLocalStream, value, flags);
1673            }
1674        }
1675
1676        private PlaybackStateCompat getStateWithUpdatedPosition() {
1677            PlaybackStateCompat state;
1678            long duration = -1;
1679            synchronized (mLock) {
1680                state = mState;
1681                if (mMetadata != null
1682                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1683                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1684                }
1685            }
1686
1687            PlaybackStateCompat result = null;
1688            if (state != null) {
1689                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1690                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1691                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1692                    long updateTime = state.getLastPositionUpdateTime();
1693                    long currentTime = SystemClock.elapsedRealtime();
1694                    if (updateTime > 0) {
1695                        long position = (long) (state.getPlaybackSpeed()
1696                                * (currentTime - updateTime)) + state.getPosition();
1697                        if (duration >= 0 && position > duration) {
1698                            position = duration;
1699                        } else if (position < 0) {
1700                            position = 0;
1701                        }
1702                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1703                                state);
1704                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1705                                currentTime);
1706                        result = builder.build();
1707                    }
1708                }
1709            }
1710            return result == null ? state : result;
1711        }
1712
1713        private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1714            int size = mControllerCallbacks.beginBroadcast();
1715            for (int i = size - 1; i >= 0; i--) {
1716                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1717                try {
1718                    cb.onVolumeInfoChanged(info);
1719                } catch (RemoteException e) {
1720                }
1721            }
1722            mControllerCallbacks.finishBroadcast();
1723        }
1724
1725        private void sendSessionDestroyed() {
1726            int size = mControllerCallbacks.beginBroadcast();
1727            for (int i = size - 1; i >= 0; i--) {
1728                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1729                try {
1730                    cb.onSessionDestroyed();
1731                } catch (RemoteException e) {
1732                }
1733            }
1734            mControllerCallbacks.finishBroadcast();
1735            mControllerCallbacks.kill();
1736        }
1737
1738        private void sendEvent(String event, Bundle extras) {
1739            int size = mControllerCallbacks.beginBroadcast();
1740            for (int i = size - 1; i >= 0; i--) {
1741                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1742                try {
1743                    cb.onEvent(event, extras);
1744                } catch (RemoteException e) {
1745                }
1746            }
1747            mControllerCallbacks.finishBroadcast();
1748        }
1749
1750        private void sendState(PlaybackStateCompat state) {
1751            int size = mControllerCallbacks.beginBroadcast();
1752            for (int i = size - 1; i >= 0; i--) {
1753                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1754                try {
1755                    cb.onPlaybackStateChanged(state);
1756                } catch (RemoteException e) {
1757                }
1758            }
1759            mControllerCallbacks.finishBroadcast();
1760        }
1761
1762        private void sendMetadata(MediaMetadataCompat metadata) {
1763            int size = mControllerCallbacks.beginBroadcast();
1764            for (int i = size - 1; i >= 0; i--) {
1765                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1766                try {
1767                    cb.onMetadataChanged(metadata);
1768                } catch (RemoteException e) {
1769                }
1770            }
1771            mControllerCallbacks.finishBroadcast();
1772        }
1773
1774        private void sendQueue(List<QueueItem> queue) {
1775            int size = mControllerCallbacks.beginBroadcast();
1776            for (int i = size - 1; i >= 0; i--) {
1777                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1778                try {
1779                    cb.onQueueChanged(queue);
1780                } catch (RemoteException e) {
1781                }
1782            }
1783            mControllerCallbacks.finishBroadcast();
1784        }
1785
1786        private void sendQueueTitle(CharSequence queueTitle) {
1787            int size = mControllerCallbacks.beginBroadcast();
1788            for (int i = size - 1; i >= 0; i--) {
1789                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1790                try {
1791                    cb.onQueueTitleChanged(queueTitle);
1792                } catch (RemoteException e) {
1793                }
1794            }
1795            mControllerCallbacks.finishBroadcast();
1796        }
1797
1798        private void sendExtras(Bundle extras) {
1799            int size = mControllerCallbacks.beginBroadcast();
1800            for (int i = size - 1; i >= 0; i--) {
1801                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1802                try {
1803                    cb.onExtrasChanged(extras);
1804                } catch (RemoteException e) {
1805                }
1806            }
1807            mControllerCallbacks.finishBroadcast();
1808        }
1809
1810        class MediaSessionStub extends IMediaSession.Stub {
1811            @Override
1812            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
1813                postToHandler(MessageHandler.MSG_COMMAND,
1814                        new Command(command, args, cb.mResultReceiver));
1815            }
1816
1817            @Override
1818            public boolean sendMediaButton(KeyEvent mediaButton) {
1819                boolean handlesMediaButtons =
1820                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
1821                if (handlesMediaButtons) {
1822                    postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
1823                }
1824                return handlesMediaButtons;
1825            }
1826
1827            @Override
1828            public void registerCallbackListener(IMediaControllerCallback cb) {
1829                // If this session is already destroyed tell the caller and
1830                // don't add them.
1831                if (mDestroyed) {
1832                    try {
1833                        cb.onSessionDestroyed();
1834                    } catch (Exception e) {
1835                        // ignored
1836                    }
1837                    return;
1838                }
1839                mControllerCallbacks.register(cb);
1840            }
1841
1842            @Override
1843            public void unregisterCallbackListener(IMediaControllerCallback cb) {
1844                mControllerCallbacks.unregister(cb);
1845            }
1846
1847            @Override
1848            public String getPackageName() {
1849                // mPackageName is final so doesn't need synchronize block
1850                return mPackageName;
1851            }
1852
1853            @Override
1854            public String getTag() {
1855                // mTag is final so doesn't need synchronize block
1856                return mTag;
1857            }
1858
1859            @Override
1860            public PendingIntent getLaunchPendingIntent() {
1861                synchronized (mLock) {
1862                    return mSessionActivity;
1863                }
1864            }
1865
1866            @Override
1867            @SessionFlags
1868            public long getFlags() {
1869                synchronized (mLock) {
1870                    return mFlags;
1871                }
1872            }
1873
1874            @Override
1875            public ParcelableVolumeInfo getVolumeAttributes() {
1876                int controlType;
1877                int max;
1878                int current;
1879                int stream;
1880                int volumeType;
1881                synchronized (mLock) {
1882                    volumeType = mVolumeType;
1883                    stream = mLocalStream;
1884                    VolumeProviderCompat vp = mVolumeProvider;
1885                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1886                        controlType = vp.getVolumeControl();
1887                        max = vp.getMaxVolume();
1888                        current = vp.getCurrentVolume();
1889                    } else {
1890                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
1891                        max = mAudioManager.getStreamMaxVolume(stream);
1892                        current = mAudioManager.getStreamVolume(stream);
1893                    }
1894                }
1895                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
1896            }
1897
1898            @Override
1899            public void adjustVolume(int direction, int flags, String packageName) {
1900                MediaSessionImplBase.this.adjustVolume(direction, flags);
1901            }
1902
1903            @Override
1904            public void setVolumeTo(int value, int flags, String packageName) {
1905                MediaSessionImplBase.this.setVolumeTo(value, flags);
1906            }
1907
1908            @Override
1909            public void prepare() throws RemoteException {
1910                postToHandler(MessageHandler.MSG_PREPARE);
1911            }
1912
1913            @Override
1914            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1915                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
1916            }
1917
1918            @Override
1919            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
1920                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
1921            }
1922
1923            @Override
1924            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
1925                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
1926            }
1927
1928            @Override
1929            public void play() throws RemoteException {
1930                postToHandler(MessageHandler.MSG_PLAY);
1931            }
1932
1933            @Override
1934            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1935                postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
1936            }
1937
1938            @Override
1939            public void playFromSearch(String query, Bundle extras) throws RemoteException {
1940                postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
1941            }
1942
1943            @Override
1944            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1945                postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
1946            }
1947
1948            @Override
1949            public void skipToQueueItem(long id) {
1950                postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
1951            }
1952
1953            @Override
1954            public void pause() throws RemoteException {
1955                postToHandler(MessageHandler.MSG_PAUSE);
1956            }
1957
1958            @Override
1959            public void stop() throws RemoteException {
1960                postToHandler(MessageHandler.MSG_STOP);
1961            }
1962
1963            @Override
1964            public void next() throws RemoteException {
1965                postToHandler(MessageHandler.MSG_NEXT);
1966            }
1967
1968            @Override
1969            public void previous() throws RemoteException {
1970                postToHandler(MessageHandler.MSG_PREVIOUS);
1971            }
1972
1973            @Override
1974            public void fastForward() throws RemoteException {
1975                postToHandler(MessageHandler.MSG_FAST_FORWARD);
1976            }
1977
1978            @Override
1979            public void rewind() throws RemoteException {
1980                postToHandler(MessageHandler.MSG_REWIND);
1981            }
1982
1983            @Override
1984            public void seekTo(long pos) throws RemoteException {
1985                postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1986            }
1987
1988            @Override
1989            public void rate(RatingCompat rating) throws RemoteException {
1990                postToHandler(MessageHandler.MSG_RATE, rating);
1991            }
1992
1993            @Override
1994            public void sendCustomAction(String action, Bundle args)
1995                    throws RemoteException {
1996                postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
1997            }
1998
1999            @Override
2000            public MediaMetadataCompat getMetadata() {
2001                return mMetadata;
2002            }
2003
2004            @Override
2005            public PlaybackStateCompat getPlaybackState() {
2006                return getStateWithUpdatedPosition();
2007            }
2008
2009            @Override
2010            public List<QueueItem> getQueue() {
2011                synchronized (mLock) {
2012                    return mQueue;
2013                }
2014            }
2015
2016            @Override
2017            public CharSequence getQueueTitle() {
2018                return mQueueTitle;
2019            }
2020
2021            @Override
2022            public Bundle getExtras() {
2023                synchronized (mLock) {
2024                    return mExtras;
2025                }
2026            }
2027
2028            @Override
2029            @RatingCompat.Style
2030            public int getRatingType() {
2031                return mRatingType;
2032            }
2033
2034            @Override
2035            public boolean isTransportControlEnabled() {
2036                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
2037            }
2038        }
2039
2040        private static final class Command {
2041            public final String command;
2042            public final Bundle extras;
2043            public final ResultReceiver stub;
2044
2045            public Command(String command, Bundle extras, ResultReceiver stub) {
2046                this.command = command;
2047                this.extras = extras;
2048                this.stub = stub;
2049            }
2050        }
2051
2052        private class MessageHandler extends Handler {
2053
2054            private static final int MSG_COMMAND = 1;
2055            private static final int MSG_ADJUST_VOLUME = 2;
2056            private static final int MSG_PREPARE = 3;
2057            private static final int MSG_PREPARE_MEDIA_ID = 4;
2058            private static final int MSG_PREPARE_SEARCH = 5;
2059            private static final int MSG_PREPARE_URI = 6;
2060            private static final int MSG_PLAY = 7;
2061            private static final int MSG_PLAY_MEDIA_ID = 8;
2062            private static final int MSG_PLAY_SEARCH = 9;
2063            private static final int MSG_PLAY_URI = 10;
2064            private static final int MSG_SKIP_TO_ITEM = 11;
2065            private static final int MSG_PAUSE = 12;
2066            private static final int MSG_STOP = 13;
2067            private static final int MSG_NEXT = 14;
2068            private static final int MSG_PREVIOUS = 15;
2069            private static final int MSG_FAST_FORWARD = 16;
2070            private static final int MSG_REWIND = 17;
2071            private static final int MSG_SEEK_TO = 18;
2072            private static final int MSG_RATE = 19;
2073            private static final int MSG_CUSTOM_ACTION = 20;
2074            private static final int MSG_MEDIA_BUTTON = 21;
2075            private static final int MSG_SET_VOLUME = 22;
2076
2077            // KeyEvent constants only available on API 11+
2078            private static final int KEYCODE_MEDIA_PAUSE = 127;
2079            private static final int KEYCODE_MEDIA_PLAY = 126;
2080
2081            public MessageHandler(Looper looper) {
2082                super(looper);
2083            }
2084
2085            public void post(int what, Object obj, Bundle bundle) {
2086                Message msg = obtainMessage(what, obj);
2087                msg.setData(bundle);
2088                msg.sendToTarget();
2089            }
2090
2091            public void post(int what, Object obj) {
2092                obtainMessage(what, obj).sendToTarget();
2093            }
2094
2095            public void post(int what) {
2096                post(what, null);
2097            }
2098
2099            public void post(int what, Object obj, int arg1) {
2100                obtainMessage(what, arg1, 0, obj).sendToTarget();
2101            }
2102
2103            @Override
2104            public void handleMessage(Message msg) {
2105                MediaSessionCompat.Callback cb = mCallback;
2106                if (cb == null) {
2107                    return;
2108                }
2109                switch (msg.what) {
2110                    case MSG_COMMAND:
2111                        Command cmd = (Command) msg.obj;
2112                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2113                        break;
2114                    case MSG_MEDIA_BUTTON:
2115                        KeyEvent keyEvent = (KeyEvent) msg.obj;
2116                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2117                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2118                        // Let the Callback handle events first before using the default behavior
2119                        if (!cb.onMediaButtonEvent(intent)) {
2120                            onMediaButtonEvent(keyEvent, cb);
2121                        }
2122                        break;
2123                    case MSG_PREPARE:
2124                        cb.onPrepare();
2125                        break;
2126                    case MSG_PREPARE_MEDIA_ID:
2127                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2128                        break;
2129                    case MSG_PREPARE_SEARCH:
2130                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2131                        break;
2132                    case MSG_PREPARE_URI:
2133                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2134                        break;
2135                    case MSG_PLAY:
2136                        cb.onPlay();
2137                        break;
2138                    case MSG_PLAY_MEDIA_ID:
2139                        cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2140                        break;
2141                    case MSG_PLAY_SEARCH:
2142                        cb.onPlayFromSearch((String) msg.obj, msg.getData());
2143                        break;
2144                    case MSG_PLAY_URI:
2145                        cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2146                        break;
2147                    case MSG_SKIP_TO_ITEM:
2148                        cb.onSkipToQueueItem((Long) msg.obj);
2149                        break;
2150                    case MSG_PAUSE:
2151                        cb.onPause();
2152                        break;
2153                    case MSG_STOP:
2154                        cb.onStop();
2155                        break;
2156                    case MSG_NEXT:
2157                        cb.onSkipToNext();
2158                        break;
2159                    case MSG_PREVIOUS:
2160                        cb.onSkipToPrevious();
2161                        break;
2162                    case MSG_FAST_FORWARD:
2163                        cb.onFastForward();
2164                        break;
2165                    case MSG_REWIND:
2166                        cb.onRewind();
2167                        break;
2168                    case MSG_SEEK_TO:
2169                        cb.onSeekTo((Long) msg.obj);
2170                        break;
2171                    case MSG_RATE:
2172                        cb.onSetRating((RatingCompat) msg.obj);
2173                        break;
2174                    case MSG_CUSTOM_ACTION:
2175                        cb.onCustomAction((String) msg.obj, msg.getData());
2176                        break;
2177                    case MSG_ADJUST_VOLUME:
2178                        adjustVolume((int) msg.obj, 0);
2179                        break;
2180                    case MSG_SET_VOLUME:
2181                        setVolumeTo((int) msg.obj, 0);
2182                        break;
2183                }
2184            }
2185
2186            private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2187                if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2188                    return;
2189                }
2190                long validActions = mState == null ? 0 : mState.getActions();
2191                switch (ke.getKeyCode()) {
2192                    // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2193                    case KEYCODE_MEDIA_PLAY:
2194                        if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2195                            cb.onPlay();
2196                        }
2197                        break;
2198                    // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2199                    case KEYCODE_MEDIA_PAUSE:
2200                        if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2201                            cb.onPause();
2202                        }
2203                        break;
2204                    case KeyEvent.KEYCODE_MEDIA_NEXT:
2205                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2206                            cb.onSkipToNext();
2207                        }
2208                        break;
2209                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2210                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2211                            cb.onSkipToPrevious();
2212                        }
2213                        break;
2214                    case KeyEvent.KEYCODE_MEDIA_STOP:
2215                        if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2216                            cb.onStop();
2217                        }
2218                        break;
2219                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2220                        if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2221                            cb.onFastForward();
2222                        }
2223                        break;
2224                    case KeyEvent.KEYCODE_MEDIA_REWIND:
2225                        if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2226                            cb.onRewind();
2227                        }
2228                        break;
2229                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2230                    case KeyEvent.KEYCODE_HEADSETHOOK:
2231                        boolean isPlaying = mState != null
2232                                && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2233                        boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2234                                | PlaybackStateCompat.ACTION_PLAY)) != 0;
2235                        boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2236                                | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2237                        if (isPlaying && canPause) {
2238                            cb.onPause();
2239                        } else if (!isPlaying && canPlay) {
2240                            cb.onPlay();
2241                        }
2242                        break;
2243                }
2244            }
2245        }
2246    }
2247
2248    static class MediaSessionImplApi21 implements MediaSessionImpl {
2249        private final Object mSessionObj;
2250        private final Token mToken;
2251
2252        private PendingIntent mMediaButtonIntent;
2253
2254        public MediaSessionImplApi21(Context context, String tag) {
2255            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
2256            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2257        }
2258
2259        public MediaSessionImplApi21(Object mediaSession) {
2260            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
2261            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2262        }
2263
2264        @Override
2265        public void setCallback(Callback callback, Handler handler) {
2266            MediaSessionCompatApi21.setCallback(mSessionObj,
2267                    callback == null ? null : callback.mCallbackObj, handler);
2268        }
2269
2270        @Override
2271        public void setFlags(@SessionFlags int flags) {
2272            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
2273        }
2274
2275        @Override
2276        public void setPlaybackToLocal(int stream) {
2277            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
2278        }
2279
2280        @Override
2281        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
2282            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
2283                    volumeProvider.getVolumeProvider());
2284        }
2285
2286        @Override
2287        public void setActive(boolean active) {
2288            MediaSessionCompatApi21.setActive(mSessionObj, active);
2289        }
2290
2291        @Override
2292        public boolean isActive() {
2293            return MediaSessionCompatApi21.isActive(mSessionObj);
2294        }
2295
2296        @Override
2297        public void sendSessionEvent(String event, Bundle extras) {
2298            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
2299        }
2300
2301        @Override
2302        public void release() {
2303            MediaSessionCompatApi21.release(mSessionObj);
2304        }
2305
2306        @Override
2307        public Token getSessionToken() {
2308            return mToken;
2309        }
2310
2311        @Override
2312        public void setPlaybackState(PlaybackStateCompat state) {
2313            MediaSessionCompatApi21.setPlaybackState(mSessionObj,
2314                    state == null ? null : state.getPlaybackState());
2315        }
2316
2317        @Override
2318        public void setMetadata(MediaMetadataCompat metadata) {
2319            MediaSessionCompatApi21.setMetadata(mSessionObj,
2320                    metadata == null ? null : metadata.getMediaMetadata());
2321        }
2322
2323        @Override
2324        public void setSessionActivity(PendingIntent pi) {
2325            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
2326        }
2327
2328        @Override
2329        public void setMediaButtonReceiver(PendingIntent mbr) {
2330            mMediaButtonIntent = mbr;
2331            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
2332        }
2333
2334        @Override
2335        public void setQueue(List<QueueItem> queue) {
2336            List<Object> queueObjs = null;
2337            if (queue != null) {
2338                queueObjs = new ArrayList<>();
2339                for (QueueItem item : queue) {
2340                    queueObjs.add(item.getQueueItem());
2341                }
2342            }
2343            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
2344        }
2345
2346        @Override
2347        public void setQueueTitle(CharSequence title) {
2348            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
2349        }
2350
2351        @Override
2352        public void setRatingType(@RatingCompat.Style int type) {
2353            if (android.os.Build.VERSION.SDK_INT < 22) {
2354                // TODO figure out 21 implementation
2355            } else {
2356                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
2357            }
2358        }
2359
2360        @Override
2361        public void setExtras(Bundle extras) {
2362            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
2363        }
2364
2365        @Override
2366        public Object getMediaSession() {
2367            return mSessionObj;
2368        }
2369
2370        @Override
2371        public Object getRemoteControlClient() {
2372            return null;
2373        }
2374
2375        @Override
2376        public String getCallingPackage() {
2377            if (android.os.Build.VERSION.SDK_INT < 24) {
2378                return null;
2379            } else {
2380                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
2381            }
2382        }
2383    }
2384}
2385