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