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