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