MediaSessionCompat.java revision c8027dbaff3a208246b849a56e828a38b26edb2b
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.media.session;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.app.Activity;
22import android.app.PendingIntent;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.graphics.Bitmap;
28import android.media.AudioManager;
29import android.media.MediaMetadataEditor;
30import android.media.MediaMetadataRetriever;
31import android.media.Rating;
32import android.media.RemoteControlClient;
33import android.net.Uri;
34import android.os.BadParcelableException;
35import android.os.Build;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.Looper;
40import android.os.Message;
41import android.os.Parcel;
42import android.os.Parcelable;
43import android.os.RemoteCallbackList;
44import android.os.RemoteException;
45import android.os.ResultReceiver;
46import android.os.SystemClock;
47import android.support.annotation.IntDef;
48import android.support.annotation.RequiresApi;
49import android.support.annotation.RestrictTo;
50import android.support.v4.app.BundleCompat;
51import android.support.v4.media.MediaDescriptionCompat;
52import android.support.v4.media.MediaMetadataCompat;
53import android.support.v4.media.RatingCompat;
54import android.support.v4.media.VolumeProviderCompat;
55import android.text.TextUtils;
56import android.util.Log;
57import android.util.TypedValue;
58import android.view.KeyEvent;
59
60import java.lang.annotation.Retention;
61import java.lang.annotation.RetentionPolicy;
62import java.lang.ref.WeakReference;
63import java.util.ArrayList;
64import java.util.List;
65
66/**
67 * Allows interaction with media controllers, volume keys, media buttons, and
68 * transport controls.
69 * <p>
70 * A MediaSession should be created when an app wants to publish media playback
71 * information or handle media keys. In general an app only needs one session
72 * for all playback, though multiple sessions can be created to provide finer
73 * grain controls of media.
74 * <p>
75 * Once a session is created the owner of the session may pass its
76 * {@link #getSessionToken() session token} to other processes to allow them to
77 * create a {@link MediaControllerCompat} to interact with the session.
78 * <p>
79 * To receive commands, media keys, and other events a {@link Callback} must be
80 * set with {@link #setCallback(Callback)}.
81 * <p>
82 * When an app is finished performing playback it must call {@link #release()}
83 * to clean up the session and notify any controllers.
84 * <p>
85 * MediaSessionCompat objects are not thread safe and all calls should be made
86 * from the same thread.
87 * <p>
88 * This is a helper for accessing features in
89 * {@link android.media.session.MediaSession} introduced after API level 4 in a
90 * backwards compatible fashion.
91 *
92 * <div class="special reference">
93 * <h3>Developer Guides</h3>
94 * <p>For information about building your media application, read the
95 * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
96 * </div>
97 */
98public class MediaSessionCompat {
99    static final String TAG = "MediaSessionCompat";
100
101    private final MediaSessionImpl mImpl;
102    private final MediaControllerCompat mController;
103    private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
104
105    /**
106     * @hide
107     */
108    @RestrictTo(LIBRARY_GROUP)
109    @IntDef(flag=true, value={
110            FLAG_HANDLES_MEDIA_BUTTONS,
111            FLAG_HANDLES_TRANSPORT_CONTROLS,
112            FLAG_HANDLES_QUEUE_COMMANDS })
113    @Retention(RetentionPolicy.SOURCE)
114    public @interface SessionFlags {}
115
116    /**
117     * Set this flag on the session to indicate that it can handle media button
118     * events.
119     */
120    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
121
122    /**
123     * Set this flag on the session to indicate that it handles transport
124     * control commands through its {@link Callback}.
125     */
126    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
127
128    /**
129     * Set this flag on the session to indicate that it handles queue
130     * management commands through its {@link Callback}.
131     */
132    public static final int FLAG_HANDLES_QUEUE_COMMANDS = 1 << 2;
133
134    /**
135     * Predefined custom action to flag the media that is currently playing as inappropriate.
136     *
137     * @see Callback#onCustomAction
138     */
139    public static final String ACTION_FLAG_AS_INAPPROPRIATE =
140            "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
141
142    /**
143     * Predefined custom action to skip the advertisement that is currently playing.
144     *
145     * @see Callback#onCustomAction
146     */
147    public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
148
149    /**
150     * Predefined custom action to follow an artist, album, or playlist. The extra bundle must have
151     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the follow action. The
152     * bundle can also have an optional string argument,
153     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to follow (e.g., the
154     * name of the artist to follow). If this argument is omitted, the currently playing media will
155     * be the target of the action. Thus, the session must perform the follow action with the
156     * current metadata. If there's no specified attribute in the current metadata, the controller
157     * must not omit this argument.
158     *
159     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE
160     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE
161     * @see Callback#onCustomAction
162     */
163    public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
164
165    /**
166     * Predefined custom action to unfollow an artist, album, or playlist. The extra bundle must
167     * have {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the unfollow action.
168     * The bundle can also have an optional string argument,
169     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to unfollow (e.g., the
170     * name of the artist to unfollow). If this argument is omitted, the currently playing media
171     * will be the target of the action. Thus, the session must perform the unfollow action with the
172     * current metadata. If there's no specified attribute in the current metadata, the controller
173     * must not omit this argument.
174     *
175     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE
176     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE
177     * @see Callback#onCustomAction
178     */
179    public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
180
181    /**
182     * Argument for use with {@link #ACTION_FOLLOW} and {@link #ACTION_UNFOLLOW} indicating the
183     * media attribute of the follow/unfollow action. It should be one of the following:
184     * <ul>
185     * <li>{@link #MEDIA_ATTRIBUTE_ARTIST}</li>
186     * <li>{@link #MEDIA_ATTRIBUTE_PLAYLIST}</li>
187     * <li>{@link #MEDIA_ATTRIBUTE_ALBUM}</li>
188     * </ul>
189     *
190     * @see #ACTION_FOLLOW
191     * @see #ACTION_UNFOLLOW
192     */
193    public static final String ACTION_ARGUMENT_MEDIA_ATTRIBUTE =
194            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE";
195
196    /**
197     * String argument for use with {@link #ACTION_FOLLOW} and {@link #ACTION_UNFOLLOW} indicating
198     * the value of the media attribute of the follow/unfollow action (e.g., the name of the artist
199     * to follow).
200     *
201     * @see #ACTION_FOLLOW
202     * @see #ACTION_UNFOLLOW
203     */
204    public static final String ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE =
205            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
206
207    /**
208     * The media attribute of the follow action which indicates that the target of the action is an
209     * artist.
210     *
211     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
212     */
213    public static final int MEDIA_ATTRIBUTE_ARTIST = 0;
214
215    /**
216     * The media attribute of the follow action which indicates that the target of the action is an
217     * album.
218     *
219     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
220     */
221    public static final int MEDIA_ATTRIBUTE_ALBUM = 1;
222
223    /**
224     * The media attribute of the follow action which indicates that the target of the action is a
225     * playlist.
226     *
227     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
228     */
229    public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2;
230
231    /**
232     * Custom action to invoke playFromUri() for the forward compatibility.
233     */
234    static final String ACTION_PLAY_FROM_URI =
235            "android.support.v4.media.session.action.PLAY_FROM_URI";
236
237    /**
238     * Custom action to invoke prepare() for the forward compatibility.
239     */
240    static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
241
242    /**
243     * Custom action to invoke prepareFromMediaId() for the forward compatibility.
244     */
245    static final String ACTION_PREPARE_FROM_MEDIA_ID =
246            "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
247
248    /**
249     * Custom action to invoke prepareFromSearch() for the forward compatibility.
250     */
251    static final String ACTION_PREPARE_FROM_SEARCH =
252            "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
253
254    /**
255     * Custom action to invoke prepareFromUri() for the forward compatibility.
256     */
257    static final String ACTION_PREPARE_FROM_URI =
258            "android.support.v4.media.session.action.PREPARE_FROM_URI";
259
260    /**
261     * Custom action to invoke setCaptioningEnabled() for the forward compatibility.
262     */
263    static final String ACTION_SET_CAPTIONING_ENABLED =
264            "android.support.v4.media.session.action.SET_CAPTIONING_ENABLED";
265
266    /**
267     * Custom action to invoke setRepeatMode() for the forward compatibility.
268     */
269    static final String ACTION_SET_REPEAT_MODE =
270            "android.support.v4.media.session.action.SET_REPEAT_MODE";
271
272    /**
273     * Custom action to invoke setShuffleModeEnabled() for the forward compatibility.
274     */
275    static final String ACTION_SET_SHUFFLE_MODE_ENABLED =
276            "android.support.v4.media.session.action.SET_SHUFFLE_MODE_ENABLED";
277
278    /**
279     * Custom action to invoke setShuffleMode() for the forward compatibility.
280     */
281    static final String ACTION_SET_SHUFFLE_MODE =
282            "android.support.v4.media.session.action.SET_SHUFFLE_MODE";
283
284    /**
285     * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
286     */
287    static final String ACTION_ARGUMENT_MEDIA_ID =
288            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
289
290    /**
291     * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
292     */
293    static final String ACTION_ARGUMENT_QUERY =
294            "android.support.v4.media.session.action.ARGUMENT_QUERY";
295
296    /**
297     * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
298     * indicating URI to play.
299     */
300    static final String ACTION_ARGUMENT_URI =
301            "android.support.v4.media.session.action.ARGUMENT_URI";
302
303    /**
304     * Argument for use with various actions indicating extra bundle.
305     */
306    static final String ACTION_ARGUMENT_EXTRAS =
307            "android.support.v4.media.session.action.ARGUMENT_EXTRAS";
308
309    /**
310     * Argument for use with {@link #ACTION_SET_CAPTIONING_ENABLED} indicating whether captioning is
311     * enabled.
312     */
313    static final String ACTION_ARGUMENT_CAPTIONING_ENABLED =
314            "android.support.v4.media.session.action.ARGUMENT_CAPTIONING_ENABLED";
315
316    /**
317     * Argument for use with {@link #ACTION_SET_REPEAT_MODE} indicating repeat mode.
318     */
319    static final String ACTION_ARGUMENT_REPEAT_MODE =
320            "android.support.v4.media.session.action.ARGUMENT_REPEAT_MODE";
321
322    /**
323     * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE_ENABLED} indicating that shuffle mode
324     * is enabled.
325     */
326    static final String ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED =
327            "android.support.v4.media.session.action.ARGUMENT_SHUFFLE_MODE_ENABLED";
328
329    /**
330     * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE} indicating shuffle mode.
331     */
332    static final String ACTION_ARGUMENT_SHUFFLE_MODE =
333            "android.support.v4.media.session.action.ARGUMENT_SHUFFLE_MODE";
334
335    static final String EXTRA_BINDER = "android.support.v4.media.session.EXTRA_BINDER";
336
337    // Maximum size of the bitmap in dp.
338    private static final int MAX_BITMAP_SIZE_IN_DP = 320;
339
340    // Maximum size of the bitmap in px. It shouldn't be changed.
341    static int sMaxBitmapSize;
342
343    /**
344     * Creates a new session. You must call {@link #release()} when finished with the session.
345     * <p>
346     * The session will automatically be registered with the system but will not be published
347     * until {@link #setActive(boolean) setActive(true)} is called.
348     * </p><p>
349     * For API 20 or earlier, note that a media button receiver is required for handling
350     * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
351     * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more
352     * details.
353     * </p>
354     * @param context The context to use to create the session.
355     * @param tag A short name for debugging purposes.
356     */
357    public MediaSessionCompat(Context context, String tag) {
358        this(context, tag, null, null);
359    }
360
361    /**
362     * Creates a new session with a specified media button receiver (a component name and/or
363     * a pending intent). You must call {@link #release()} when finished with the session.
364     * <p>
365     * The session will automatically be registered with the system but will not be published
366     * until {@link #setActive(boolean) setActive(true)} is called.
367     * </p><p>
368     * For API 20 or earlier, note that a media button receiver is required for handling
369     * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
370     * {@link BroadcastReceiver} from your manifest if it's not specified. See
371     * {@link MediaButtonReceiver} for more details.
372     * </p>
373     * @param context The context to use to create the session.
374     * @param tag A short name for debugging purposes.
375     * @param mbrComponent The component name for your media button receiver.
376     * @param mbrIntent The PendingIntent for your receiver component that handles
377     *            media button events. This is optional and will be used on between
378     *            {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
379     *            {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the
380     *            component name.
381     */
382    public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent,
383            PendingIntent mbrIntent) {
384        if (context == null) {
385            throw new IllegalArgumentException("context must not be null");
386        }
387        if (TextUtils.isEmpty(tag)) {
388            throw new IllegalArgumentException("tag must not be null or empty");
389        }
390
391        if (mbrComponent == null) {
392            mbrComponent = MediaButtonReceiver.getMediaButtonReceiverComponent(context);
393            if (mbrComponent == null) {
394                Log.w(TAG, "Couldn't find a unique registered media button receiver in the "
395                        + "given context.");
396            }
397        }
398        if (mbrComponent != null && mbrIntent == null) {
399            // construct a PendingIntent for the media button
400            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
401            // the associated intent will be handled by the component being registered
402            mediaButtonIntent.setComponent(mbrComponent);
403            mbrIntent = PendingIntent.getBroadcast(context,
404                    0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
405        }
406        if (android.os.Build.VERSION.SDK_INT >= 21) {
407            mImpl = new MediaSessionImplApi21(context, tag);
408            // Set default callback to respond to controllers' extra binder requests.
409            setCallback(new Callback() {});
410            mImpl.setMediaButtonReceiver(mbrIntent);
411        } else if (android.os.Build.VERSION.SDK_INT >= 19) {
412            mImpl = new MediaSessionImplApi19(context, tag, mbrComponent, mbrIntent);
413        } else if (android.os.Build.VERSION.SDK_INT >= 18) {
414            mImpl = new MediaSessionImplApi18(context, tag, mbrComponent, mbrIntent);
415        } else {
416            mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent);
417        }
418        mController = new MediaControllerCompat(context, this);
419
420        if (sMaxBitmapSize == 0) {
421            sMaxBitmapSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
422                    MAX_BITMAP_SIZE_IN_DP, context.getResources().getDisplayMetrics());
423        }
424    }
425
426    private MediaSessionCompat(Context context, MediaSessionImpl impl) {
427        mImpl = impl;
428        if (android.os.Build.VERSION.SDK_INT >= 21) {
429            // Set default callback to respond to controllers' extra binder requests.
430            setCallback(new Callback() {});
431        }
432        mController = new MediaControllerCompat(context, this);
433    }
434
435    /**
436     * Add a callback to receive updates on for the MediaSession. This includes
437     * media button and volume events. The caller's thread will be used to post
438     * events.
439     *
440     * @param callback The callback object
441     */
442    public void setCallback(Callback callback) {
443        setCallback(callback, null);
444    }
445
446    /**
447     * Set the callback to receive updates for the MediaSession. This includes
448     * media button and volume events. Set the callback to null to stop
449     * receiving events.
450     *
451     * @param callback The callback to receive updates on.
452     * @param handler The handler that events should be posted on.
453     */
454    public void setCallback(Callback callback, Handler handler) {
455        mImpl.setCallback(callback, handler != null ? handler : new Handler());
456    }
457
458    /**
459     * Set an intent for launching UI for this Session. This can be used as a
460     * quick link to an ongoing media screen. The intent should be for an
461     * activity that may be started using
462     * {@link Activity#startActivity(Intent)}.
463     *
464     * @param pi The intent to launch to show UI for this Session.
465     */
466    public void setSessionActivity(PendingIntent pi) {
467        mImpl.setSessionActivity(pi);
468    }
469
470    /**
471     * Set a pending intent for your media button receiver to allow restarting
472     * playback after the session has been stopped. If your app is started in
473     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
474     * the pending intent.
475     * <p>
476     * This method will only work on
477     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
478     * platform versions must include the media button receiver in the
479     * constructor.
480     *
481     * @param mbr The {@link PendingIntent} to send the media button event to.
482     */
483    public void setMediaButtonReceiver(PendingIntent mbr) {
484        mImpl.setMediaButtonReceiver(mbr);
485    }
486
487    /**
488     * Set any flags for the session.
489     *
490     * @param flags The flags to set for this session.
491     */
492    public void setFlags(@SessionFlags int flags) {
493        mImpl.setFlags(flags);
494    }
495
496    /**
497     * Set the stream this session is playing on. This will affect the system's
498     * volume handling for this session. If {@link #setPlaybackToRemote} was
499     * previously called it will stop receiving volume commands and the system
500     * will begin sending volume changes to the appropriate stream.
501     * <p>
502     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
503     *
504     * @param stream The {@link AudioManager} stream this session is playing on.
505     */
506    public void setPlaybackToLocal(int stream) {
507        mImpl.setPlaybackToLocal(stream);
508    }
509
510    /**
511     * Configure this session to use remote volume handling. This must be called
512     * to receive volume button events, otherwise the system will adjust the
513     * current stream volume for this session. If {@link #setPlaybackToLocal}
514     * was previously called that stream will stop receiving volume changes for
515     * this session.
516     * <p>
517     * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
518     * this will only allow an app to handle volume commands sent directly to
519     * the session by a {@link MediaControllerCompat}. System routing of volume
520     * keys will not use the volume provider.
521     *
522     * @param volumeProvider The provider that will handle volume changes. May
523     *            not be null.
524     */
525    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
526        if (volumeProvider == null) {
527            throw new IllegalArgumentException("volumeProvider may not be null!");
528        }
529        mImpl.setPlaybackToRemote(volumeProvider);
530    }
531
532    /**
533     * Set if this session is currently active and ready to receive commands. If
534     * set to false your session's controller may not be discoverable. You must
535     * set the session to active before it can start receiving media button
536     * events or transport commands.
537     * <p>
538     * On platforms earlier than
539     * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
540     * a media button event receiver should be set via the constructor to
541     * receive media button events.
542     *
543     * @param active Whether this session is active or not.
544     */
545    public void setActive(boolean active) {
546        mImpl.setActive(active);
547        for (OnActiveChangeListener listener : mActiveListeners) {
548            listener.onActiveChanged();
549        }
550    }
551
552    /**
553     * Get the current active state of this session.
554     *
555     * @return True if the session is active, false otherwise.
556     */
557    public boolean isActive() {
558        return mImpl.isActive();
559    }
560
561    /**
562     * Send a proprietary event to all MediaControllers listening to this
563     * Session. It's up to the Controller/Session owner to determine the meaning
564     * of any events.
565     *
566     * @param event The name of the event to send
567     * @param extras Any extras included with the event
568     */
569    public void sendSessionEvent(String event, Bundle extras) {
570        if (TextUtils.isEmpty(event)) {
571            throw new IllegalArgumentException("event cannot be null or empty");
572        }
573        mImpl.sendSessionEvent(event, extras);
574    }
575
576    /**
577     * This must be called when an app has finished performing playback. If
578     * playback is expected to start again shortly the session can be left open,
579     * but it must be released if your activity or service is being destroyed.
580     */
581    public void release() {
582        mImpl.release();
583    }
584
585    /**
586     * Retrieve a token object that can be used by apps to create a
587     * {@link MediaControllerCompat} for interacting with this session. The
588     * owner of the session is responsible for deciding how to distribute these
589     * tokens.
590     * <p>
591     * On platform versions before
592     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
593     * used within your app as there is no way to guarantee other apps are using
594     * the same version of the support library.
595     *
596     * @return A token that can be used to create a media controller for this
597     *         session.
598     */
599    public Token getSessionToken() {
600        return mImpl.getSessionToken();
601    }
602
603    /**
604     * Get a controller for this session. This is a convenience method to avoid
605     * having to cache your own controller in process.
606     *
607     * @return A controller for this session.
608     */
609    public MediaControllerCompat getController() {
610        return mController;
611    }
612
613    /**
614     * Update the current playback state.
615     *
616     * @param state The current state of playback
617     */
618    public void setPlaybackState(PlaybackStateCompat state) {
619        mImpl.setPlaybackState(state);
620    }
621
622    /**
623     * Update the current metadata. New metadata can be created using
624     * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
625     * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
626     *
627     * @param metadata The new metadata
628     * @see android.support.v4.media.MediaMetadataCompat.Builder#putBitmap
629     */
630    public void setMetadata(MediaMetadataCompat metadata) {
631        mImpl.setMetadata(metadata);
632    }
633
634    /**
635     * Update the list of items in the play queue. It is an ordered list and
636     * should contain the current item, and previous or upcoming items if they
637     * exist. Specify null if there is no current play queue.
638     * <p>
639     * The queue should be of reasonable size. If the play queue is unbounded
640     * within your app, it is better to send a reasonable amount in a sliding
641     * window instead.
642     *
643     * @param queue A list of items in the play queue.
644     */
645    public void setQueue(List<QueueItem> queue) {
646        mImpl.setQueue(queue);
647    }
648
649    /**
650     * Set the title of the play queue. The UI should display this title along
651     * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
652     * name.
653     *
654     * @param title The title of the play queue.
655     */
656    public void setQueueTitle(CharSequence title) {
657        mImpl.setQueueTitle(title);
658    }
659
660    /**
661     * Set the style of rating used by this session. Apps trying to set the
662     * rating should use this style. Must be one of the following:
663     * <ul>
664     * <li>{@link RatingCompat#RATING_NONE}</li>
665     * <li>{@link RatingCompat#RATING_3_STARS}</li>
666     * <li>{@link RatingCompat#RATING_4_STARS}</li>
667     * <li>{@link RatingCompat#RATING_5_STARS}</li>
668     * <li>{@link RatingCompat#RATING_HEART}</li>
669     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
670     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
671     * </ul>
672     */
673    public void setRatingType(@RatingCompat.Style int type) {
674        mImpl.setRatingType(type);
675    }
676
677    /**
678     * Enable/disable captioning for this session.
679     *
680     * @param enabled {@code true} to enable captioning, {@code false} to disable.
681     */
682    public void setCaptioningEnabled(boolean enabled) {
683        mImpl.setCaptioningEnabled(enabled);
684    }
685
686    /**
687     * Set the repeat mode for this session.
688     * <p>
689     * Note that if this method is not called before, {@link MediaControllerCompat#getRepeatMode}
690     * will return {@link PlaybackStateCompat#REPEAT_MODE_NONE}.
691     *
692     * @param repeatMode The repeat mode. Must be one of the followings:
693     *            {@link PlaybackStateCompat#REPEAT_MODE_NONE},
694     *            {@link PlaybackStateCompat#REPEAT_MODE_ONE},
695     *            {@link PlaybackStateCompat#REPEAT_MODE_ALL},
696     *            {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
697     */
698    public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
699        mImpl.setRepeatMode(repeatMode);
700    }
701
702    /**
703     * Set the shuffle mode for this session.
704     * <p>
705     * Note that if this method is not called before,
706     * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
707     *
708     * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
709     * @deprecated Use {@link #setShuffleMode} instead.
710     */
711    @Deprecated
712    public void setShuffleModeEnabled(boolean enabled) {
713        mImpl.setShuffleModeEnabled(enabled);
714    }
715
716    /**
717     * Set the shuffle mode for this session.
718     * <p>
719     * Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
720     * will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
721     *
722     * @param shuffleMode The shuffle mode. Must be one of the followings:
723     *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
724     *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
725     *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
726     */
727    public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
728        mImpl.setShuffleMode(shuffleMode);
729    }
730
731    /**
732     * Set some extras that can be associated with the
733     * {@link MediaSessionCompat}. No assumptions should be made as to how a
734     * {@link MediaControllerCompat} will handle these extras. Keys should be
735     * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
736     *
737     * @param extras The extras associated with the session.
738     */
739    public void setExtras(Bundle extras) {
740        mImpl.setExtras(extras);
741    }
742
743    /**
744     * Gets the underlying framework {@link android.media.session.MediaSession}
745     * object.
746     * <p>
747     * This method is only supported on API 21+.
748     * </p>
749     *
750     * @return The underlying {@link android.media.session.MediaSession} object,
751     *         or null if none.
752     */
753    public Object getMediaSession() {
754        return mImpl.getMediaSession();
755    }
756
757    /**
758     * Gets the underlying framework {@link android.media.RemoteControlClient}
759     * object.
760     * <p>
761     * This method is only supported on APIs 14-20. On API 21+
762     * {@link #getMediaSession()} should be used instead.
763     *
764     * @return The underlying {@link android.media.RemoteControlClient} object,
765     *         or null if none.
766     */
767    public Object getRemoteControlClient() {
768        return mImpl.getRemoteControlClient();
769    }
770
771    /**
772     * Returns the name of the package that sent the last media button, transport control, or
773     * command from controllers and the system. This is only valid while in a request callback, such
774     * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
775     *
776     * @hide
777     */
778    @RestrictTo(LIBRARY_GROUP)
779    public String getCallingPackage() {
780        return mImpl.getCallingPackage();
781    }
782
783    /**
784     * Adds a listener to be notified when the active status of this session
785     * changes. This is primarily used by the support library and should not be
786     * needed by apps.
787     *
788     * @param listener The listener to add.
789     */
790    public void addOnActiveChangeListener(OnActiveChangeListener listener) {
791        if (listener == null) {
792            throw new IllegalArgumentException("Listener may not be null");
793        }
794        mActiveListeners.add(listener);
795    }
796
797    /**
798     * Stops the listener from being notified when the active status of this
799     * session changes.
800     *
801     * @param listener The listener to remove.
802     */
803    public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
804        if (listener == null) {
805            throw new IllegalArgumentException("Listener may not be null");
806        }
807        mActiveListeners.remove(listener);
808    }
809
810    /**
811     * Creates an instance from a framework {@link android.media.session.MediaSession} object.
812     * <p>
813     * This method is only supported on API 21+. On API 20 and below, it returns null.
814     * </p>
815     *
816     * @param context The context to use to create the session.
817     * @param mediaSession A {@link android.media.session.MediaSession} object.
818     * @return An equivalent {@link MediaSessionCompat} object, or null if none.
819     */
820    public static MediaSessionCompat fromMediaSession(Context context, Object mediaSession) {
821        if (context != null && mediaSession != null && Build.VERSION.SDK_INT >= 21) {
822            return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
823        }
824        return null;
825    }
826
827    private static PlaybackStateCompat getStateWithUpdatedPosition(
828            PlaybackStateCompat state, MediaMetadataCompat metadata) {
829        if (state == null || state.getPosition() == PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN) {
830            return state;
831        }
832
833        if (state.getState() == PlaybackStateCompat.STATE_PLAYING
834                || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
835                || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
836            long updateTime = state.getLastPositionUpdateTime();
837            if (updateTime > 0) {
838                long currentTime = SystemClock.elapsedRealtime();
839                long position = (long) (state.getPlaybackSpeed() * (currentTime - updateTime))
840                        + state.getPosition();
841                long duration = -1;
842                if (metadata != null && metadata.containsKey(
843                        MediaMetadataCompat.METADATA_KEY_DURATION)) {
844                    duration = metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
845                }
846
847                if (duration >= 0 && position > duration) {
848                    position = duration;
849                } else if (position < 0) {
850                    position = 0;
851                }
852                return new PlaybackStateCompat.Builder(state)
853                        .setState(state.getState(), position, state.getPlaybackSpeed(), currentTime)
854                        .build();
855            }
856        }
857        return state;
858    }
859
860    /**
861     * Receives transport controls, media buttons, and commands from controllers
862     * and the system. The callback may be set using {@link #setCallback}.
863     */
864    public abstract static class Callback {
865        final Object mCallbackObj;
866        WeakReference<MediaSessionImpl> mSessionImpl;
867
868        public Callback() {
869            if (android.os.Build.VERSION.SDK_INT >= 24) {
870                mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
871            } else if (android.os.Build.VERSION.SDK_INT >= 23) {
872                mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
873            } else if (android.os.Build.VERSION.SDK_INT >= 21) {
874                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
875            } else {
876                mCallbackObj = null;
877            }
878        }
879
880        /**
881         * Called when a controller has sent a custom command to this session.
882         * The owner of the session may handle custom commands but is not
883         * required to.
884         *
885         * @param command The command name.
886         * @param extras Optional parameters for the command, may be null.
887         * @param cb A result receiver to which a result may be sent by the command, may be null.
888         */
889        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
890        }
891
892        /**
893         * Override to handle media button events.
894         *
895         * @param mediaButtonEvent The media button event intent.
896         * @return True if the event was handled, false otherwise.
897         */
898        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
899            return false;
900        }
901
902        /**
903         * Override to handle requests to prepare playback. During the preparation, a session
904         * should not hold audio focus in order to allow other session play seamlessly.
905         * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
906         * after the preparation is done.
907         */
908        public void onPrepare() {
909        }
910
911        /**
912         * Override to handle requests to prepare for playing a specific mediaId that was provided
913         * by your app. During the preparation, a session should not hold audio focus in order to
914         * allow other session play seamlessly. The state of playback should be updated to
915         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
916         * of the prepared content should start in the implementation of {@link #onPlay}. Override
917         * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
918         */
919        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
920        }
921
922        /**
923         * Override to handle requests to prepare playback from a search query. An
924         * empty query indicates that the app may prepare any music. The
925         * implementation should attempt to make a smart choice about what to
926         * play. During the preparation, a session should not hold audio focus in order to allow
927         * other session play seamlessly. The state of playback should be updated to
928         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
929         * The playback of the prepared content should start in the implementation of
930         * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
931         * starting playback without preparation.
932         */
933        public void onPrepareFromSearch(String query, Bundle extras) {
934        }
935
936        /**
937         * Override to handle requests to prepare a specific media item represented by a URI.
938         * During the preparation, a session should not hold audio focus in order to allow other
939         * session play seamlessly. The state of playback should be updated to
940         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
941         * the prepared content should start in the implementation of {@link #onPlay}. Override
942         * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
943         */
944        public void onPrepareFromUri(Uri uri, Bundle extras) {
945        }
946
947        /**
948         * Override to handle requests to begin playback.
949         */
950        public void onPlay() {
951        }
952
953        /**
954         * Override to handle requests to play a specific mediaId that was
955         * provided by your app.
956         */
957        public void onPlayFromMediaId(String mediaId, Bundle extras) {
958        }
959
960        /**
961         * Override to handle requests to begin playback from a search query. An
962         * empty query indicates that the app may play any music. The
963         * implementation should attempt to make a smart choice about what to
964         * play.
965         */
966        public void onPlayFromSearch(String query, Bundle extras) {
967        }
968
969        /**
970         * Override to handle requests to play a specific media item represented by a URI.
971         */
972        public void onPlayFromUri(Uri uri, Bundle extras) {
973        }
974
975        /**
976         * Override to handle requests to play an item with a given id from the
977         * play queue.
978         */
979        public void onSkipToQueueItem(long id) {
980        }
981
982        /**
983         * Override to handle requests to pause playback.
984         */
985        public void onPause() {
986        }
987
988        /**
989         * Override to handle requests to skip to the next media item.
990         */
991        public void onSkipToNext() {
992        }
993
994        /**
995         * Override to handle requests to skip to the previous media item.
996         */
997        public void onSkipToPrevious() {
998        }
999
1000        /**
1001         * Override to handle requests to fast forward.
1002         */
1003        public void onFastForward() {
1004        }
1005
1006        /**
1007         * Override to handle requests to rewind.
1008         */
1009        public void onRewind() {
1010        }
1011
1012        /**
1013         * Override to handle requests to stop playback.
1014         */
1015        public void onStop() {
1016        }
1017
1018        /**
1019         * Override to handle requests to seek to a specific position in ms.
1020         *
1021         * @param pos New position to move to, in milliseconds.
1022         */
1023        public void onSeekTo(long pos) {
1024        }
1025
1026        /**
1027         * Override to handle the item being rated.
1028         *
1029         * @param rating
1030         */
1031        public void onSetRating(RatingCompat rating) {
1032        }
1033
1034        /**
1035         * Override to handle requests to enable/disable captioning.
1036         *
1037         * @param enabled {@code true} to enable captioning, {@code false} to disable.
1038         */
1039        public void onSetCaptioningEnabled(boolean enabled) {
1040        }
1041
1042        /**
1043         * Override to handle the setting of the repeat mode.
1044         * <p>
1045         * You should call {@link #setRepeatMode} before end of this method in order to notify
1046         * the change to the {@link MediaControllerCompat}, or
1047         * {@link MediaControllerCompat#getRepeatMode} could return an invalid value.
1048         *
1049         * @param repeatMode The repeat mode which is one of followings:
1050         *            {@link PlaybackStateCompat#REPEAT_MODE_NONE},
1051         *            {@link PlaybackStateCompat#REPEAT_MODE_ONE},
1052         *            {@link PlaybackStateCompat#REPEAT_MODE_ALL},
1053         *            {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
1054         */
1055        public void onSetRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
1056        }
1057
1058        /**
1059         * Override to handle the setting of the shuffle mode.
1060         * <p>
1061         * You should call {@link #setShuffleModeEnabled} before the end of this method in order to
1062         * notify the change to the {@link MediaControllerCompat}, or
1063         * {@link MediaControllerCompat#isShuffleModeEnabled} could return an invalid value.
1064         *
1065         * @param enabled true when the shuffle mode is enabled, false otherwise.
1066         * @deprecated Use {@link #onSetShuffleMode} instead.
1067         */
1068        @Deprecated
1069        public void onSetShuffleModeEnabled(boolean enabled) {
1070        }
1071
1072        /**
1073         * Override to handle the setting of the shuffle mode.
1074         * <p>
1075         * You should call {@link #setShuffleMode} before the end of this method in order to
1076         * notify the change to the {@link MediaControllerCompat}, or
1077         * {@link MediaControllerCompat#getShuffleMode} could return an invalid value.
1078         *
1079         * @param shuffleMode The shuffle mode which is one of followings:
1080         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
1081         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
1082         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
1083         */
1084        public void onSetShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
1085        }
1086
1087        /**
1088         * Called when a {@link MediaControllerCompat} wants a
1089         * {@link PlaybackStateCompat.CustomAction} to be performed.
1090         *
1091         * @param action The action that was originally sent in the
1092         *            {@link PlaybackStateCompat.CustomAction}.
1093         * @param extras Optional extras specified by the
1094         *            {@link MediaControllerCompat}.
1095         * @see #ACTION_FLAG_AS_INAPPROPRIATE
1096         * @see #ACTION_SKIP_AD
1097         * @see #ACTION_FOLLOW
1098         * @see #ACTION_UNFOLLOW
1099         */
1100        public void onCustomAction(String action, Bundle extras) {
1101        }
1102
1103        /**
1104         * Called when a {@link MediaControllerCompat} wants to add a {@link QueueItem}
1105         * with the given {@link MediaDescriptionCompat description} at the end of the play queue.
1106         *
1107         * @param description The {@link MediaDescriptionCompat} for creating the {@link QueueItem}
1108         *            to be inserted.
1109         */
1110        public void onAddQueueItem(MediaDescriptionCompat description) {
1111        }
1112
1113        /**
1114         * Called when a {@link MediaControllerCompat} wants to add a {@link QueueItem}
1115         * with the given {@link MediaDescriptionCompat description} at the specified position
1116         * in the play queue.
1117         *
1118         * @param description The {@link MediaDescriptionCompat} for creating the {@link QueueItem}
1119         *            to be inserted.
1120         * @param index The index at which the created {@link QueueItem} is to be inserted.
1121         */
1122        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
1123        }
1124
1125        /**
1126         * Called when a {@link MediaControllerCompat} wants to remove the first occurrence of the
1127         * specified {@link QueueItem} with the given {@link MediaDescriptionCompat description}
1128         * in the play queue.
1129         *
1130         * @param description The {@link MediaDescriptionCompat} for denoting the {@link QueueItem}
1131         *            to be removed.
1132         */
1133        public void onRemoveQueueItem(MediaDescriptionCompat description) {
1134        }
1135
1136        /**
1137         * Called when a {@link MediaControllerCompat} wants to remove a {@link QueueItem} at the
1138         * specified position in the play queue.
1139         *
1140         * @param index The index of the element to be removed.
1141         * @deprecated {@link #onRemoveQueueItem} will be called instead.
1142         */
1143        @Deprecated
1144        public void onRemoveQueueItemAt(int index) {
1145        }
1146
1147        @RequiresApi(21)
1148        private class StubApi21 implements MediaSessionCompatApi21.Callback {
1149
1150            StubApi21() {
1151            }
1152
1153            @Override
1154            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
1155                try {
1156                    if (command.equals(MediaControllerCompat.COMMAND_GET_EXTRA_BINDER)) {
1157                        MediaSessionImplApi21 impl = (MediaSessionImplApi21) mSessionImpl.get();
1158                        if (impl != null) {
1159                            Bundle result = new Bundle();
1160                            IMediaSession extraBinder = impl.getSessionToken().getExtraBinder();
1161                            BundleCompat.putBinder(result, EXTRA_BINDER,
1162                                    extraBinder == null ? null : extraBinder.asBinder());
1163                            cb.send(0, result);
1164                        }
1165                    } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM)) {
1166                        extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
1167                        Callback.this.onAddQueueItem(
1168                                (MediaDescriptionCompat) extras.getParcelable(
1169                                        MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION));
1170                    } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM_AT)) {
1171                        extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
1172                        Callback.this.onAddQueueItem(
1173                                (MediaDescriptionCompat) extras.getParcelable(
1174                                        MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION),
1175                                extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX));
1176                    } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM)) {
1177                        extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
1178                        Callback.this.onRemoveQueueItem(
1179                                (MediaDescriptionCompat) extras.getParcelable(
1180                                        MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION));
1181                    } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM_AT)) {
1182                        MediaSessionImplApi21 impl = (MediaSessionImplApi21) mSessionImpl.get();
1183                        if (impl != null && impl.mQueue != null) {
1184                            int index =
1185                                    extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX, -1);
1186                            QueueItem item = (index >= 0 && index < impl.mQueue.size())
1187                                    ? impl.mQueue.get(index) : null;
1188                            if (item != null) {
1189                                Callback.this.onRemoveQueueItem(item.getDescription());
1190                            }
1191                        }
1192                    } else {
1193                        Callback.this.onCommand(command, extras, cb);
1194                    }
1195                } catch (BadParcelableException e) {
1196                    // Do not print the exception here, since it is already done by the Parcel
1197                    // class.
1198                    Log.e(TAG, "Could not unparcel the extra data.");
1199                }
1200            }
1201
1202            @Override
1203            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
1204                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
1205            }
1206
1207            @Override
1208            public void onPlay() {
1209                Callback.this.onPlay();
1210            }
1211
1212            @Override
1213            public void onPlayFromMediaId(String mediaId, Bundle extras) {
1214                Callback.this.onPlayFromMediaId(mediaId, extras);
1215            }
1216
1217            @Override
1218            public void onPlayFromSearch(String search, Bundle extras) {
1219                Callback.this.onPlayFromSearch(search, extras);
1220            }
1221
1222            @Override
1223            public void onSkipToQueueItem(long id) {
1224                Callback.this.onSkipToQueueItem(id);
1225            }
1226
1227            @Override
1228            public void onPause() {
1229                Callback.this.onPause();
1230            }
1231
1232            @Override
1233            public void onSkipToNext() {
1234                Callback.this.onSkipToNext();
1235            }
1236
1237            @Override
1238            public void onSkipToPrevious() {
1239                Callback.this.onSkipToPrevious();
1240            }
1241
1242            @Override
1243            public void onFastForward() {
1244                Callback.this.onFastForward();
1245            }
1246
1247            @Override
1248            public void onRewind() {
1249                Callback.this.onRewind();
1250            }
1251
1252            @Override
1253            public void onStop() {
1254                Callback.this.onStop();
1255            }
1256
1257            @Override
1258            public void onSeekTo(long pos) {
1259                Callback.this.onSeekTo(pos);
1260            }
1261
1262            @Override
1263            public void onSetRating(Object ratingObj) {
1264                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
1265            }
1266
1267            @Override
1268            public void onCustomAction(String action, Bundle extras) {
1269                if (action.equals(ACTION_PLAY_FROM_URI)) {
1270                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
1271                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
1272                    Callback.this.onPlayFromUri(uri, bundle);
1273                } else if (action.equals(ACTION_PREPARE)) {
1274                    Callback.this.onPrepare();
1275                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
1276                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
1277                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1278                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
1279                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
1280                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
1281                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1282                    Callback.this.onPrepareFromSearch(query, bundle);
1283                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
1284                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
1285                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1286                    Callback.this.onPrepareFromUri(uri, bundle);
1287                } else if (action.equals(ACTION_SET_CAPTIONING_ENABLED)) {
1288                    boolean enabled = extras.getBoolean(ACTION_ARGUMENT_CAPTIONING_ENABLED);
1289                    Callback.this.onSetCaptioningEnabled(enabled);
1290                } else if (action.equals(ACTION_SET_REPEAT_MODE)) {
1291                    int repeatMode = extras.getInt(ACTION_ARGUMENT_REPEAT_MODE);
1292                    Callback.this.onSetRepeatMode(repeatMode);
1293                } else if (action.equals(ACTION_SET_SHUFFLE_MODE_ENABLED)) {
1294                    boolean enabled = extras.getBoolean(ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED);
1295                    Callback.this.onSetShuffleModeEnabled(enabled);
1296                } else if (action.equals(ACTION_SET_SHUFFLE_MODE)) {
1297                    int shuffleMode = extras.getInt(ACTION_ARGUMENT_SHUFFLE_MODE);
1298                    Callback.this.onSetShuffleMode(shuffleMode);
1299                } else {
1300                    Callback.this.onCustomAction(action, extras);
1301                }
1302            }
1303        }
1304
1305        @RequiresApi(23)
1306        private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
1307
1308            StubApi23() {
1309            }
1310
1311            @Override
1312            public void onPlayFromUri(Uri uri, Bundle extras) {
1313                Callback.this.onPlayFromUri(uri, extras);
1314            }
1315        }
1316
1317        @RequiresApi(24)
1318        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
1319
1320            StubApi24() {
1321            }
1322
1323            @Override
1324            public void onPrepare() {
1325                Callback.this.onPrepare();
1326            }
1327
1328            @Override
1329            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
1330                Callback.this.onPrepareFromMediaId(mediaId, extras);
1331            }
1332
1333            @Override
1334            public void onPrepareFromSearch(String query, Bundle extras) {
1335                Callback.this.onPrepareFromSearch(query, extras);
1336            }
1337
1338            @Override
1339            public void onPrepareFromUri(Uri uri, Bundle extras) {
1340                Callback.this.onPrepareFromUri(uri, extras);
1341            }
1342        }
1343    }
1344
1345    /**
1346     * Represents an ongoing session. This may be passed to apps by the session
1347     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
1348     * the session.
1349     */
1350    public static final class Token implements Parcelable {
1351        private final Object mInner;
1352        private final IMediaSession mExtraBinder;
1353
1354        Token(Object inner) {
1355            this(inner, null);
1356        }
1357
1358        Token(Object inner, IMediaSession extraBinder) {
1359            mInner = inner;
1360            mExtraBinder = extraBinder;
1361        }
1362
1363        /**
1364         * Creates a compat Token from a framework
1365         * {@link android.media.session.MediaSession.Token} object.
1366         * <p>
1367         * This method is only supported on
1368         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
1369         * </p>
1370         *
1371         * @param token The framework token object.
1372         * @return A compat Token for use with {@link MediaControllerCompat}.
1373         */
1374        public static Token fromToken(Object token) {
1375            return fromToken(token, null);
1376        }
1377
1378        /**
1379         * Creates a compat Token from a framework
1380         * {@link android.media.session.MediaSession.Token} object, and the extra binder.
1381         * <p>
1382         * This method is only supported on
1383         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
1384         * </p>
1385         *
1386         * @param token The framework token object.
1387         * @param extraBinder The extra binder.
1388         * @return A compat Token for use with {@link MediaControllerCompat}.
1389         * @hide
1390         */
1391        @RestrictTo(LIBRARY_GROUP)
1392        public static Token fromToken(Object token, IMediaSession extraBinder) {
1393            if (token != null && android.os.Build.VERSION.SDK_INT >= 21) {
1394                return new Token(MediaSessionCompatApi21.verifyToken(token), extraBinder);
1395            }
1396            return null;
1397        }
1398
1399        @Override
1400        public int describeContents() {
1401            return 0;
1402        }
1403
1404        @Override
1405        public void writeToParcel(Parcel dest, int flags) {
1406            if (android.os.Build.VERSION.SDK_INT >= 21) {
1407                dest.writeParcelable((Parcelable) mInner, flags);
1408            } else {
1409                dest.writeStrongBinder((IBinder) mInner);
1410            }
1411        }
1412
1413        @Override
1414        public int hashCode() {
1415            if (mInner == null) {
1416                return 0;
1417            }
1418            return mInner.hashCode();
1419        }
1420
1421        @Override
1422        public boolean equals(Object obj) {
1423            if (this == obj) {
1424                return true;
1425            }
1426            if (!(obj instanceof Token)) {
1427                return false;
1428            }
1429
1430            Token other = (Token) obj;
1431            if (mInner == null) {
1432                return other.mInner == null;
1433            }
1434            if (other.mInner == null) {
1435                return false;
1436            }
1437            return mInner.equals(other.mInner);
1438        }
1439
1440        /**
1441         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
1442         * <p>
1443         * This method is only supported on API 21+.
1444         * </p>
1445         *
1446         * @return The underlying {@link android.media.session.MediaSession.Token} object,
1447         * or null if none.
1448         */
1449        public Object getToken() {
1450            return mInner;
1451        }
1452
1453        /**
1454         * @hide
1455         */
1456        @RestrictTo(LIBRARY_GROUP)
1457        public IMediaSession getExtraBinder() {
1458            return mExtraBinder;
1459        }
1460
1461        public static final Parcelable.Creator<Token> CREATOR
1462                = new Parcelable.Creator<Token>() {
1463                    @Override
1464                    public Token createFromParcel(Parcel in) {
1465                        Object inner;
1466                        if (android.os.Build.VERSION.SDK_INT >= 21) {
1467                            inner = in.readParcelable(null);
1468                        } else {
1469                            inner = in.readStrongBinder();
1470                        }
1471                        return new Token(inner);
1472                    }
1473
1474                    @Override
1475                    public Token[] newArray(int size) {
1476                        return new Token[size];
1477                    }
1478        };
1479    }
1480
1481    /**
1482     * A single item that is part of the play queue. It contains a description
1483     * of the item and its id in the queue.
1484     */
1485    public static final class QueueItem implements Parcelable {
1486        /**
1487         * This id is reserved. No items can be explicitly assigned this id.
1488         */
1489        public static final int UNKNOWN_ID = -1;
1490
1491        private final MediaDescriptionCompat mDescription;
1492        private final long mId;
1493
1494        private Object mItem;
1495
1496        /**
1497         * Create a new {@link MediaSessionCompat.QueueItem}.
1498         *
1499         * @param description The {@link MediaDescriptionCompat} for this item.
1500         * @param id An identifier for this item. It must be unique within the
1501         *            play queue and cannot be {@link #UNKNOWN_ID}.
1502         */
1503        public QueueItem(MediaDescriptionCompat description, long id) {
1504            this(null, description, id);
1505        }
1506
1507        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
1508            if (description == null) {
1509                throw new IllegalArgumentException("Description cannot be null.");
1510            }
1511            if (id == UNKNOWN_ID) {
1512                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1513            }
1514            mDescription = description;
1515            mId = id;
1516            mItem = queueItem;
1517        }
1518
1519        QueueItem(Parcel in) {
1520            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
1521            mId = in.readLong();
1522        }
1523
1524        /**
1525         * Get the description for this item.
1526         */
1527        public MediaDescriptionCompat getDescription() {
1528            return mDescription;
1529        }
1530
1531        /**
1532         * Get the queue id for this item.
1533         */
1534        public long getQueueId() {
1535            return mId;
1536        }
1537
1538        @Override
1539        public void writeToParcel(Parcel dest, int flags) {
1540            mDescription.writeToParcel(dest, flags);
1541            dest.writeLong(mId);
1542        }
1543
1544        @Override
1545        public int describeContents() {
1546            return 0;
1547        }
1548
1549        /**
1550         * Get the underlying
1551         * {@link android.media.session.MediaSession.QueueItem}.
1552         * <p>
1553         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1554         * is returned.
1555         *
1556         * @return The underlying
1557         *         {@link android.media.session.MediaSession.QueueItem} or null.
1558         */
1559        public Object getQueueItem() {
1560            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1561                return mItem;
1562            }
1563            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1564                    mId);
1565            return mItem;
1566        }
1567
1568        /**
1569         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1570         * object.
1571         * <p>
1572         * This method is only supported on API 21+. On API 20 and below, it returns null.
1573         * </p>
1574         *
1575         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1576         * @return An equivalent {@link QueueItem} object, or null if none.
1577         */
1578        public static QueueItem fromQueueItem(Object queueItem) {
1579            if (queueItem == null || Build.VERSION.SDK_INT < 21) {
1580                return null;
1581            }
1582            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1583            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1584                    descriptionObj);
1585            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1586            return new QueueItem(queueItem, description, id);
1587        }
1588
1589        /**
1590         * Creates a list of {@link QueueItem} objects from a framework
1591         * {@link android.media.session.MediaSession.QueueItem} object list.
1592         * <p>
1593         * This method is only supported on API 21+. On API 20 and below, it returns null.
1594         * </p>
1595         *
1596         * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects.
1597         * @return An equivalent list of {@link QueueItem} objects, or null if none.
1598         */
1599        public static List<QueueItem> fromQueueItemList(List<?> itemList) {
1600            if (itemList == null || Build.VERSION.SDK_INT < 21) {
1601                return null;
1602            }
1603            List<QueueItem> items = new ArrayList<>();
1604            for (Object itemObj : itemList) {
1605                items.add(fromQueueItem(itemObj));
1606            }
1607            return items;
1608        }
1609
1610        public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1611                = new Creator<MediaSessionCompat.QueueItem>() {
1612
1613            @Override
1614            public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1615                return new MediaSessionCompat.QueueItem(p);
1616            }
1617
1618            @Override
1619            public MediaSessionCompat.QueueItem[] newArray(int size) {
1620                return new MediaSessionCompat.QueueItem[size];
1621            }
1622        };
1623
1624        @Override
1625        public String toString() {
1626            return "MediaSession.QueueItem {" +
1627                    "Description=" + mDescription +
1628                    ", Id=" + mId + " }";
1629        }
1630    }
1631
1632    /**
1633     * This is a wrapper for {@link ResultReceiver} for sending over aidl
1634     * interfaces. The framework version was not exposed to aidls until
1635     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1636     */
1637    static final class ResultReceiverWrapper implements Parcelable {
1638        private ResultReceiver mResultReceiver;
1639
1640        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1641            mResultReceiver = resultReceiver;
1642        }
1643
1644        ResultReceiverWrapper(Parcel in) {
1645            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1646        }
1647
1648        public static final Creator<ResultReceiverWrapper>
1649                CREATOR = new Creator<ResultReceiverWrapper>() {
1650            @Override
1651            public ResultReceiverWrapper createFromParcel(Parcel p) {
1652                return new ResultReceiverWrapper(p);
1653            }
1654
1655            @Override
1656            public ResultReceiverWrapper[] newArray(int size) {
1657                return new ResultReceiverWrapper[size];
1658            }
1659        };
1660
1661        @Override
1662        public int describeContents() {
1663            return 0;
1664        }
1665
1666        @Override
1667        public void writeToParcel(Parcel dest, int flags) {
1668            mResultReceiver.writeToParcel(dest, flags);
1669        }
1670    }
1671
1672    public interface OnActiveChangeListener {
1673        void onActiveChanged();
1674    }
1675
1676    interface MediaSessionImpl {
1677        void setCallback(Callback callback, Handler handler);
1678        void setFlags(@SessionFlags int flags);
1679        void setPlaybackToLocal(int stream);
1680        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
1681        void setActive(boolean active);
1682        boolean isActive();
1683        void sendSessionEvent(String event, Bundle extras);
1684        void release();
1685        Token getSessionToken();
1686        void setPlaybackState(PlaybackStateCompat state);
1687        void setMetadata(MediaMetadataCompat metadata);
1688
1689        void setSessionActivity(PendingIntent pi);
1690
1691        void setMediaButtonReceiver(PendingIntent mbr);
1692        void setQueue(List<QueueItem> queue);
1693        void setQueueTitle(CharSequence title);
1694
1695        void setRatingType(@RatingCompat.Style int type);
1696        void setCaptioningEnabled(boolean enabled);
1697        void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
1698        void setShuffleModeEnabled(boolean enabled);
1699        void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
1700        void setExtras(Bundle extras);
1701
1702        Object getMediaSession();
1703
1704        Object getRemoteControlClient();
1705
1706        String getCallingPackage();
1707    }
1708
1709    static class MediaSessionImplBase implements MediaSessionImpl {
1710        /***** RemoteControlClient States, we only need none as the others were public *******/
1711        static final int RCC_PLAYSTATE_NONE = 0;
1712
1713        private final Context mContext;
1714        private final ComponentName mMediaButtonReceiverComponentName;
1715        private final PendingIntent mMediaButtonReceiverIntent;
1716        private final MediaSessionStub mStub;
1717        private final Token mToken;
1718        final String mPackageName;
1719        final String mTag;
1720        final AudioManager mAudioManager;
1721        final RemoteControlClient mRcc;
1722
1723        final Object mLock = new Object();
1724        final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1725                = new RemoteCallbackList<>();
1726
1727        private MessageHandler mHandler;
1728        boolean mDestroyed = false;
1729        boolean mIsActive = false;
1730        private boolean mIsMbrRegistered = false;
1731        private boolean mIsRccRegistered = false;
1732        volatile Callback mCallback;
1733
1734        @SessionFlags int mFlags;
1735
1736        MediaMetadataCompat mMetadata;
1737        PlaybackStateCompat mState;
1738        PendingIntent mSessionActivity;
1739        List<QueueItem> mQueue;
1740        CharSequence mQueueTitle;
1741        @RatingCompat.Style int mRatingType;
1742        boolean mCaptioningEnabled;
1743        @PlaybackStateCompat.RepeatMode int mRepeatMode;
1744        @PlaybackStateCompat.ShuffleMode int mShuffleMode;
1745        boolean mShuffleModeEnabled;
1746        Bundle mExtras;
1747
1748        int mVolumeType;
1749        int mLocalStream;
1750        VolumeProviderCompat mVolumeProvider;
1751
1752        private VolumeProviderCompat.Callback mVolumeCallback
1753                = new VolumeProviderCompat.Callback() {
1754            @Override
1755            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1756                if (mVolumeProvider != volumeProvider) {
1757                    return;
1758                }
1759                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1760                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1761                        volumeProvider.getCurrentVolume());
1762                sendVolumeInfoChanged(info);
1763            }
1764        };
1765
1766        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1767                PendingIntent mbrIntent) {
1768            if (mbrComponent == null) {
1769                throw new IllegalArgumentException(
1770                        "MediaButtonReceiver component may not be null.");
1771            }
1772            mContext = context;
1773            mPackageName = context.getPackageName();
1774            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1775            mTag = tag;
1776            mMediaButtonReceiverComponentName = mbrComponent;
1777            mMediaButtonReceiverIntent = mbrIntent;
1778            mStub = new MediaSessionStub();
1779            mToken = new Token(mStub);
1780
1781            mRatingType = RatingCompat.RATING_NONE;
1782            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1783            mLocalStream = AudioManager.STREAM_MUSIC;
1784            mRcc = new RemoteControlClient(mbrIntent);
1785        }
1786
1787        @Override
1788        public void setCallback(Callback callback, Handler handler) {
1789            mCallback = callback;
1790            if (callback != null) {
1791                if (handler == null) {
1792                    handler = new Handler();
1793                }
1794                synchronized (mLock) {
1795                    mHandler = new MessageHandler(handler.getLooper());
1796                }
1797            }
1798        }
1799
1800        void postToHandler(int what) {
1801            postToHandler(what, null);
1802        }
1803
1804        void postToHandler(int what, int arg1) {
1805            postToHandler(what, null, arg1);
1806        }
1807
1808        void postToHandler(int what, Object obj) {
1809            postToHandler(what, obj, null);
1810        }
1811
1812        void postToHandler(int what, Object obj, int arg1) {
1813            synchronized (mLock) {
1814                if (mHandler != null) {
1815                    mHandler.post(what, obj, arg1);
1816                }
1817            }
1818        }
1819
1820        void postToHandler(int what, Object obj, Bundle extras) {
1821            synchronized (mLock) {
1822                if (mHandler != null) {
1823                    mHandler.post(what, obj, extras);
1824                }
1825            }
1826        }
1827
1828        @Override
1829        public void setFlags(@SessionFlags int flags) {
1830            synchronized (mLock) {
1831                mFlags = flags;
1832            }
1833            update();
1834        }
1835
1836        @Override
1837        public void setPlaybackToLocal(int stream) {
1838            if (mVolumeProvider != null) {
1839                mVolumeProvider.setCallback(null);
1840            }
1841            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1842            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1843                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1844                    mAudioManager.getStreamMaxVolume(mLocalStream),
1845                    mAudioManager.getStreamVolume(mLocalStream));
1846            sendVolumeInfoChanged(info);
1847        }
1848
1849        @Override
1850        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1851            if (volumeProvider == null) {
1852                throw new IllegalArgumentException("volumeProvider may not be null");
1853            }
1854            if (mVolumeProvider != null) {
1855                mVolumeProvider.setCallback(null);
1856            }
1857            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1858            mVolumeProvider = volumeProvider;
1859            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1860                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1861                    mVolumeProvider.getCurrentVolume());
1862            sendVolumeInfoChanged(info);
1863
1864            volumeProvider.setCallback(mVolumeCallback);
1865        }
1866
1867        @Override
1868        public void setActive(boolean active) {
1869            if (active == mIsActive) {
1870                return;
1871            }
1872            mIsActive = active;
1873            if (update()) {
1874                setMetadata(mMetadata);
1875                setPlaybackState(mState);
1876            }
1877        }
1878
1879        @Override
1880        public boolean isActive() {
1881            return mIsActive;
1882        }
1883
1884        @Override
1885        public void sendSessionEvent(String event, Bundle extras) {
1886            sendEvent(event, extras);
1887        }
1888
1889        @Override
1890        public void release() {
1891            mIsActive = false;
1892            mDestroyed = true;
1893            update();
1894            sendSessionDestroyed();
1895        }
1896
1897        @Override
1898        public Token getSessionToken() {
1899            return mToken;
1900        }
1901
1902        @Override
1903        public void setPlaybackState(PlaybackStateCompat state) {
1904            synchronized (mLock) {
1905                mState = state;
1906            }
1907            sendState(state);
1908            if (!mIsActive) {
1909                // Don't set the state until after the RCC is registered
1910                return;
1911            }
1912            if (state == null) {
1913                mRcc.setPlaybackState(0);
1914                mRcc.setTransportControlFlags(0);
1915            } else {
1916                // Set state
1917                setRccState(state);
1918
1919                // Set transport control flags
1920                mRcc.setTransportControlFlags(
1921                        getRccTransportControlFlagsFromActions(state.getActions()));
1922            }
1923        }
1924
1925        void setRccState(PlaybackStateCompat state) {
1926            mRcc.setPlaybackState(getRccStateFromState(state.getState()));
1927        }
1928
1929        int getRccStateFromState(int state) {
1930            switch (state) {
1931                case PlaybackStateCompat.STATE_CONNECTING:
1932                case PlaybackStateCompat.STATE_BUFFERING:
1933                    return RemoteControlClient.PLAYSTATE_BUFFERING;
1934                case PlaybackStateCompat.STATE_ERROR:
1935                    return RemoteControlClient.PLAYSTATE_ERROR;
1936                case PlaybackStateCompat.STATE_FAST_FORWARDING:
1937                    return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
1938                case PlaybackStateCompat.STATE_NONE:
1939                    return RCC_PLAYSTATE_NONE;
1940                case PlaybackStateCompat.STATE_PAUSED:
1941                    return RemoteControlClient.PLAYSTATE_PAUSED;
1942                case PlaybackStateCompat.STATE_PLAYING:
1943                    return RemoteControlClient.PLAYSTATE_PLAYING;
1944                case PlaybackStateCompat.STATE_REWINDING:
1945                    return RemoteControlClient.PLAYSTATE_REWINDING;
1946                case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
1947                    return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
1948                case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
1949                case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
1950                    return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
1951                case PlaybackStateCompat.STATE_STOPPED:
1952                    return RemoteControlClient.PLAYSTATE_STOPPED;
1953                default:
1954                    return -1;
1955            }
1956        }
1957
1958        int getRccTransportControlFlagsFromActions(long actions) {
1959            int transportControlFlags = 0;
1960            if ((actions & PlaybackStateCompat.ACTION_STOP) != 0) {
1961                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_STOP;
1962            }
1963            if ((actions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
1964                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
1965            }
1966            if ((actions & PlaybackStateCompat.ACTION_PLAY) != 0) {
1967                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
1968            }
1969            if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
1970                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
1971            }
1972            if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
1973                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
1974            }
1975            if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
1976                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
1977            }
1978            if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
1979                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
1980            }
1981            if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
1982                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
1983            }
1984            return transportControlFlags;
1985        }
1986
1987        @Override
1988        public void setMetadata(MediaMetadataCompat metadata) {
1989            if (metadata != null) {
1990                // Clones {@link MediaMetadataCompat} and scales down bitmaps if they are large.
1991                metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build();
1992            }
1993
1994            synchronized (mLock) {
1995                mMetadata = metadata;
1996            }
1997            sendMetadata(metadata);
1998            if (!mIsActive) {
1999                // Don't set metadata until after the rcc has been registered
2000                return;
2001            }
2002            RemoteControlClient.MetadataEditor editor = buildRccMetadata(
2003                    metadata == null ? null : metadata.getBundle());
2004            editor.apply();
2005        }
2006
2007        RemoteControlClient.MetadataEditor buildRccMetadata(Bundle metadata) {
2008            RemoteControlClient.MetadataEditor editor = mRcc.editMetadata(true);
2009            if (metadata == null) {
2010                return editor;
2011            }
2012            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ART)) {
2013                Bitmap art = metadata.getParcelable(MediaMetadataCompat.METADATA_KEY_ART);
2014                if (art != null) {
2015                    // Clone the bitmap to prevent it from being recycled by RCC.
2016                    art = art.copy(art.getConfig(), false);
2017                }
2018                editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, art);
2019            } else if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)) {
2020                // Fall back to album art if the track art wasn't available
2021                Bitmap art = metadata.getParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
2022                if (art != null) {
2023                    // Clone the bitmap to prevent it from being recycled by RCC.
2024                    art = art.copy(art.getConfig(), false);
2025                }
2026                editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, art);
2027            }
2028            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ALBUM)) {
2029                editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM,
2030                        metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
2031            }
2032            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST)) {
2033                editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
2034                        metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST));
2035            }
2036            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ARTIST)) {
2037                editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST,
2038                        metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
2039            }
2040            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_AUTHOR)) {
2041                editor.putString(MediaMetadataRetriever.METADATA_KEY_AUTHOR,
2042                        metadata.getString(MediaMetadataCompat.METADATA_KEY_AUTHOR));
2043            }
2044            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_COMPILATION)) {
2045                editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
2046                        metadata.getString(MediaMetadataCompat.METADATA_KEY_COMPILATION));
2047            }
2048            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_COMPOSER)) {
2049                editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPOSER,
2050                        metadata.getString(MediaMetadataCompat.METADATA_KEY_COMPOSER));
2051            }
2052            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_DATE)) {
2053                editor.putString(MediaMetadataRetriever.METADATA_KEY_DATE,
2054                        metadata.getString(MediaMetadataCompat.METADATA_KEY_DATE));
2055            }
2056            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER)) {
2057                editor.putLong(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
2058                        metadata.getLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER));
2059            }
2060            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
2061                editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
2062                        metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
2063            }
2064            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_GENRE)) {
2065                editor.putString(MediaMetadataRetriever.METADATA_KEY_GENRE,
2066                        metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE));
2067            }
2068            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_TITLE)) {
2069                editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE,
2070                        metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
2071            }
2072            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)) {
2073                editor.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
2074                        metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER));
2075            }
2076            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_WRITER)) {
2077                editor.putString(MediaMetadataRetriever.METADATA_KEY_WRITER,
2078                        metadata.getString(MediaMetadataCompat.METADATA_KEY_WRITER));
2079            }
2080            return editor;
2081        }
2082
2083        @Override
2084        public void setSessionActivity(PendingIntent pi) {
2085            synchronized (mLock) {
2086                mSessionActivity = pi;
2087            }
2088        }
2089
2090        @Override
2091        public void setMediaButtonReceiver(PendingIntent mbr) {
2092            // Do nothing, changing this is not supported before API 21.
2093        }
2094
2095        @Override
2096        public void setQueue(List<QueueItem> queue) {
2097            mQueue = queue;
2098            sendQueue(queue);
2099        }
2100
2101        @Override
2102        public void setQueueTitle(CharSequence title) {
2103            mQueueTitle = title;
2104            sendQueueTitle(title);
2105        }
2106
2107        @Override
2108        public Object getMediaSession() {
2109            return null;
2110        }
2111
2112        @Override
2113        public Object getRemoteControlClient() {
2114            return null;
2115        }
2116
2117        @Override
2118        public String getCallingPackage() {
2119            return null;
2120        }
2121
2122        @Override
2123        public void setRatingType(@RatingCompat.Style int type) {
2124            mRatingType = type;
2125        }
2126
2127        @Override
2128        public void setCaptioningEnabled(boolean enabled) {
2129            if (mCaptioningEnabled != enabled) {
2130                mCaptioningEnabled = enabled;
2131                sendCaptioningEnabled(enabled);
2132            }
2133        }
2134
2135        @Override
2136        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
2137            if (mRepeatMode != repeatMode) {
2138                mRepeatMode = repeatMode;
2139                sendRepeatMode(repeatMode);
2140            }
2141        }
2142
2143        @Override
2144        public void setShuffleModeEnabled(boolean enabled) {
2145            if (mShuffleModeEnabled != enabled) {
2146                mShuffleModeEnabled = enabled;
2147                sendShuffleModeEnabled(enabled);
2148            }
2149        }
2150
2151        @Override
2152        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
2153            if (mShuffleMode != shuffleMode) {
2154                mShuffleMode = shuffleMode;
2155                sendShuffleMode(shuffleMode);
2156            }
2157        }
2158
2159        @Override
2160        public void setExtras(Bundle extras) {
2161            mExtras = extras;
2162            sendExtras(extras);
2163        }
2164
2165        // Registers/unregisters components as needed.
2166        boolean update() {
2167            boolean registeredRcc = false;
2168            if (mIsActive) {
2169                // Register a MBR if it's supported, unregister it if support was removed.
2170                if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
2171                    registerMediaButtonEventReceiver(mMediaButtonReceiverIntent,
2172                            mMediaButtonReceiverComponentName);
2173                    mIsMbrRegistered = true;
2174                } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
2175                    unregisterMediaButtonEventReceiver(mMediaButtonReceiverIntent,
2176                            mMediaButtonReceiverComponentName);
2177                    mIsMbrRegistered = false;
2178                }
2179                // Register a RCC if it's supported, unregister it if support was removed.
2180                if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
2181                    mAudioManager.registerRemoteControlClient(mRcc);
2182                    mIsRccRegistered = true;
2183                    registeredRcc = true;
2184                } else if (mIsRccRegistered
2185                        && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
2186                    // RCC keeps the state while the system resets its state internally when
2187                    // we register RCC. Reset the state so that the states in RCC and the system
2188                    // are in sync when we re-register the RCC.
2189                    mRcc.setPlaybackState(0);
2190                    mAudioManager.unregisterRemoteControlClient(mRcc);
2191                    mIsRccRegistered = false;
2192                }
2193            } else {
2194                // When inactive remove any registered components.
2195                if (mIsMbrRegistered) {
2196                    unregisterMediaButtonEventReceiver(mMediaButtonReceiverIntent,
2197                            mMediaButtonReceiverComponentName);
2198                    mIsMbrRegistered = false;
2199                }
2200                if (mIsRccRegistered) {
2201                    // RCC keeps the state while the system resets its state internally when
2202                    // we register RCC. Reset the state so that the states in RCC and the system
2203                    // are in sync when we re-register the RCC.
2204                    mRcc.setPlaybackState(0);
2205                    mAudioManager.unregisterRemoteControlClient(mRcc);
2206                    mIsRccRegistered = false;
2207                }
2208            }
2209            return registeredRcc;
2210        }
2211
2212        void registerMediaButtonEventReceiver(PendingIntent mbrIntent, ComponentName mbrComponent) {
2213            mAudioManager.registerMediaButtonEventReceiver(mbrComponent);
2214        }
2215
2216        void unregisterMediaButtonEventReceiver(PendingIntent mbrIntent,
2217                ComponentName mbrComponent) {
2218            mAudioManager.unregisterMediaButtonEventReceiver(mbrComponent);
2219        }
2220
2221        void adjustVolume(int direction, int flags) {
2222            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
2223                if (mVolumeProvider != null) {
2224                    mVolumeProvider.onAdjustVolume(direction);
2225                }
2226            } else {
2227                mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
2228            }
2229        }
2230
2231        void setVolumeTo(int value, int flags) {
2232            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
2233                if (mVolumeProvider != null) {
2234                    mVolumeProvider.onSetVolumeTo(value);
2235                }
2236            } else {
2237                mAudioManager.setStreamVolume(mLocalStream, value, flags);
2238            }
2239        }
2240
2241        void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
2242            int size = mControllerCallbacks.beginBroadcast();
2243            for (int i = size - 1; i >= 0; i--) {
2244                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2245                try {
2246                    cb.onVolumeInfoChanged(info);
2247                } catch (RemoteException e) {
2248                }
2249            }
2250            mControllerCallbacks.finishBroadcast();
2251        }
2252
2253        private void sendSessionDestroyed() {
2254            int size = mControllerCallbacks.beginBroadcast();
2255            for (int i = size - 1; i >= 0; i--) {
2256                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2257                try {
2258                    cb.onSessionDestroyed();
2259                } catch (RemoteException e) {
2260                }
2261            }
2262            mControllerCallbacks.finishBroadcast();
2263            mControllerCallbacks.kill();
2264        }
2265
2266        private void sendEvent(String event, Bundle extras) {
2267            int size = mControllerCallbacks.beginBroadcast();
2268            for (int i = size - 1; i >= 0; i--) {
2269                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2270                try {
2271                    cb.onEvent(event, extras);
2272                } catch (RemoteException e) {
2273                }
2274            }
2275            mControllerCallbacks.finishBroadcast();
2276        }
2277
2278        private void sendState(PlaybackStateCompat state) {
2279            int size = mControllerCallbacks.beginBroadcast();
2280            for (int i = size - 1; i >= 0; i--) {
2281                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2282                try {
2283                    cb.onPlaybackStateChanged(state);
2284                } catch (RemoteException e) {
2285                }
2286            }
2287            mControllerCallbacks.finishBroadcast();
2288        }
2289
2290        private void sendMetadata(MediaMetadataCompat metadata) {
2291            int size = mControllerCallbacks.beginBroadcast();
2292            for (int i = size - 1; i >= 0; i--) {
2293                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2294                try {
2295                    cb.onMetadataChanged(metadata);
2296                } catch (RemoteException e) {
2297                }
2298            }
2299            mControllerCallbacks.finishBroadcast();
2300        }
2301
2302        private void sendQueue(List<QueueItem> queue) {
2303            int size = mControllerCallbacks.beginBroadcast();
2304            for (int i = size - 1; i >= 0; i--) {
2305                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2306                try {
2307                    cb.onQueueChanged(queue);
2308                } catch (RemoteException e) {
2309                }
2310            }
2311            mControllerCallbacks.finishBroadcast();
2312        }
2313
2314        private void sendQueueTitle(CharSequence queueTitle) {
2315            int size = mControllerCallbacks.beginBroadcast();
2316            for (int i = size - 1; i >= 0; i--) {
2317                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2318                try {
2319                    cb.onQueueTitleChanged(queueTitle);
2320                } catch (RemoteException e) {
2321                }
2322            }
2323            mControllerCallbacks.finishBroadcast();
2324        }
2325
2326        private void sendCaptioningEnabled(boolean enabled) {
2327            int size = mControllerCallbacks.beginBroadcast();
2328            for (int i = size - 1; i >= 0; i--) {
2329                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2330                try {
2331                    cb.onCaptioningEnabledChanged(enabled);
2332                } catch (RemoteException e) {
2333                }
2334            }
2335            mControllerCallbacks.finishBroadcast();
2336        }
2337
2338        private void sendRepeatMode(int repeatMode) {
2339            int size = mControllerCallbacks.beginBroadcast();
2340            for (int i = size - 1; i >= 0; i--) {
2341                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2342                try {
2343                    cb.onRepeatModeChanged(repeatMode);
2344                } catch (RemoteException e) {
2345                }
2346            }
2347            mControllerCallbacks.finishBroadcast();
2348        }
2349
2350        private void sendShuffleModeEnabled(boolean enabled) {
2351            int size = mControllerCallbacks.beginBroadcast();
2352            for (int i = size - 1; i >= 0; i--) {
2353                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2354                try {
2355                    cb.onShuffleModeChangedDeprecated(enabled);
2356                } catch (RemoteException e) {
2357                }
2358            }
2359            mControllerCallbacks.finishBroadcast();
2360        }
2361
2362        private void sendShuffleMode(int shuffleMode) {
2363            int size = mControllerCallbacks.beginBroadcast();
2364            for (int i = size - 1; i >= 0; i--) {
2365                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2366                try {
2367                    cb.onShuffleModeChanged(shuffleMode);
2368                } catch (RemoteException e) {
2369                }
2370            }
2371            mControllerCallbacks.finishBroadcast();
2372        }
2373
2374        private void sendExtras(Bundle extras) {
2375            int size = mControllerCallbacks.beginBroadcast();
2376            for (int i = size - 1; i >= 0; i--) {
2377                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2378                try {
2379                    cb.onExtrasChanged(extras);
2380                } catch (RemoteException e) {
2381                }
2382            }
2383            mControllerCallbacks.finishBroadcast();
2384        }
2385
2386        class MediaSessionStub extends IMediaSession.Stub {
2387            @Override
2388            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
2389                postToHandler(MessageHandler.MSG_COMMAND,
2390                        new Command(command, args, cb.mResultReceiver));
2391            }
2392
2393            @Override
2394            public boolean sendMediaButton(KeyEvent mediaButton) {
2395                boolean handlesMediaButtons =
2396                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
2397                if (handlesMediaButtons) {
2398                    postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
2399                }
2400                return handlesMediaButtons;
2401            }
2402
2403            @Override
2404            public void registerCallbackListener(IMediaControllerCallback cb) {
2405                // If this session is already destroyed tell the caller and
2406                // don't add them.
2407                if (mDestroyed) {
2408                    try {
2409                        cb.onSessionDestroyed();
2410                    } catch (Exception e) {
2411                        // ignored
2412                    }
2413                    return;
2414                }
2415                mControllerCallbacks.register(cb);
2416            }
2417
2418            @Override
2419            public void unregisterCallbackListener(IMediaControllerCallback cb) {
2420                mControllerCallbacks.unregister(cb);
2421            }
2422
2423            @Override
2424            public String getPackageName() {
2425                // mPackageName is final so doesn't need synchronize block
2426                return mPackageName;
2427            }
2428
2429            @Override
2430            public String getTag() {
2431                // mTag is final so doesn't need synchronize block
2432                return mTag;
2433            }
2434
2435            @Override
2436            public PendingIntent getLaunchPendingIntent() {
2437                synchronized (mLock) {
2438                    return mSessionActivity;
2439                }
2440            }
2441
2442            @Override
2443            @SessionFlags
2444            public long getFlags() {
2445                synchronized (mLock) {
2446                    return mFlags;
2447                }
2448            }
2449
2450            @Override
2451            public ParcelableVolumeInfo getVolumeAttributes() {
2452                int controlType;
2453                int max;
2454                int current;
2455                int stream;
2456                int volumeType;
2457                synchronized (mLock) {
2458                    volumeType = mVolumeType;
2459                    stream = mLocalStream;
2460                    VolumeProviderCompat vp = mVolumeProvider;
2461                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
2462                        controlType = vp.getVolumeControl();
2463                        max = vp.getMaxVolume();
2464                        current = vp.getCurrentVolume();
2465                    } else {
2466                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
2467                        max = mAudioManager.getStreamMaxVolume(stream);
2468                        current = mAudioManager.getStreamVolume(stream);
2469                    }
2470                }
2471                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
2472            }
2473
2474            @Override
2475            public void adjustVolume(int direction, int flags, String packageName) {
2476                MediaSessionImplBase.this.adjustVolume(direction, flags);
2477            }
2478
2479            @Override
2480            public void setVolumeTo(int value, int flags, String packageName) {
2481                MediaSessionImplBase.this.setVolumeTo(value, flags);
2482            }
2483
2484            @Override
2485            public void prepare() throws RemoteException {
2486                postToHandler(MessageHandler.MSG_PREPARE);
2487            }
2488
2489            @Override
2490            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2491                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
2492            }
2493
2494            @Override
2495            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
2496                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
2497            }
2498
2499            @Override
2500            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
2501                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
2502            }
2503
2504            @Override
2505            public void play() throws RemoteException {
2506                postToHandler(MessageHandler.MSG_PLAY);
2507            }
2508
2509            @Override
2510            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2511                postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
2512            }
2513
2514            @Override
2515            public void playFromSearch(String query, Bundle extras) throws RemoteException {
2516                postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
2517            }
2518
2519            @Override
2520            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
2521                postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
2522            }
2523
2524            @Override
2525            public void skipToQueueItem(long id) {
2526                postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
2527            }
2528
2529            @Override
2530            public void pause() throws RemoteException {
2531                postToHandler(MessageHandler.MSG_PAUSE);
2532            }
2533
2534            @Override
2535            public void stop() throws RemoteException {
2536                postToHandler(MessageHandler.MSG_STOP);
2537            }
2538
2539            @Override
2540            public void next() throws RemoteException {
2541                postToHandler(MessageHandler.MSG_NEXT);
2542            }
2543
2544            @Override
2545            public void previous() throws RemoteException {
2546                postToHandler(MessageHandler.MSG_PREVIOUS);
2547            }
2548
2549            @Override
2550            public void fastForward() throws RemoteException {
2551                postToHandler(MessageHandler.MSG_FAST_FORWARD);
2552            }
2553
2554            @Override
2555            public void rewind() throws RemoteException {
2556                postToHandler(MessageHandler.MSG_REWIND);
2557            }
2558
2559            @Override
2560            public void seekTo(long pos) throws RemoteException {
2561                postToHandler(MessageHandler.MSG_SEEK_TO, pos);
2562            }
2563
2564            @Override
2565            public void rate(RatingCompat rating) throws RemoteException {
2566                postToHandler(MessageHandler.MSG_RATE, rating);
2567            }
2568
2569            @Override
2570            public void setCaptioningEnabled(boolean enabled) throws RemoteException {
2571                postToHandler(MessageHandler.MSG_SET_CAPTIONING_ENABLED, enabled);
2572            }
2573
2574            @Override
2575            public void setRepeatMode(int repeatMode) throws RemoteException {
2576                postToHandler(MessageHandler.MSG_SET_REPEAT_MODE, repeatMode);
2577            }
2578
2579            @Override
2580            public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
2581                postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE_ENABLED, enabled);
2582            }
2583
2584            @Override
2585            public void setShuffleMode(int shuffleMode) throws RemoteException {
2586                postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE, shuffleMode);
2587            }
2588
2589            @Override
2590            public void sendCustomAction(String action, Bundle args)
2591                    throws RemoteException {
2592                postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
2593            }
2594
2595            @Override
2596            public MediaMetadataCompat getMetadata() {
2597                return mMetadata;
2598            }
2599
2600            @Override
2601            public PlaybackStateCompat getPlaybackState() {
2602                PlaybackStateCompat state;
2603                MediaMetadataCompat metadata;
2604                synchronized (mLock) {
2605                    state = mState;
2606                    metadata = mMetadata;
2607                }
2608                return getStateWithUpdatedPosition(state, metadata);
2609            }
2610
2611            @Override
2612            public List<QueueItem> getQueue() {
2613                synchronized (mLock) {
2614                    return mQueue;
2615                }
2616            }
2617
2618            @Override
2619            public void addQueueItem(MediaDescriptionCompat description) {
2620                postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM, description);
2621            }
2622
2623            @Override
2624            public void addQueueItemAt(MediaDescriptionCompat description, int index) {
2625                postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM_AT, description, index);
2626            }
2627
2628            @Override
2629            public void removeQueueItem(MediaDescriptionCompat description) {
2630                postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM, description);
2631            }
2632
2633            @Override
2634            public void removeQueueItemAt(int index) {
2635                postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM_AT, index);
2636            }
2637
2638            @Override
2639            public CharSequence getQueueTitle() {
2640                return mQueueTitle;
2641            }
2642
2643            @Override
2644            public Bundle getExtras() {
2645                synchronized (mLock) {
2646                    return mExtras;
2647                }
2648            }
2649
2650            @Override
2651            @RatingCompat.Style
2652            public int getRatingType() {
2653                return mRatingType;
2654            }
2655
2656            @Override
2657            public boolean isCaptioningEnabled() {
2658                return mCaptioningEnabled;
2659            }
2660
2661            @Override
2662            @PlaybackStateCompat.RepeatMode
2663            public int getRepeatMode() {
2664                return mRepeatMode;
2665            }
2666
2667            @Override
2668            public boolean isShuffleModeEnabledDeprecated() {
2669                return mShuffleModeEnabled;
2670            }
2671
2672            @Override
2673            @PlaybackStateCompat.ShuffleMode
2674            public int getShuffleMode() {
2675                return mShuffleMode;
2676            }
2677
2678            @Override
2679            public boolean isTransportControlEnabled() {
2680                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
2681            }
2682        }
2683
2684        private static final class Command {
2685            public final String command;
2686            public final Bundle extras;
2687            public final ResultReceiver stub;
2688
2689            public Command(String command, Bundle extras, ResultReceiver stub) {
2690                this.command = command;
2691                this.extras = extras;
2692                this.stub = stub;
2693            }
2694        }
2695
2696        class MessageHandler extends Handler {
2697
2698            private static final int MSG_COMMAND = 1;
2699            private static final int MSG_ADJUST_VOLUME = 2;
2700            private static final int MSG_PREPARE = 3;
2701            private static final int MSG_PREPARE_MEDIA_ID = 4;
2702            private static final int MSG_PREPARE_SEARCH = 5;
2703            private static final int MSG_PREPARE_URI = 6;
2704            private static final int MSG_PLAY = 7;
2705            private static final int MSG_PLAY_MEDIA_ID = 8;
2706            private static final int MSG_PLAY_SEARCH = 9;
2707            private static final int MSG_PLAY_URI = 10;
2708            private static final int MSG_SKIP_TO_ITEM = 11;
2709            private static final int MSG_PAUSE = 12;
2710            private static final int MSG_STOP = 13;
2711            private static final int MSG_NEXT = 14;
2712            private static final int MSG_PREVIOUS = 15;
2713            private static final int MSG_FAST_FORWARD = 16;
2714            private static final int MSG_REWIND = 17;
2715            private static final int MSG_SEEK_TO = 18;
2716            private static final int MSG_RATE = 19;
2717            private static final int MSG_CUSTOM_ACTION = 20;
2718            private static final int MSG_MEDIA_BUTTON = 21;
2719            private static final int MSG_SET_VOLUME = 22;
2720            private static final int MSG_SET_REPEAT_MODE = 23;
2721            private static final int MSG_SET_SHUFFLE_MODE_ENABLED = 24;
2722            private static final int MSG_ADD_QUEUE_ITEM = 25;
2723            private static final int MSG_ADD_QUEUE_ITEM_AT = 26;
2724            private static final int MSG_REMOVE_QUEUE_ITEM = 27;
2725            private static final int MSG_REMOVE_QUEUE_ITEM_AT = 28;
2726            private static final int MSG_SET_CAPTIONING_ENABLED = 29;
2727            private static final int MSG_SET_SHUFFLE_MODE = 30;
2728
2729            // KeyEvent constants only available on API 11+
2730            private static final int KEYCODE_MEDIA_PAUSE = 127;
2731            private static final int KEYCODE_MEDIA_PLAY = 126;
2732
2733            public MessageHandler(Looper looper) {
2734                super(looper);
2735            }
2736
2737            public void post(int what, Object obj, Bundle bundle) {
2738                Message msg = obtainMessage(what, obj);
2739                msg.setData(bundle);
2740                msg.sendToTarget();
2741            }
2742
2743            public void post(int what, Object obj) {
2744                obtainMessage(what, obj).sendToTarget();
2745            }
2746
2747            public void post(int what) {
2748                post(what, null);
2749            }
2750
2751            public void post(int what, Object obj, int arg1) {
2752                obtainMessage(what, arg1, 0, obj).sendToTarget();
2753            }
2754
2755            @Override
2756            public void handleMessage(Message msg) {
2757                MediaSessionCompat.Callback cb = mCallback;
2758                if (cb == null) {
2759                    return;
2760                }
2761                switch (msg.what) {
2762                    case MSG_COMMAND:
2763                        Command cmd = (Command) msg.obj;
2764                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2765                        break;
2766                    case MSG_MEDIA_BUTTON:
2767                        KeyEvent keyEvent = (KeyEvent) msg.obj;
2768                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2769                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2770                        // Let the Callback handle events first before using the default behavior
2771                        if (!cb.onMediaButtonEvent(intent)) {
2772                            onMediaButtonEvent(keyEvent, cb);
2773                        }
2774                        break;
2775                    case MSG_PREPARE:
2776                        cb.onPrepare();
2777                        break;
2778                    case MSG_PREPARE_MEDIA_ID:
2779                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2780                        break;
2781                    case MSG_PREPARE_SEARCH:
2782                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2783                        break;
2784                    case MSG_PREPARE_URI:
2785                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2786                        break;
2787                    case MSG_PLAY:
2788                        cb.onPlay();
2789                        break;
2790                    case MSG_PLAY_MEDIA_ID:
2791                        cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2792                        break;
2793                    case MSG_PLAY_SEARCH:
2794                        cb.onPlayFromSearch((String) msg.obj, msg.getData());
2795                        break;
2796                    case MSG_PLAY_URI:
2797                        cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2798                        break;
2799                    case MSG_SKIP_TO_ITEM:
2800                        cb.onSkipToQueueItem((Long) msg.obj);
2801                        break;
2802                    case MSG_PAUSE:
2803                        cb.onPause();
2804                        break;
2805                    case MSG_STOP:
2806                        cb.onStop();
2807                        break;
2808                    case MSG_NEXT:
2809                        cb.onSkipToNext();
2810                        break;
2811                    case MSG_PREVIOUS:
2812                        cb.onSkipToPrevious();
2813                        break;
2814                    case MSG_FAST_FORWARD:
2815                        cb.onFastForward();
2816                        break;
2817                    case MSG_REWIND:
2818                        cb.onRewind();
2819                        break;
2820                    case MSG_SEEK_TO:
2821                        cb.onSeekTo((Long) msg.obj);
2822                        break;
2823                    case MSG_RATE:
2824                        cb.onSetRating((RatingCompat) msg.obj);
2825                        break;
2826                    case MSG_CUSTOM_ACTION:
2827                        cb.onCustomAction((String) msg.obj, msg.getData());
2828                        break;
2829                    case MSG_ADD_QUEUE_ITEM:
2830                        cb.onAddQueueItem((MediaDescriptionCompat) msg.obj);
2831                        break;
2832                    case MSG_ADD_QUEUE_ITEM_AT:
2833                        cb.onAddQueueItem((MediaDescriptionCompat) msg.obj, msg.arg1);
2834                        break;
2835                    case MSG_REMOVE_QUEUE_ITEM:
2836                        cb.onRemoveQueueItem((MediaDescriptionCompat) msg.obj);
2837                        break;
2838                    case MSG_REMOVE_QUEUE_ITEM_AT:
2839                        if (mQueue != null) {
2840                            QueueItem item = (msg.arg1 >= 0 && msg.arg1 < mQueue.size())
2841                                    ? mQueue.get(msg.arg1) : null;
2842                            if (item != null) {
2843                                cb.onRemoveQueueItem(item.getDescription());
2844                            }
2845                        }
2846                        break;
2847                    case MSG_ADJUST_VOLUME:
2848                        adjustVolume(msg.arg1, 0);
2849                        break;
2850                    case MSG_SET_VOLUME:
2851                        setVolumeTo(msg.arg1, 0);
2852                        break;
2853                    case MSG_SET_CAPTIONING_ENABLED:
2854                        cb.onSetCaptioningEnabled((boolean) msg.obj);
2855                        break;
2856                    case MSG_SET_REPEAT_MODE:
2857                        cb.onSetRepeatMode(msg.arg1);
2858                        break;
2859                    case MSG_SET_SHUFFLE_MODE_ENABLED:
2860                        cb.onSetShuffleModeEnabled((boolean) msg.obj);
2861                        break;
2862                    case MSG_SET_SHUFFLE_MODE:
2863                        cb.onSetShuffleMode(msg.arg1);
2864                        break;
2865                }
2866            }
2867
2868            private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2869                if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2870                    return;
2871                }
2872                long validActions = mState == null ? 0 : mState.getActions();
2873                switch (ke.getKeyCode()) {
2874                    // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2875                    case KEYCODE_MEDIA_PLAY:
2876                        if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2877                            cb.onPlay();
2878                        }
2879                        break;
2880                    // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2881                    case KEYCODE_MEDIA_PAUSE:
2882                        if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2883                            cb.onPause();
2884                        }
2885                        break;
2886                    case KeyEvent.KEYCODE_MEDIA_NEXT:
2887                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2888                            cb.onSkipToNext();
2889                        }
2890                        break;
2891                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2892                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2893                            cb.onSkipToPrevious();
2894                        }
2895                        break;
2896                    case KeyEvent.KEYCODE_MEDIA_STOP:
2897                        if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2898                            cb.onStop();
2899                        }
2900                        break;
2901                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2902                        if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2903                            cb.onFastForward();
2904                        }
2905                        break;
2906                    case KeyEvent.KEYCODE_MEDIA_REWIND:
2907                        if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2908                            cb.onRewind();
2909                        }
2910                        break;
2911                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2912                    case KeyEvent.KEYCODE_HEADSETHOOK:
2913                        boolean isPlaying = mState != null
2914                                && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2915                        boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2916                                | PlaybackStateCompat.ACTION_PLAY)) != 0;
2917                        boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2918                                | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2919                        if (isPlaying && canPause) {
2920                            cb.onPause();
2921                        } else if (!isPlaying && canPlay) {
2922                            cb.onPlay();
2923                        }
2924                        break;
2925                }
2926            }
2927        }
2928    }
2929
2930    @RequiresApi(18)
2931    static class MediaSessionImplApi18 extends MediaSessionImplBase {
2932        private static boolean sIsMbrPendingIntentSupported = true;
2933
2934        MediaSessionImplApi18(Context context, String tag, ComponentName mbrComponent,
2935                PendingIntent mbrIntent) {
2936            super(context, tag, mbrComponent, mbrIntent);
2937        }
2938
2939        @Override
2940        public void setCallback(Callback callback, Handler handler) {
2941            super.setCallback(callback, handler);
2942            if (callback == null) {
2943                mRcc.setPlaybackPositionUpdateListener(null);
2944            } else {
2945                RemoteControlClient.OnPlaybackPositionUpdateListener listener =
2946                        new RemoteControlClient.OnPlaybackPositionUpdateListener() {
2947                            @Override
2948                            public void onPlaybackPositionUpdate(long newPositionMs) {
2949                                postToHandler(MessageHandler.MSG_SEEK_TO, newPositionMs);
2950                            }
2951                        };
2952                mRcc.setPlaybackPositionUpdateListener(listener);
2953            }
2954        }
2955
2956        @Override
2957        void setRccState(PlaybackStateCompat state) {
2958            long position = state.getPosition();
2959            float speed = state.getPlaybackSpeed();
2960            long updateTime = state.getLastPositionUpdateTime();
2961            long currTime = SystemClock.elapsedRealtime();
2962            if (state.getState() == PlaybackStateCompat.STATE_PLAYING && position > 0) {
2963                long diff = 0;
2964                if (updateTime > 0) {
2965                    diff = currTime - updateTime;
2966                    if (speed > 0 && speed != 1f) {
2967                        diff = (long) (diff * speed);
2968                    }
2969                }
2970                position += diff;
2971            }
2972            mRcc.setPlaybackState(getRccStateFromState(state.getState()), position, speed);
2973        }
2974
2975        @Override
2976        int getRccTransportControlFlagsFromActions(long actions) {
2977            int transportControlFlags = super.getRccTransportControlFlagsFromActions(actions);
2978            if ((actions & PlaybackStateCompat.ACTION_SEEK_TO) != 0) {
2979                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
2980            }
2981            return transportControlFlags;
2982        }
2983
2984        @Override
2985        void registerMediaButtonEventReceiver(PendingIntent mbrIntent, ComponentName mbrComponent) {
2986            // Some Android implementations are not able to register a media button event receiver
2987            // using a PendingIntent but need a ComponentName instead. These will raise a
2988            // NullPointerException.
2989            if (sIsMbrPendingIntentSupported) {
2990                try {
2991                    mAudioManager.registerMediaButtonEventReceiver(mbrIntent);
2992                } catch (NullPointerException e) {
2993                    Log.w(TAG, "Unable to register media button event receiver with "
2994                            + "PendingIntent, falling back to ComponentName.");
2995                    sIsMbrPendingIntentSupported = false;
2996                }
2997            }
2998
2999            if (!sIsMbrPendingIntentSupported) {
3000                super.registerMediaButtonEventReceiver(mbrIntent, mbrComponent);
3001            }
3002        }
3003
3004        @Override
3005        void unregisterMediaButtonEventReceiver(PendingIntent mbrIntent,
3006                ComponentName mbrComponent) {
3007            if (sIsMbrPendingIntentSupported) {
3008                mAudioManager.unregisterMediaButtonEventReceiver(mbrIntent);
3009            } else {
3010                super.unregisterMediaButtonEventReceiver(mbrIntent, mbrComponent);
3011            }
3012        }
3013    }
3014
3015    @RequiresApi(19)
3016    static class MediaSessionImplApi19 extends MediaSessionImplApi18 {
3017        MediaSessionImplApi19(Context context, String tag, ComponentName mbrComponent,
3018                PendingIntent mbrIntent) {
3019            super(context, tag, mbrComponent, mbrIntent);
3020        }
3021
3022        @Override
3023        public void setCallback(Callback callback, Handler handler) {
3024            super.setCallback(callback, handler);
3025            if (callback == null) {
3026                mRcc.setMetadataUpdateListener(null);
3027            } else {
3028                RemoteControlClient.OnMetadataUpdateListener listener =
3029                        new RemoteControlClient.OnMetadataUpdateListener() {
3030                            @Override
3031                            public void onMetadataUpdate(int key, Object newValue) {
3032                                if (key == MediaMetadataEditor.RATING_KEY_BY_USER
3033                                        && newValue instanceof Rating) {
3034                                    postToHandler(MessageHandler.MSG_RATE,
3035                                            RatingCompat.fromRating(newValue));
3036                                }
3037                            }
3038                        };
3039                mRcc.setMetadataUpdateListener(listener);
3040            }
3041        }
3042
3043        @Override
3044        int getRccTransportControlFlagsFromActions(long actions) {
3045            int transportControlFlags = super.getRccTransportControlFlagsFromActions(actions);
3046            if ((actions & PlaybackStateCompat.ACTION_SET_RATING) != 0) {
3047                transportControlFlags |= RemoteControlClient.FLAG_KEY_MEDIA_RATING;
3048            }
3049            return transportControlFlags;
3050        }
3051
3052        @Override
3053        RemoteControlClient.MetadataEditor buildRccMetadata(Bundle metadata) {
3054            RemoteControlClient.MetadataEditor editor = super.buildRccMetadata(metadata);
3055            long actions = mState == null ? 0 : mState.getActions();
3056            if ((actions & PlaybackStateCompat.ACTION_SET_RATING) != 0) {
3057                editor.addEditableKey(RemoteControlClient.MetadataEditor.RATING_KEY_BY_USER);
3058            }
3059
3060            if (metadata == null) {
3061                return editor;
3062            }
3063            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_YEAR)) {
3064                editor.putLong(MediaMetadataRetriever.METADATA_KEY_YEAR,
3065                        metadata.getLong(MediaMetadataCompat.METADATA_KEY_YEAR));
3066            }
3067            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_RATING)) {
3068                editor.putObject(MediaMetadataEditor.RATING_KEY_BY_OTHERS,
3069                        metadata.getParcelable(MediaMetadataCompat.METADATA_KEY_RATING));
3070            }
3071            if (metadata.containsKey(MediaMetadataCompat.METADATA_KEY_USER_RATING)) {
3072                editor.putObject(MediaMetadataEditor.RATING_KEY_BY_USER,
3073                        metadata.getParcelable(MediaMetadataCompat.METADATA_KEY_USER_RATING));
3074            }
3075            return editor;
3076        }
3077    }
3078
3079    @RequiresApi(21)
3080    static class MediaSessionImplApi21 implements MediaSessionImpl {
3081        private final Object mSessionObj;
3082        private final Token mToken;
3083
3084        private boolean mDestroyed = false;
3085        private final RemoteCallbackList<IMediaControllerCallback> mExtraControllerCallbacks =
3086                new RemoteCallbackList<>();
3087
3088        private PlaybackStateCompat mPlaybackState;
3089        private List<QueueItem> mQueue;
3090        private MediaMetadataCompat mMetadata;
3091        @RatingCompat.Style int mRatingType;
3092        boolean mCaptioningEnabled;
3093        @PlaybackStateCompat.RepeatMode int mRepeatMode;
3094        boolean mShuffleModeEnabled;
3095        @PlaybackStateCompat.ShuffleMode int mShuffleMode;
3096
3097        public MediaSessionImplApi21(Context context, String tag) {
3098            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
3099            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
3100                    new ExtraSession());
3101        }
3102
3103        public MediaSessionImplApi21(Object mediaSession) {
3104            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
3105            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
3106                    new ExtraSession());
3107        }
3108
3109        @Override
3110        public void setCallback(Callback callback, Handler handler) {
3111            MediaSessionCompatApi21.setCallback(mSessionObj,
3112                    callback == null ? null : callback.mCallbackObj, handler);
3113            if (callback != null) {
3114                callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
3115            }
3116        }
3117
3118        @Override
3119        public void setFlags(@SessionFlags int flags) {
3120            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
3121        }
3122
3123        @Override
3124        public void setPlaybackToLocal(int stream) {
3125            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
3126        }
3127
3128        @Override
3129        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
3130            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
3131                    volumeProvider.getVolumeProvider());
3132        }
3133
3134        @Override
3135        public void setActive(boolean active) {
3136            MediaSessionCompatApi21.setActive(mSessionObj, active);
3137        }
3138
3139        @Override
3140        public boolean isActive() {
3141            return MediaSessionCompatApi21.isActive(mSessionObj);
3142        }
3143
3144        @Override
3145        public void sendSessionEvent(String event, Bundle extras) {
3146            if (android.os.Build.VERSION.SDK_INT < 23) {
3147                int size = mExtraControllerCallbacks.beginBroadcast();
3148                for (int i = size - 1; i >= 0; i--) {
3149                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3150                    try {
3151                        cb.onEvent(event, extras);
3152                    } catch (RemoteException e) {
3153                    }
3154                }
3155                mExtraControllerCallbacks.finishBroadcast();
3156            }
3157            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
3158        }
3159
3160        @Override
3161        public void release() {
3162            mDestroyed = true;
3163            MediaSessionCompatApi21.release(mSessionObj);
3164        }
3165
3166        @Override
3167        public Token getSessionToken() {
3168            return mToken;
3169        }
3170
3171        @Override
3172        public void setPlaybackState(PlaybackStateCompat state) {
3173            mPlaybackState = state;
3174            int size = mExtraControllerCallbacks.beginBroadcast();
3175            for (int i = size - 1; i >= 0; i--) {
3176                IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3177                try {
3178                    cb.onPlaybackStateChanged(state);
3179                } catch (RemoteException e) {
3180                }
3181            }
3182            mExtraControllerCallbacks.finishBroadcast();
3183            MediaSessionCompatApi21.setPlaybackState(mSessionObj,
3184                    state == null ? null : state.getPlaybackState());
3185        }
3186
3187        @Override
3188        public void setMetadata(MediaMetadataCompat metadata) {
3189            mMetadata = metadata;
3190            MediaSessionCompatApi21.setMetadata(mSessionObj,
3191                    metadata == null ? null : metadata.getMediaMetadata());
3192        }
3193
3194        @Override
3195        public void setSessionActivity(PendingIntent pi) {
3196            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
3197        }
3198
3199        @Override
3200        public void setMediaButtonReceiver(PendingIntent mbr) {
3201            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
3202        }
3203
3204        @Override
3205        public void setQueue(List<QueueItem> queue) {
3206            mQueue = queue;
3207            List<Object> queueObjs = null;
3208            if (queue != null) {
3209                queueObjs = new ArrayList<>();
3210                for (QueueItem item : queue) {
3211                    queueObjs.add(item.getQueueItem());
3212                }
3213            }
3214            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
3215        }
3216
3217        @Override
3218        public void setQueueTitle(CharSequence title) {
3219            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
3220        }
3221
3222        @Override
3223        public void setRatingType(@RatingCompat.Style int type) {
3224            if (android.os.Build.VERSION.SDK_INT < 22) {
3225                mRatingType = type;
3226            } else {
3227                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
3228            }
3229        }
3230
3231        @Override
3232        public void setCaptioningEnabled(boolean enabled) {
3233            if (mCaptioningEnabled != enabled) {
3234                mCaptioningEnabled = enabled;
3235                int size = mExtraControllerCallbacks.beginBroadcast();
3236                for (int i = size - 1; i >= 0; i--) {
3237                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3238                    try {
3239                        cb.onCaptioningEnabledChanged(enabled);
3240                    } catch (RemoteException e) {
3241                    }
3242                }
3243                mExtraControllerCallbacks.finishBroadcast();
3244            }
3245        }
3246
3247        @Override
3248        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
3249            if (mRepeatMode != repeatMode) {
3250                mRepeatMode = repeatMode;
3251                int size = mExtraControllerCallbacks.beginBroadcast();
3252                for (int i = size - 1; i >= 0; i--) {
3253                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3254                    try {
3255                        cb.onRepeatModeChanged(repeatMode);
3256                    } catch (RemoteException e) {
3257                    }
3258                }
3259                mExtraControllerCallbacks.finishBroadcast();
3260            }
3261        }
3262
3263        @Override
3264        public void setShuffleModeEnabled(boolean enabled) {
3265            if (mShuffleModeEnabled != enabled) {
3266                mShuffleModeEnabled = enabled;
3267                int size = mExtraControllerCallbacks.beginBroadcast();
3268                for (int i = size - 1; i >= 0; i--) {
3269                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3270                    try {
3271                        cb.onShuffleModeChangedDeprecated(enabled);
3272                    } catch (RemoteException e) {
3273                    }
3274                }
3275                mExtraControllerCallbacks.finishBroadcast();
3276            }
3277        }
3278
3279        @Override
3280        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
3281            if (mShuffleMode != shuffleMode) {
3282                mShuffleMode = shuffleMode;
3283                int size = mExtraControllerCallbacks.beginBroadcast();
3284                for (int i = size - 1; i >= 0; i--) {
3285                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
3286                    try {
3287                        cb.onShuffleModeChanged(shuffleMode);
3288                    } catch (RemoteException e) {
3289                    }
3290                }
3291                mExtraControllerCallbacks.finishBroadcast();
3292            }
3293        }
3294
3295        @Override
3296        public void setExtras(Bundle extras) {
3297            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
3298        }
3299
3300        @Override
3301        public Object getMediaSession() {
3302            return mSessionObj;
3303        }
3304
3305        @Override
3306        public Object getRemoteControlClient() {
3307            return null;
3308        }
3309
3310        @Override
3311        public String getCallingPackage() {
3312            if (android.os.Build.VERSION.SDK_INT < 24) {
3313                return null;
3314            } else {
3315                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
3316            }
3317        }
3318
3319        class ExtraSession extends IMediaSession.Stub {
3320            @Override
3321            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
3322                // Will not be called.
3323                throw new AssertionError();
3324            }
3325
3326            @Override
3327            public boolean sendMediaButton(KeyEvent mediaButton) {
3328                // Will not be called.
3329                throw new AssertionError();
3330            }
3331
3332            @Override
3333            public void registerCallbackListener(IMediaControllerCallback cb) {
3334                if (!mDestroyed) {
3335                    mExtraControllerCallbacks.register(cb);
3336                }
3337            }
3338
3339            @Override
3340            public void unregisterCallbackListener(IMediaControllerCallback cb) {
3341                mExtraControllerCallbacks.unregister(cb);
3342            }
3343
3344            @Override
3345            public String getPackageName() {
3346                // Will not be called.
3347                throw new AssertionError();
3348            }
3349
3350            @Override
3351            public String getTag() {
3352                // Will not be called.
3353                throw new AssertionError();
3354            }
3355
3356            @Override
3357            public PendingIntent getLaunchPendingIntent() {
3358                // Will not be called.
3359                throw new AssertionError();
3360            }
3361
3362            @Override
3363            @SessionFlags
3364            public long getFlags() {
3365                // Will not be called.
3366                throw new AssertionError();
3367            }
3368
3369            @Override
3370            public ParcelableVolumeInfo getVolumeAttributes() {
3371                // Will not be called.
3372                throw new AssertionError();
3373            }
3374
3375            @Override
3376            public void adjustVolume(int direction, int flags, String packageName) {
3377                // Will not be called.
3378                throw new AssertionError();
3379            }
3380
3381            @Override
3382            public void setVolumeTo(int value, int flags, String packageName) {
3383                // Will not be called.
3384                throw new AssertionError();
3385            }
3386
3387            @Override
3388            public void prepare() throws RemoteException {
3389                // Will not be called.
3390                throw new AssertionError();
3391            }
3392
3393            @Override
3394            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
3395                // Will not be called.
3396                throw new AssertionError();
3397            }
3398
3399            @Override
3400            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
3401                // Will not be called.
3402                throw new AssertionError();
3403            }
3404
3405            @Override
3406            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
3407                // Will not be called.
3408                throw new AssertionError();
3409            }
3410
3411            @Override
3412            public void play() throws RemoteException {
3413                // Will not be called.
3414                throw new AssertionError();
3415            }
3416
3417            @Override
3418            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
3419                // Will not be called.
3420                throw new AssertionError();
3421            }
3422
3423            @Override
3424            public void playFromSearch(String query, Bundle extras) throws RemoteException {
3425                // Will not be called.
3426                throw new AssertionError();
3427            }
3428
3429            @Override
3430            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
3431                // Will not be called.
3432                throw new AssertionError();
3433            }
3434
3435            @Override
3436            public void skipToQueueItem(long id) {
3437                // Will not be called.
3438                throw new AssertionError();
3439            }
3440
3441            @Override
3442            public void pause() throws RemoteException {
3443                // Will not be called.
3444                throw new AssertionError();
3445            }
3446
3447            @Override
3448            public void stop() throws RemoteException {
3449                // Will not be called.
3450                throw new AssertionError();
3451            }
3452
3453            @Override
3454            public void next() throws RemoteException {
3455                // Will not be called.
3456                throw new AssertionError();
3457            }
3458
3459            @Override
3460            public void previous() throws RemoteException {
3461                // Will not be called.
3462                throw new AssertionError();
3463            }
3464
3465            @Override
3466            public void fastForward() throws RemoteException {
3467                // Will not be called.
3468                throw new AssertionError();
3469            }
3470
3471            @Override
3472            public void rewind() throws RemoteException {
3473                // Will not be called.
3474                throw new AssertionError();
3475            }
3476
3477            @Override
3478            public void seekTo(long pos) throws RemoteException {
3479                // Will not be called.
3480                throw new AssertionError();
3481            }
3482
3483            @Override
3484            public void rate(RatingCompat rating) throws RemoteException {
3485                // Will not be called.
3486                throw new AssertionError();
3487            }
3488
3489            @Override
3490            public void setCaptioningEnabled(boolean enabled) throws RemoteException {
3491                // Will not be called.
3492                throw new AssertionError();
3493            }
3494
3495            @Override
3496            public void setRepeatMode(int repeatMode) throws RemoteException {
3497                // Will not be called.
3498                throw new AssertionError();
3499            }
3500
3501            @Override
3502            public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
3503                // Will not be called.
3504                throw new AssertionError();
3505            }
3506
3507            @Override
3508            public void setShuffleMode(int shuffleMode) throws RemoteException {
3509                // Will not be called.
3510                throw new AssertionError();
3511            }
3512
3513            @Override
3514            public void sendCustomAction(String action, Bundle args) throws RemoteException {
3515                // Will not be called.
3516                throw new AssertionError();
3517            }
3518
3519            @Override
3520            public MediaMetadataCompat getMetadata() {
3521                // Will not be called.
3522                throw new AssertionError();
3523            }
3524
3525            @Override
3526            public PlaybackStateCompat getPlaybackState() {
3527                return getStateWithUpdatedPosition(mPlaybackState, mMetadata);
3528            }
3529
3530            @Override
3531            public List<QueueItem> getQueue() {
3532                // Will not be called.
3533                return null;
3534            }
3535
3536            @Override
3537            public void addQueueItem(MediaDescriptionCompat descriptionCompat) {
3538                // Will not be called.
3539                throw new AssertionError();
3540            }
3541
3542            @Override
3543            public void addQueueItemAt(MediaDescriptionCompat descriptionCompat, int index) {
3544                // Will not be called.
3545                throw new AssertionError();
3546            }
3547
3548            @Override
3549            public void removeQueueItem(MediaDescriptionCompat description) {
3550                // Will not be called.
3551                throw new AssertionError();
3552            }
3553
3554            @Override
3555            public void removeQueueItemAt(int index) {
3556                // Will not be called.
3557                throw new AssertionError();
3558            }
3559
3560            @Override
3561            public CharSequence getQueueTitle() {
3562                // Will not be called.
3563                throw new AssertionError();
3564            }
3565
3566            @Override
3567            public Bundle getExtras() {
3568                // Will not be called.
3569                throw new AssertionError();
3570            }
3571
3572            @Override
3573            @RatingCompat.Style
3574            public int getRatingType() {
3575                return mRatingType;
3576            }
3577
3578            @Override
3579            public boolean isCaptioningEnabled() {
3580                return mCaptioningEnabled;
3581            }
3582
3583            @Override
3584            @PlaybackStateCompat.RepeatMode
3585            public int getRepeatMode() {
3586                return mRepeatMode;
3587            }
3588
3589            @Override
3590            public boolean isShuffleModeEnabledDeprecated() {
3591                return mShuffleModeEnabled;
3592            }
3593
3594            @Override
3595            @PlaybackStateCompat.ShuffleMode
3596            public int getShuffleMode() {
3597                return mShuffleMode;
3598            }
3599
3600            @Override
3601            public boolean isTransportControlEnabled() {
3602                // Will not be called.
3603                throw new AssertionError();
3604            }
3605        }
3606    }
3607}
3608