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