MediaSessionCompat.java revision 165261b30365e1186c7a754d946b87ca65e52b83
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,
945                                (IBinder) impl.getSessionToken().getExtraBinder());
946                        cb.send(0, result);
947                    }
948                } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM)) {
949                    extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
950                    Callback.this.onAddQueueItem(
951                            (MediaDescriptionCompat) extras.getParcelable(
952                                    MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION));
953                } else if (command.equals(MediaControllerCompat.COMMAND_ADD_QUEUE_ITEM_AT)) {
954                    extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
955                    Callback.this.onAddQueueItem(
956                            (MediaDescriptionCompat) extras.getParcelable(
957                                    MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION),
958                            extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX));
959                } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM)) {
960                    extras.setClassLoader(MediaDescriptionCompat.class.getClassLoader());
961                    Callback.this.onRemoveQueueItem(
962                            (MediaDescriptionCompat) extras.getParcelable(
963                                    MediaControllerCompat.COMMAND_ARGUMENT_MEDIA_DESCRIPTION));
964                } else if (command.equals(MediaControllerCompat.COMMAND_REMOVE_QUEUE_ITEM_AT)) {
965                    Callback.this.onRemoveQueueItemAt(
966                            extras.getInt(MediaControllerCompat.COMMAND_ARGUMENT_INDEX));
967                } else {
968                    Callback.this.onCommand(command, extras, cb);
969                }
970            }
971
972            @Override
973            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
974                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
975            }
976
977            @Override
978            public void onPlay() {
979                Callback.this.onPlay();
980            }
981
982            @Override
983            public void onPlayFromMediaId(String mediaId, Bundle extras) {
984                Callback.this.onPlayFromMediaId(mediaId, extras);
985            }
986
987            @Override
988            public void onPlayFromSearch(String search, Bundle extras) {
989                Callback.this.onPlayFromSearch(search, extras);
990            }
991
992            @Override
993            public void onSkipToQueueItem(long id) {
994                Callback.this.onSkipToQueueItem(id);
995            }
996
997            @Override
998            public void onPause() {
999                Callback.this.onPause();
1000            }
1001
1002            @Override
1003            public void onSkipToNext() {
1004                Callback.this.onSkipToNext();
1005            }
1006
1007            @Override
1008            public void onSkipToPrevious() {
1009                Callback.this.onSkipToPrevious();
1010            }
1011
1012            @Override
1013            public void onFastForward() {
1014                Callback.this.onFastForward();
1015            }
1016
1017            @Override
1018            public void onRewind() {
1019                Callback.this.onRewind();
1020            }
1021
1022            @Override
1023            public void onStop() {
1024                Callback.this.onStop();
1025            }
1026
1027            @Override
1028            public void onSeekTo(long pos) {
1029                Callback.this.onSeekTo(pos);
1030            }
1031
1032            @Override
1033            public void onSetRating(Object ratingObj) {
1034                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
1035            }
1036
1037            @Override
1038            public void onCustomAction(String action, Bundle extras) {
1039                if (action.equals(ACTION_PLAY_FROM_URI)) {
1040                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
1041                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
1042                    Callback.this.onPlayFromUri(uri, bundle);
1043                } else if (action.equals(ACTION_PREPARE)) {
1044                    Callback.this.onPrepare();
1045                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
1046                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
1047                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1048                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
1049                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
1050                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
1051                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1052                    Callback.this.onPrepareFromSearch(query, bundle);
1053                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
1054                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
1055                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
1056                    Callback.this.onPrepareFromUri(uri, bundle);
1057                } else if (action.equals(ACTION_SET_REPEAT_MODE)) {
1058                    int repeatMode = extras.getInt(ACTION_ARGUMENT_REPEAT_MODE);
1059                    Callback.this.onSetRepeatMode(repeatMode);
1060                } else if (action.equals(ACTION_SET_SHUFFLE_MODE_ENABLED)) {
1061                    boolean enabled = extras.getBoolean(ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED);
1062                    Callback.this.onSetShuffleModeEnabled(enabled);
1063                } else {
1064                    Callback.this.onCustomAction(action, extras);
1065                }
1066            }
1067        }
1068
1069        private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
1070
1071            StubApi23() {
1072            }
1073
1074            @Override
1075            public void onPlayFromUri(Uri uri, Bundle extras) {
1076                Callback.this.onPlayFromUri(uri, extras);
1077            }
1078        }
1079
1080        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
1081
1082            StubApi24() {
1083            }
1084
1085            @Override
1086            public void onPrepare() {
1087                Callback.this.onPrepare();
1088            }
1089
1090            @Override
1091            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
1092                Callback.this.onPrepareFromMediaId(mediaId, extras);
1093            }
1094
1095            @Override
1096            public void onPrepareFromSearch(String query, Bundle extras) {
1097                Callback.this.onPrepareFromSearch(query, extras);
1098            }
1099
1100            @Override
1101            public void onPrepareFromUri(Uri uri, Bundle extras) {
1102                Callback.this.onPrepareFromUri(uri, extras);
1103            }
1104        }
1105    }
1106
1107    /**
1108     * Represents an ongoing session. This may be passed to apps by the session
1109     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
1110     * the session.
1111     */
1112    public static final class Token implements Parcelable {
1113        private final Object mInner;
1114        private final IMediaSession mExtraBinder;
1115
1116        Token(Object inner) {
1117            this(inner, null);
1118        }
1119
1120        Token(Object inner, IMediaSession extraBinder) {
1121            mInner = inner;
1122            mExtraBinder = extraBinder;
1123        }
1124
1125        /**
1126         * Creates a compat Token from a framework
1127         * {@link android.media.session.MediaSession.Token} object.
1128         * <p>
1129         * This method is only supported on
1130         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
1131         * </p>
1132         *
1133         * @param token The framework token object.
1134         * @return A compat Token for use with {@link MediaControllerCompat}.
1135         */
1136        public static Token fromToken(Object token) {
1137            return fromToken(token, null);
1138        }
1139
1140        /**
1141         * Creates a compat Token from a framework
1142         * {@link android.media.session.MediaSession.Token} object, and the extra binder.
1143         * <p>
1144         * This method is only supported on
1145         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
1146         * </p>
1147         *
1148         * @param token The framework token object.
1149         * @param extraBinder The extra binder.
1150         * @return A compat Token for use with {@link MediaControllerCompat}.
1151         * @hide
1152         */
1153        @RestrictTo(LIBRARY_GROUP)
1154        public static Token fromToken(Object token, IMediaSession extraBinder) {
1155            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
1156                return null;
1157            }
1158            return new Token(MediaSessionCompatApi21.verifyToken(token), extraBinder);
1159        }
1160
1161        @Override
1162        public int describeContents() {
1163            return 0;
1164        }
1165
1166        @Override
1167        public void writeToParcel(Parcel dest, int flags) {
1168            if (android.os.Build.VERSION.SDK_INT >= 21) {
1169                dest.writeParcelable((Parcelable) mInner, flags);
1170                dest.writeStrongBinder((IBinder) mExtraBinder);
1171            } else {
1172                dest.writeStrongBinder((IBinder) mInner);
1173            }
1174        }
1175
1176        @Override
1177        public int hashCode() {
1178            if (mInner == null) {
1179                return 0;
1180            }
1181            return mInner.hashCode();
1182        }
1183
1184        @Override
1185        public boolean equals(Object obj) {
1186            if (this == obj) {
1187                return true;
1188            }
1189            if (!(obj instanceof Token)) {
1190                return false;
1191            }
1192
1193            Token other = (Token) obj;
1194            if (mInner == null) {
1195                return other.mInner == null;
1196            }
1197            if (other.mInner == null) {
1198                return false;
1199            }
1200            return mInner.equals(other.mInner);
1201        }
1202
1203        /**
1204         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
1205         * <p>
1206         * This method is only supported on API 21+.
1207         * </p>
1208         *
1209         * @return The underlying {@link android.media.session.MediaSession.Token} object,
1210         * or null if none.
1211         */
1212        public Object getToken() {
1213            return mInner;
1214        }
1215
1216        /**
1217         * @hide
1218         */
1219        @RestrictTo(LIBRARY_GROUP)
1220        public IMediaSession getExtraBinder() {
1221            return mExtraBinder;
1222        }
1223
1224        public static final Parcelable.Creator<Token> CREATOR
1225                = new Parcelable.Creator<Token>() {
1226            @Override
1227            public Token createFromParcel(Parcel in) {
1228                Object inner;
1229                IMediaSession extraBinder = null;
1230                if (android.os.Build.VERSION.SDK_INT >= 21) {
1231                    inner = in.readParcelable(null);
1232                    extraBinder = (IMediaSession) in.readStrongBinder();
1233                } else {
1234                    inner = in.readStrongBinder();
1235                }
1236                return new Token(inner, extraBinder);
1237            }
1238
1239            @Override
1240            public Token[] newArray(int size) {
1241                return new Token[size];
1242            }
1243        };
1244    }
1245
1246    /**
1247     * A single item that is part of the play queue. It contains a description
1248     * of the item and its id in the queue.
1249     */
1250    public static final class QueueItem implements Parcelable {
1251        /**
1252         * This id is reserved. No items can be explicitly assigned this id.
1253         */
1254        public static final int UNKNOWN_ID = -1;
1255
1256        private final MediaDescriptionCompat mDescription;
1257        private final long mId;
1258
1259        private Object mItem;
1260
1261        /**
1262         * Create a new {@link MediaSessionCompat.QueueItem}.
1263         *
1264         * @param description The {@link MediaDescriptionCompat} for this item.
1265         * @param id An identifier for this item. It must be unique within the
1266         *            play queue and cannot be {@link #UNKNOWN_ID}.
1267         */
1268        public QueueItem(MediaDescriptionCompat description, long id) {
1269            this(null, description, id);
1270        }
1271
1272        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
1273            if (description == null) {
1274                throw new IllegalArgumentException("Description cannot be null.");
1275            }
1276            if (id == UNKNOWN_ID) {
1277                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1278            }
1279            mDescription = description;
1280            mId = id;
1281            mItem = queueItem;
1282        }
1283
1284        QueueItem(Parcel in) {
1285            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
1286            mId = in.readLong();
1287        }
1288
1289        /**
1290         * Get the description for this item.
1291         */
1292        public MediaDescriptionCompat getDescription() {
1293            return mDescription;
1294        }
1295
1296        /**
1297         * Get the queue id for this item.
1298         */
1299        public long getQueueId() {
1300            return mId;
1301        }
1302
1303        @Override
1304        public void writeToParcel(Parcel dest, int flags) {
1305            mDescription.writeToParcel(dest, flags);
1306            dest.writeLong(mId);
1307        }
1308
1309        @Override
1310        public int describeContents() {
1311            return 0;
1312        }
1313
1314        /**
1315         * Get the underlying
1316         * {@link android.media.session.MediaSession.QueueItem}.
1317         * <p>
1318         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1319         * is returned.
1320         *
1321         * @return The underlying
1322         *         {@link android.media.session.MediaSession.QueueItem} or null.
1323         */
1324        public Object getQueueItem() {
1325            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1326                return mItem;
1327            }
1328            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1329                    mId);
1330            return mItem;
1331        }
1332
1333        /**
1334         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1335         * object.
1336         * <p>
1337         * This method is only supported on API 21+. On API 20 and below, it returns null.
1338         * </p>
1339         *
1340         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1341         * @return An equivalent {@link QueueItem} object, or null if none.
1342         * @deprecated Use {@link #fromQueueItem(Object)} instead.
1343         */
1344        @Deprecated
1345        public static QueueItem obtain(Object queueItem) {
1346            return fromQueueItem(queueItem);
1347        }
1348
1349        /**
1350         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
1351         * object.
1352         * <p>
1353         * This method is only supported on API 21+. On API 20 and below, it returns null.
1354         * </p>
1355         *
1356         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
1357         * @return An equivalent {@link QueueItem} object, or null if none.
1358         */
1359        public static QueueItem fromQueueItem(Object queueItem) {
1360            if (queueItem == null || Build.VERSION.SDK_INT < 21) {
1361                return null;
1362            }
1363            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1364            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1365                    descriptionObj);
1366            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1367            return new QueueItem(queueItem, description, id);
1368        }
1369
1370        /**
1371         * Creates a list of {@link QueueItem} objects from a framework
1372         * {@link android.media.session.MediaSession.QueueItem} object list.
1373         * <p>
1374         * This method is only supported on API 21+. On API 20 and below, it returns null.
1375         * </p>
1376         *
1377         * @param itemList A list of {@link android.media.session.MediaSession.QueueItem} objects.
1378         * @return An equivalent list of {@link QueueItem} objects, or null if none.
1379         */
1380        public static List<QueueItem> fromQueueItemList(List<?> itemList) {
1381            if (itemList == null || Build.VERSION.SDK_INT < 21) {
1382                return null;
1383            }
1384            List<QueueItem> items = new ArrayList<>();
1385            for (Object itemObj : itemList) {
1386                items.add(fromQueueItem(itemObj));
1387            }
1388            return items;
1389        }
1390
1391        public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1392                = new Creator<MediaSessionCompat.QueueItem>() {
1393
1394            @Override
1395            public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1396                return new MediaSessionCompat.QueueItem(p);
1397            }
1398
1399            @Override
1400            public MediaSessionCompat.QueueItem[] newArray(int size) {
1401                return new MediaSessionCompat.QueueItem[size];
1402            }
1403        };
1404
1405        @Override
1406        public String toString() {
1407            return "MediaSession.QueueItem {" +
1408                    "Description=" + mDescription +
1409                    ", Id=" + mId + " }";
1410        }
1411    }
1412
1413    /**
1414     * This is a wrapper for {@link ResultReceiver} for sending over aidl
1415     * interfaces. The framework version was not exposed to aidls until
1416     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1417     */
1418    static final class ResultReceiverWrapper implements Parcelable {
1419        private ResultReceiver mResultReceiver;
1420
1421        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1422            mResultReceiver = resultReceiver;
1423        }
1424
1425        ResultReceiverWrapper(Parcel in) {
1426            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1427        }
1428
1429        public static final Creator<ResultReceiverWrapper>
1430                CREATOR = new Creator<ResultReceiverWrapper>() {
1431            @Override
1432            public ResultReceiverWrapper createFromParcel(Parcel p) {
1433                return new ResultReceiverWrapper(p);
1434            }
1435
1436            @Override
1437            public ResultReceiverWrapper[] newArray(int size) {
1438                return new ResultReceiverWrapper[size];
1439            }
1440        };
1441
1442        @Override
1443        public int describeContents() {
1444            return 0;
1445        }
1446
1447        @Override
1448        public void writeToParcel(Parcel dest, int flags) {
1449            mResultReceiver.writeToParcel(dest, flags);
1450        }
1451    }
1452
1453    public interface OnActiveChangeListener {
1454        void onActiveChanged();
1455    }
1456
1457    interface MediaSessionImpl {
1458        void setCallback(Callback callback, Handler handler);
1459        void setFlags(@SessionFlags int flags);
1460        void setPlaybackToLocal(int stream);
1461        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
1462        void setActive(boolean active);
1463        boolean isActive();
1464        void sendSessionEvent(String event, Bundle extras);
1465        void release();
1466        Token getSessionToken();
1467        void setPlaybackState(PlaybackStateCompat state);
1468        void setMetadata(MediaMetadataCompat metadata);
1469
1470        void setSessionActivity(PendingIntent pi);
1471
1472        void setMediaButtonReceiver(PendingIntent mbr);
1473        void setQueue(List<QueueItem> queue);
1474        void setQueueTitle(CharSequence title);
1475
1476        void setRatingType(@RatingCompat.Style int type);
1477        void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
1478        void setShuffleModeEnabled(boolean enabled);
1479        void setExtras(Bundle extras);
1480
1481        Object getMediaSession();
1482
1483        Object getRemoteControlClient();
1484
1485        String getCallingPackage();
1486    }
1487
1488    static class MediaSessionImplBase implements MediaSessionImpl {
1489        private final Context mContext;
1490        private final ComponentName mMediaButtonReceiverComponentName;
1491        private final PendingIntent mMediaButtonReceiverIntent;
1492        private final Object mRccObj;
1493        private final MediaSessionStub mStub;
1494        private final Token mToken;
1495        final String mPackageName;
1496        final String mTag;
1497        final AudioManager mAudioManager;
1498
1499        final Object mLock = new Object();
1500        final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1501                = new RemoteCallbackList<>();
1502
1503        private MessageHandler mHandler;
1504        boolean mDestroyed = false;
1505        private boolean mIsActive = false;
1506        private boolean mIsRccRegistered = false;
1507        private boolean mIsMbrRegistered = false;
1508        volatile Callback mCallback;
1509
1510        @SessionFlags int mFlags;
1511
1512        MediaMetadataCompat mMetadata;
1513        PlaybackStateCompat mState;
1514        PendingIntent mSessionActivity;
1515        List<QueueItem> mQueue;
1516        CharSequence mQueueTitle;
1517        @RatingCompat.Style int mRatingType;
1518        @PlaybackStateCompat.RepeatMode int mRepeatMode;
1519        boolean mShuffleModeEnabled;
1520        Bundle mExtras;
1521
1522        int mVolumeType;
1523        int mLocalStream;
1524        VolumeProviderCompat mVolumeProvider;
1525
1526        private VolumeProviderCompat.Callback mVolumeCallback
1527                = new VolumeProviderCompat.Callback() {
1528            @Override
1529            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1530                if (mVolumeProvider != volumeProvider) {
1531                    return;
1532                }
1533                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1534                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1535                        volumeProvider.getCurrentVolume());
1536                sendVolumeInfoChanged(info);
1537            }
1538        };
1539
1540        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1541                PendingIntent mbrIntent) {
1542            if (mbrComponent == null) {
1543                throw new IllegalArgumentException(
1544                        "MediaButtonReceiver component may not be null.");
1545            }
1546            mContext = context;
1547            mPackageName = context.getPackageName();
1548            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1549            mTag = tag;
1550            mMediaButtonReceiverComponentName = mbrComponent;
1551            mMediaButtonReceiverIntent = mbrIntent;
1552            mStub = new MediaSessionStub();
1553            mToken = new Token(mStub);
1554
1555            mRatingType = RatingCompat.RATING_NONE;
1556            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1557            mLocalStream = AudioManager.STREAM_MUSIC;
1558            if (android.os.Build.VERSION.SDK_INT >= 14) {
1559                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
1560            } else {
1561                mRccObj = null;
1562            }
1563        }
1564
1565        @Override
1566        public void setCallback(Callback callback, Handler handler) {
1567            mCallback = callback;
1568            if (callback == null) {
1569                // There's nothing to unregister on API < 18 since media buttons
1570                // all go through the media button receiver
1571                if (android.os.Build.VERSION.SDK_INT >= 18) {
1572                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1573                }
1574                if (android.os.Build.VERSION.SDK_INT >= 19) {
1575                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1576                }
1577            } else {
1578                if (handler == null) {
1579                    handler = new Handler();
1580                }
1581                synchronized (mLock) {
1582                    mHandler = new MessageHandler(handler.getLooper());
1583                }
1584                MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
1585                    @Override
1586                    public void onSetRating(Object ratingObj) {
1587                        postToHandler(MessageHandler.MSG_RATE,
1588                                RatingCompat.fromRating(ratingObj));
1589                    }
1590
1591                    @Override
1592                    public void onSeekTo(long pos) {
1593                        postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1594                    }
1595                };
1596                if (android.os.Build.VERSION.SDK_INT >= 18) {
1597                    Object onPositionUpdateObj = MediaSessionCompatApi18
1598                            .createPlaybackPositionUpdateListener(cb19);
1599                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1600                            onPositionUpdateObj);
1601                }
1602                if (android.os.Build.VERSION.SDK_INT >= 19) {
1603                    Object onMetadataUpdateObj = MediaSessionCompatApi19
1604                            .createMetadataUpdateListener(cb19);
1605                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1606                            onMetadataUpdateObj);
1607                }
1608            }
1609        }
1610
1611        void postToHandler(int what) {
1612            postToHandler(what, null);
1613        }
1614
1615        void postToHandler(int what, int arg1) {
1616            postToHandler(what, null, arg1);
1617        }
1618
1619        void postToHandler(int what, Object obj) {
1620            postToHandler(what, obj, null);
1621        }
1622
1623        void postToHandler(int what, Object obj, int arg1) {
1624            synchronized (mLock) {
1625                if (mHandler != null) {
1626                    mHandler.post(what, obj, arg1);
1627                }
1628            }
1629        }
1630
1631        void postToHandler(int what, Object obj, Bundle extras) {
1632            synchronized (mLock) {
1633                if (mHandler != null) {
1634                    mHandler.post(what, obj, extras);
1635                }
1636            }
1637        }
1638
1639        @Override
1640        public void setFlags(@SessionFlags int flags) {
1641            synchronized (mLock) {
1642                mFlags = flags;
1643            }
1644            update();
1645        }
1646
1647        @Override
1648        public void setPlaybackToLocal(int stream) {
1649            if (mVolumeProvider != null) {
1650                mVolumeProvider.setCallback(null);
1651            }
1652            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1653            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1654                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1655                    mAudioManager.getStreamMaxVolume(mLocalStream),
1656                    mAudioManager.getStreamVolume(mLocalStream));
1657            sendVolumeInfoChanged(info);
1658        }
1659
1660        @Override
1661        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1662            if (volumeProvider == null) {
1663                throw new IllegalArgumentException("volumeProvider may not be null");
1664            }
1665            if (mVolumeProvider != null) {
1666                mVolumeProvider.setCallback(null);
1667            }
1668            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1669            mVolumeProvider = volumeProvider;
1670            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1671                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1672                    mVolumeProvider.getCurrentVolume());
1673            sendVolumeInfoChanged(info);
1674
1675            volumeProvider.setCallback(mVolumeCallback);
1676        }
1677
1678        @Override
1679        public void setActive(boolean active) {
1680            if (active == mIsActive) {
1681                return;
1682            }
1683            mIsActive = active;
1684            if (update()) {
1685                setMetadata(mMetadata);
1686                setPlaybackState(mState);
1687            }
1688        }
1689
1690        @Override
1691        public boolean isActive() {
1692            return mIsActive;
1693        }
1694
1695        @Override
1696        public void sendSessionEvent(String event, Bundle extras) {
1697            sendEvent(event, extras);
1698        }
1699
1700        @Override
1701        public void release() {
1702            mIsActive = false;
1703            mDestroyed = true;
1704            update();
1705            sendSessionDestroyed();
1706        }
1707
1708        @Override
1709        public Token getSessionToken() {
1710            return mToken;
1711        }
1712
1713        @Override
1714        public void setPlaybackState(PlaybackStateCompat state) {
1715            synchronized (mLock) {
1716                mState = state;
1717            }
1718            sendState(state);
1719            if (!mIsActive) {
1720                // Don't set the state until after the RCC is registered
1721                return;
1722            }
1723            if (state == null) {
1724                if (android.os.Build.VERSION.SDK_INT >= 14) {
1725                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1726                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0);
1727                }
1728            } else {
1729                // Set state
1730                if (android.os.Build.VERSION.SDK_INT >= 18) {
1731                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1732                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1733                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1734                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
1735                }
1736
1737                // Set transport control flags
1738                if (android.os.Build.VERSION.SDK_INT >= 19) {
1739                    MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions());
1740                } else if (android.os.Build.VERSION.SDK_INT >= 18) {
1741                    MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions());
1742                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1743                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions());
1744                }
1745            }
1746        }
1747
1748        @Override
1749        public void setMetadata(MediaMetadataCompat metadata) {
1750            if (metadata != null) {
1751                // Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the
1752                // metadata if necessary. Bitmaps can be scaled down if they are large.
1753                metadata = new MediaMetadataCompat.Builder(metadata, sMaxBitmapSize).build();
1754            }
1755
1756            synchronized (mLock) {
1757                mMetadata = metadata;
1758            }
1759            sendMetadata(metadata);
1760            if (!mIsActive) {
1761                // Don't set metadata until after the rcc has been registered
1762                return;
1763            }
1764            if (android.os.Build.VERSION.SDK_INT >= 19) {
1765                MediaSessionCompatApi19.setMetadata(mRccObj,
1766                        metadata == null ? null : metadata.getBundle(),
1767                        mState == null ? 0 : mState.getActions());
1768            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1769                MediaSessionCompatApi14.setMetadata(mRccObj,
1770                        metadata == null ? null : metadata.getBundle());
1771            }
1772        }
1773
1774        @Override
1775        public void setSessionActivity(PendingIntent pi) {
1776            synchronized (mLock) {
1777                mSessionActivity = pi;
1778            }
1779        }
1780
1781        @Override
1782        public void setMediaButtonReceiver(PendingIntent mbr) {
1783            // Do nothing, changing this is not supported before API 21.
1784        }
1785
1786        @Override
1787        public void setQueue(List<QueueItem> queue) {
1788            mQueue = queue;
1789            sendQueue(queue);
1790        }
1791
1792        @Override
1793        public void setQueueTitle(CharSequence title) {
1794            mQueueTitle = title;
1795            sendQueueTitle(title);
1796        }
1797
1798        @Override
1799        public Object getMediaSession() {
1800            return null;
1801        }
1802
1803        @Override
1804        public Object getRemoteControlClient() {
1805            return mRccObj;
1806        }
1807
1808        @Override
1809        public String getCallingPackage() {
1810            return null;
1811        }
1812
1813        @Override
1814        public void setRatingType(@RatingCompat.Style int type) {
1815            mRatingType = type;
1816        }
1817
1818        @Override
1819        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
1820            if (mRepeatMode != repeatMode) {
1821                mRepeatMode = repeatMode;
1822                sendRepeatMode(repeatMode);
1823            }
1824        }
1825
1826        @Override
1827        public void setShuffleModeEnabled(boolean enabled) {
1828            if (mShuffleModeEnabled != enabled) {
1829                mShuffleModeEnabled = enabled;
1830                sendShuffleModeEnabled(enabled);
1831            }
1832        }
1833
1834        @Override
1835        public void setExtras(Bundle extras) {
1836            mExtras = extras;
1837            sendExtras(extras);
1838        }
1839
1840        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
1841        private boolean update() {
1842            boolean registeredRcc = false;
1843            if (mIsActive) {
1844                // Register a MBR if it's supported, unregister it
1845                // if support was removed.
1846                if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1847                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1848                        MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1849                                mMediaButtonReceiverIntent,
1850                                mMediaButtonReceiverComponentName);
1851                    } else {
1852                        AudioManager am = (AudioManager) mContext.getSystemService(
1853                                Context.AUDIO_SERVICE);
1854                        am.registerMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1855                    }
1856                    mIsMbrRegistered = true;
1857                } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1858                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1859                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1860                                mMediaButtonReceiverIntent,
1861                                mMediaButtonReceiverComponentName);
1862                    } else {
1863                        AudioManager am = (AudioManager) mContext.getSystemService(
1864                                Context.AUDIO_SERVICE);
1865                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1866                    }
1867                    mIsMbrRegistered = false;
1868                }
1869                // On API 14+ register a RCC if it's supported, unregister it if
1870                // not.
1871                if (android.os.Build.VERSION.SDK_INT >= 14) {
1872                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1873                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1874                        mIsRccRegistered = true;
1875                        registeredRcc = true;
1876                    } else if (mIsRccRegistered
1877                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1878                        // RCC keeps the state while the system resets its state internally when
1879                        // we register RCC. Reset the state so that the states in RCC and the system
1880                        // are in sync when we re-register the RCC.
1881                        MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1882                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1883                        mIsRccRegistered = false;
1884                    }
1885                }
1886            } else {
1887                // When inactive remove any registered components.
1888                if (mIsMbrRegistered) {
1889                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1890                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1891                                mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
1892                    } else {
1893                        AudioManager am = (AudioManager) mContext.getSystemService(
1894                                Context.AUDIO_SERVICE);
1895                        am.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponentName);
1896                    }
1897                    mIsMbrRegistered = false;
1898                }
1899                if (mIsRccRegistered) {
1900                    // RCC keeps the state while the system resets its state internally when
1901                    // we register RCC. Reset the state so that the states in RCC and the system
1902                    // are in sync when we re-register the RCC.
1903                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1904                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1905                    mIsRccRegistered = false;
1906                }
1907            }
1908            return registeredRcc;
1909        }
1910
1911        void adjustVolume(int direction, int flags) {
1912            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1913                if (mVolumeProvider != null) {
1914                    mVolumeProvider.onAdjustVolume(direction);
1915                }
1916            } else {
1917                mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
1918            }
1919        }
1920
1921        void setVolumeTo(int value, int flags) {
1922            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1923                if (mVolumeProvider != null) {
1924                    mVolumeProvider.onSetVolumeTo(value);
1925                }
1926            } else {
1927                mAudioManager.setStreamVolume(mLocalStream, value, flags);
1928            }
1929        }
1930
1931        PlaybackStateCompat getStateWithUpdatedPosition() {
1932            PlaybackStateCompat state;
1933            long duration = -1;
1934            synchronized (mLock) {
1935                state = mState;
1936                if (mMetadata != null
1937                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1938                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1939                }
1940            }
1941
1942            PlaybackStateCompat result = null;
1943            if (state != null) {
1944                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1945                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1946                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1947                    long updateTime = state.getLastPositionUpdateTime();
1948                    long currentTime = SystemClock.elapsedRealtime();
1949                    if (updateTime > 0) {
1950                        long position = (long) (state.getPlaybackSpeed()
1951                                * (currentTime - updateTime)) + state.getPosition();
1952                        if (duration >= 0 && position > duration) {
1953                            position = duration;
1954                        } else if (position < 0) {
1955                            position = 0;
1956                        }
1957                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1958                                state);
1959                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1960                                currentTime);
1961                        result = builder.build();
1962                    }
1963                }
1964            }
1965            return result == null ? state : result;
1966        }
1967
1968        void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1969            int size = mControllerCallbacks.beginBroadcast();
1970            for (int i = size - 1; i >= 0; i--) {
1971                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1972                try {
1973                    cb.onVolumeInfoChanged(info);
1974                } catch (RemoteException e) {
1975                }
1976            }
1977            mControllerCallbacks.finishBroadcast();
1978        }
1979
1980        private void sendSessionDestroyed() {
1981            int size = mControllerCallbacks.beginBroadcast();
1982            for (int i = size - 1; i >= 0; i--) {
1983                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1984                try {
1985                    cb.onSessionDestroyed();
1986                } catch (RemoteException e) {
1987                }
1988            }
1989            mControllerCallbacks.finishBroadcast();
1990            mControllerCallbacks.kill();
1991        }
1992
1993        private void sendEvent(String event, Bundle extras) {
1994            int size = mControllerCallbacks.beginBroadcast();
1995            for (int i = size - 1; i >= 0; i--) {
1996                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1997                try {
1998                    cb.onEvent(event, extras);
1999                } catch (RemoteException e) {
2000                }
2001            }
2002            mControllerCallbacks.finishBroadcast();
2003        }
2004
2005        private void sendState(PlaybackStateCompat state) {
2006            int size = mControllerCallbacks.beginBroadcast();
2007            for (int i = size - 1; i >= 0; i--) {
2008                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2009                try {
2010                    cb.onPlaybackStateChanged(state);
2011                } catch (RemoteException e) {
2012                }
2013            }
2014            mControllerCallbacks.finishBroadcast();
2015        }
2016
2017        private void sendMetadata(MediaMetadataCompat metadata) {
2018            int size = mControllerCallbacks.beginBroadcast();
2019            for (int i = size - 1; i >= 0; i--) {
2020                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2021                try {
2022                    cb.onMetadataChanged(metadata);
2023                } catch (RemoteException e) {
2024                }
2025            }
2026            mControllerCallbacks.finishBroadcast();
2027        }
2028
2029        private void sendQueue(List<QueueItem> queue) {
2030            int size = mControllerCallbacks.beginBroadcast();
2031            for (int i = size - 1; i >= 0; i--) {
2032                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2033                try {
2034                    cb.onQueueChanged(queue);
2035                } catch (RemoteException e) {
2036                }
2037            }
2038            mControllerCallbacks.finishBroadcast();
2039        }
2040
2041        private void sendQueueTitle(CharSequence queueTitle) {
2042            int size = mControllerCallbacks.beginBroadcast();
2043            for (int i = size - 1; i >= 0; i--) {
2044                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2045                try {
2046                    cb.onQueueTitleChanged(queueTitle);
2047                } catch (RemoteException e) {
2048                }
2049            }
2050            mControllerCallbacks.finishBroadcast();
2051        }
2052
2053        private void sendRepeatMode(int repeatMode) {
2054            int size = mControllerCallbacks.beginBroadcast();
2055            for (int i = size - 1; i >= 0; i--) {
2056                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2057                try {
2058                    cb.onRepeatModeChanged(repeatMode);
2059                } catch (RemoteException e) {
2060                }
2061            }
2062            mControllerCallbacks.finishBroadcast();
2063        }
2064
2065        private void sendShuffleModeEnabled(boolean enabled) {
2066            int size = mControllerCallbacks.beginBroadcast();
2067            for (int i = size - 1; i >= 0; i--) {
2068                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2069                try {
2070                    cb.onShuffleModeChanged(enabled);
2071                } catch (RemoteException e) {
2072                }
2073            }
2074            mControllerCallbacks.finishBroadcast();
2075        }
2076
2077        private void sendExtras(Bundle extras) {
2078            int size = mControllerCallbacks.beginBroadcast();
2079            for (int i = size - 1; i >= 0; i--) {
2080                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
2081                try {
2082                    cb.onExtrasChanged(extras);
2083                } catch (RemoteException e) {
2084                }
2085            }
2086            mControllerCallbacks.finishBroadcast();
2087        }
2088
2089        class MediaSessionStub extends IMediaSession.Stub {
2090            @Override
2091            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
2092                postToHandler(MessageHandler.MSG_COMMAND,
2093                        new Command(command, args, cb.mResultReceiver));
2094            }
2095
2096            @Override
2097            public boolean sendMediaButton(KeyEvent mediaButton) {
2098                boolean handlesMediaButtons =
2099                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
2100                if (handlesMediaButtons) {
2101                    postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
2102                }
2103                return handlesMediaButtons;
2104            }
2105
2106            @Override
2107            public void registerCallbackListener(IMediaControllerCallback cb) {
2108                // If this session is already destroyed tell the caller and
2109                // don't add them.
2110                if (mDestroyed) {
2111                    try {
2112                        cb.onSessionDestroyed();
2113                    } catch (Exception e) {
2114                        // ignored
2115                    }
2116                    return;
2117                }
2118                mControllerCallbacks.register(cb);
2119            }
2120
2121            @Override
2122            public void unregisterCallbackListener(IMediaControllerCallback cb) {
2123                mControllerCallbacks.unregister(cb);
2124            }
2125
2126            @Override
2127            public String getPackageName() {
2128                // mPackageName is final so doesn't need synchronize block
2129                return mPackageName;
2130            }
2131
2132            @Override
2133            public String getTag() {
2134                // mTag is final so doesn't need synchronize block
2135                return mTag;
2136            }
2137
2138            @Override
2139            public PendingIntent getLaunchPendingIntent() {
2140                synchronized (mLock) {
2141                    return mSessionActivity;
2142                }
2143            }
2144
2145            @Override
2146            @SessionFlags
2147            public long getFlags() {
2148                synchronized (mLock) {
2149                    return mFlags;
2150                }
2151            }
2152
2153            @Override
2154            public ParcelableVolumeInfo getVolumeAttributes() {
2155                int controlType;
2156                int max;
2157                int current;
2158                int stream;
2159                int volumeType;
2160                synchronized (mLock) {
2161                    volumeType = mVolumeType;
2162                    stream = mLocalStream;
2163                    VolumeProviderCompat vp = mVolumeProvider;
2164                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
2165                        controlType = vp.getVolumeControl();
2166                        max = vp.getMaxVolume();
2167                        current = vp.getCurrentVolume();
2168                    } else {
2169                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
2170                        max = mAudioManager.getStreamMaxVolume(stream);
2171                        current = mAudioManager.getStreamVolume(stream);
2172                    }
2173                }
2174                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
2175            }
2176
2177            @Override
2178            public void adjustVolume(int direction, int flags, String packageName) {
2179                MediaSessionImplBase.this.adjustVolume(direction, flags);
2180            }
2181
2182            @Override
2183            public void setVolumeTo(int value, int flags, String packageName) {
2184                MediaSessionImplBase.this.setVolumeTo(value, flags);
2185            }
2186
2187            @Override
2188            public void prepare() throws RemoteException {
2189                postToHandler(MessageHandler.MSG_PREPARE);
2190            }
2191
2192            @Override
2193            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2194                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
2195            }
2196
2197            @Override
2198            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
2199                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
2200            }
2201
2202            @Override
2203            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
2204                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
2205            }
2206
2207            @Override
2208            public void play() throws RemoteException {
2209                postToHandler(MessageHandler.MSG_PLAY);
2210            }
2211
2212            @Override
2213            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2214                postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
2215            }
2216
2217            @Override
2218            public void playFromSearch(String query, Bundle extras) throws RemoteException {
2219                postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
2220            }
2221
2222            @Override
2223            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
2224                postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
2225            }
2226
2227            @Override
2228            public void skipToQueueItem(long id) {
2229                postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
2230            }
2231
2232            @Override
2233            public void pause() throws RemoteException {
2234                postToHandler(MessageHandler.MSG_PAUSE);
2235            }
2236
2237            @Override
2238            public void stop() throws RemoteException {
2239                postToHandler(MessageHandler.MSG_STOP);
2240            }
2241
2242            @Override
2243            public void next() throws RemoteException {
2244                postToHandler(MessageHandler.MSG_NEXT);
2245            }
2246
2247            @Override
2248            public void previous() throws RemoteException {
2249                postToHandler(MessageHandler.MSG_PREVIOUS);
2250            }
2251
2252            @Override
2253            public void fastForward() throws RemoteException {
2254                postToHandler(MessageHandler.MSG_FAST_FORWARD);
2255            }
2256
2257            @Override
2258            public void rewind() throws RemoteException {
2259                postToHandler(MessageHandler.MSG_REWIND);
2260            }
2261
2262            @Override
2263            public void seekTo(long pos) throws RemoteException {
2264                postToHandler(MessageHandler.MSG_SEEK_TO, pos);
2265            }
2266
2267            @Override
2268            public void rate(RatingCompat rating) throws RemoteException {
2269                postToHandler(MessageHandler.MSG_RATE, rating);
2270            }
2271
2272            @Override
2273            public void setRepeatMode(int repeatMode) throws RemoteException {
2274                postToHandler(MessageHandler.MSG_SET_REPEAT_MODE, repeatMode);
2275            }
2276
2277            @Override
2278            public void setShuffleModeEnabled(boolean enabled) throws RemoteException {
2279                postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE_ENABLED, enabled);
2280            }
2281
2282            @Override
2283            public void sendCustomAction(String action, Bundle args)
2284                    throws RemoteException {
2285                postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
2286            }
2287
2288            @Override
2289            public MediaMetadataCompat getMetadata() {
2290                return mMetadata;
2291            }
2292
2293            @Override
2294            public PlaybackStateCompat getPlaybackState() {
2295                return getStateWithUpdatedPosition();
2296            }
2297
2298            @Override
2299            public List<QueueItem> getQueue() {
2300                synchronized (mLock) {
2301                    return mQueue;
2302                }
2303            }
2304
2305            @Override
2306            public void addQueueItem(MediaDescriptionCompat description) {
2307                postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM, description);
2308            }
2309
2310            @Override
2311            public void addQueueItemAt(MediaDescriptionCompat description, int index) {
2312                postToHandler(MessageHandler.MSG_ADD_QUEUE_ITEM_AT, description, index);
2313            }
2314
2315            @Override
2316            public void removeQueueItem(MediaDescriptionCompat description) {
2317                postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM, description);
2318            }
2319
2320            @Override
2321            public void removeQueueItemAt(int index) {
2322                postToHandler(MessageHandler.MSG_REMOVE_QUEUE_ITEM_AT, index);
2323            }
2324
2325            @Override
2326            public CharSequence getQueueTitle() {
2327                return mQueueTitle;
2328            }
2329
2330            @Override
2331            public Bundle getExtras() {
2332                synchronized (mLock) {
2333                    return mExtras;
2334                }
2335            }
2336
2337            @Override
2338            @RatingCompat.Style
2339            public int getRatingType() {
2340                return mRatingType;
2341            }
2342
2343            @Override
2344            @PlaybackStateCompat.RepeatMode
2345            public int getRepeatMode() {
2346                return mRepeatMode;
2347            }
2348
2349            @Override
2350            public boolean isShuffleModeEnabled() {
2351                return mShuffleModeEnabled;
2352            }
2353
2354            @Override
2355            public boolean isTransportControlEnabled() {
2356                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
2357            }
2358        }
2359
2360        private static final class Command {
2361            public final String command;
2362            public final Bundle extras;
2363            public final ResultReceiver stub;
2364
2365            public Command(String command, Bundle extras, ResultReceiver stub) {
2366                this.command = command;
2367                this.extras = extras;
2368                this.stub = stub;
2369            }
2370        }
2371
2372        private class MessageHandler extends Handler {
2373
2374            private static final int MSG_COMMAND = 1;
2375            private static final int MSG_ADJUST_VOLUME = 2;
2376            private static final int MSG_PREPARE = 3;
2377            private static final int MSG_PREPARE_MEDIA_ID = 4;
2378            private static final int MSG_PREPARE_SEARCH = 5;
2379            private static final int MSG_PREPARE_URI = 6;
2380            private static final int MSG_PLAY = 7;
2381            private static final int MSG_PLAY_MEDIA_ID = 8;
2382            private static final int MSG_PLAY_SEARCH = 9;
2383            private static final int MSG_PLAY_URI = 10;
2384            private static final int MSG_SKIP_TO_ITEM = 11;
2385            private static final int MSG_PAUSE = 12;
2386            private static final int MSG_STOP = 13;
2387            private static final int MSG_NEXT = 14;
2388            private static final int MSG_PREVIOUS = 15;
2389            private static final int MSG_FAST_FORWARD = 16;
2390            private static final int MSG_REWIND = 17;
2391            private static final int MSG_SEEK_TO = 18;
2392            private static final int MSG_RATE = 19;
2393            private static final int MSG_CUSTOM_ACTION = 20;
2394            private static final int MSG_MEDIA_BUTTON = 21;
2395            private static final int MSG_SET_VOLUME = 22;
2396            private static final int MSG_SET_REPEAT_MODE = 23;
2397            private static final int MSG_SET_SHUFFLE_MODE_ENABLED = 24;
2398            private static final int MSG_ADD_QUEUE_ITEM = 25;
2399            private static final int MSG_ADD_QUEUE_ITEM_AT = 26;
2400            private static final int MSG_REMOVE_QUEUE_ITEM = 27;
2401            private static final int MSG_REMOVE_QUEUE_ITEM_AT = 28;
2402
2403            // KeyEvent constants only available on API 11+
2404            private static final int KEYCODE_MEDIA_PAUSE = 127;
2405            private static final int KEYCODE_MEDIA_PLAY = 126;
2406
2407            public MessageHandler(Looper looper) {
2408                super(looper);
2409            }
2410
2411            public void post(int what, Object obj, Bundle bundle) {
2412                Message msg = obtainMessage(what, obj);
2413                msg.setData(bundle);
2414                msg.sendToTarget();
2415            }
2416
2417            public void post(int what, Object obj) {
2418                obtainMessage(what, obj).sendToTarget();
2419            }
2420
2421            public void post(int what) {
2422                post(what, null);
2423            }
2424
2425            public void post(int what, Object obj, int arg1) {
2426                obtainMessage(what, arg1, 0, obj).sendToTarget();
2427            }
2428
2429            @Override
2430            public void handleMessage(Message msg) {
2431                MediaSessionCompat.Callback cb = mCallback;
2432                if (cb == null) {
2433                    return;
2434                }
2435                switch (msg.what) {
2436                    case MSG_COMMAND:
2437                        Command cmd = (Command) msg.obj;
2438                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2439                        break;
2440                    case MSG_MEDIA_BUTTON:
2441                        KeyEvent keyEvent = (KeyEvent) msg.obj;
2442                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2443                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2444                        // Let the Callback handle events first before using the default behavior
2445                        if (!cb.onMediaButtonEvent(intent)) {
2446                            onMediaButtonEvent(keyEvent, cb);
2447                        }
2448                        break;
2449                    case MSG_PREPARE:
2450                        cb.onPrepare();
2451                        break;
2452                    case MSG_PREPARE_MEDIA_ID:
2453                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2454                        break;
2455                    case MSG_PREPARE_SEARCH:
2456                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2457                        break;
2458                    case MSG_PREPARE_URI:
2459                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2460                        break;
2461                    case MSG_PLAY:
2462                        cb.onPlay();
2463                        break;
2464                    case MSG_PLAY_MEDIA_ID:
2465                        cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2466                        break;
2467                    case MSG_PLAY_SEARCH:
2468                        cb.onPlayFromSearch((String) msg.obj, msg.getData());
2469                        break;
2470                    case MSG_PLAY_URI:
2471                        cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2472                        break;
2473                    case MSG_SKIP_TO_ITEM:
2474                        cb.onSkipToQueueItem((Long) msg.obj);
2475                        break;
2476                    case MSG_PAUSE:
2477                        cb.onPause();
2478                        break;
2479                    case MSG_STOP:
2480                        cb.onStop();
2481                        break;
2482                    case MSG_NEXT:
2483                        cb.onSkipToNext();
2484                        break;
2485                    case MSG_PREVIOUS:
2486                        cb.onSkipToPrevious();
2487                        break;
2488                    case MSG_FAST_FORWARD:
2489                        cb.onFastForward();
2490                        break;
2491                    case MSG_REWIND:
2492                        cb.onRewind();
2493                        break;
2494                    case MSG_SEEK_TO:
2495                        cb.onSeekTo((Long) msg.obj);
2496                        break;
2497                    case MSG_RATE:
2498                        cb.onSetRating((RatingCompat) msg.obj);
2499                        break;
2500                    case MSG_CUSTOM_ACTION:
2501                        cb.onCustomAction((String) msg.obj, msg.getData());
2502                        break;
2503                    case MSG_ADD_QUEUE_ITEM:
2504                        cb.onAddQueueItem((MediaDescriptionCompat) msg.obj);
2505                        break;
2506                    case MSG_ADD_QUEUE_ITEM_AT:
2507                        cb.onAddQueueItem((MediaDescriptionCompat) msg.obj, msg.arg1);
2508                        break;
2509                    case MSG_REMOVE_QUEUE_ITEM:
2510                        cb.onRemoveQueueItem((MediaDescriptionCompat) msg.obj);
2511                        break;
2512                    case MSG_REMOVE_QUEUE_ITEM_AT:
2513                        cb.onRemoveQueueItemAt(msg.arg1);
2514                        break;
2515                    case MSG_ADJUST_VOLUME:
2516                        adjustVolume(msg.arg1, 0);
2517                        break;
2518                    case MSG_SET_VOLUME:
2519                        setVolumeTo(msg.arg1, 0);
2520                        break;
2521                    case MSG_SET_REPEAT_MODE:
2522                        cb.onSetRepeatMode(msg.arg1);
2523                        break;
2524                    case MSG_SET_SHUFFLE_MODE_ENABLED:
2525                        cb.onSetShuffleModeEnabled((boolean) msg.obj);
2526                        break;
2527                }
2528            }
2529
2530            private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2531                if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2532                    return;
2533                }
2534                long validActions = mState == null ? 0 : mState.getActions();
2535                switch (ke.getKeyCode()) {
2536                    // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2537                    case KEYCODE_MEDIA_PLAY:
2538                        if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2539                            cb.onPlay();
2540                        }
2541                        break;
2542                    // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2543                    case KEYCODE_MEDIA_PAUSE:
2544                        if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2545                            cb.onPause();
2546                        }
2547                        break;
2548                    case KeyEvent.KEYCODE_MEDIA_NEXT:
2549                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2550                            cb.onSkipToNext();
2551                        }
2552                        break;
2553                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2554                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2555                            cb.onSkipToPrevious();
2556                        }
2557                        break;
2558                    case KeyEvent.KEYCODE_MEDIA_STOP:
2559                        if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2560                            cb.onStop();
2561                        }
2562                        break;
2563                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2564                        if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2565                            cb.onFastForward();
2566                        }
2567                        break;
2568                    case KeyEvent.KEYCODE_MEDIA_REWIND:
2569                        if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2570                            cb.onRewind();
2571                        }
2572                        break;
2573                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2574                    case KeyEvent.KEYCODE_HEADSETHOOK:
2575                        boolean isPlaying = mState != null
2576                                && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2577                        boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2578                                | PlaybackStateCompat.ACTION_PLAY)) != 0;
2579                        boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2580                                | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2581                        if (isPlaying && canPause) {
2582                            cb.onPause();
2583                        } else if (!isPlaying && canPlay) {
2584                            cb.onPlay();
2585                        }
2586                        break;
2587                }
2588            }
2589        }
2590    }
2591
2592    static class MediaSessionImplApi21 implements MediaSessionImpl {
2593        private final Object mSessionObj;
2594        private final Token mToken;
2595
2596        private boolean mDestroyed = false;
2597        private final RemoteCallbackList<IMediaControllerCallback> mExtraControllerCallbacks =
2598                new RemoteCallbackList<>();
2599
2600        private PlaybackStateCompat mPlaybackState;
2601        @RatingCompat.Style int mRatingType;
2602        @PlaybackStateCompat.RepeatMode int mRepeatMode;
2603        boolean mShuffleModeEnabled;
2604
2605        public MediaSessionImplApi21(Context context, String tag) {
2606            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
2607            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
2608                    new ExtraSession());
2609        }
2610
2611        public MediaSessionImplApi21(Object mediaSession) {
2612            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
2613            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj),
2614                    new ExtraSession());
2615        }
2616
2617        @Override
2618        public void setCallback(Callback callback, Handler handler) {
2619            MediaSessionCompatApi21.setCallback(mSessionObj,
2620                    callback == null ? null : callback.mCallbackObj, handler);
2621            if (callback != null) {
2622                callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
2623            }
2624        }
2625
2626        @Override
2627        public void setFlags(@SessionFlags int flags) {
2628            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
2629        }
2630
2631        @Override
2632        public void setPlaybackToLocal(int stream) {
2633            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
2634        }
2635
2636        @Override
2637        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
2638            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
2639                    volumeProvider.getVolumeProvider());
2640        }
2641
2642        @Override
2643        public void setActive(boolean active) {
2644            MediaSessionCompatApi21.setActive(mSessionObj, active);
2645        }
2646
2647        @Override
2648        public boolean isActive() {
2649            return MediaSessionCompatApi21.isActive(mSessionObj);
2650        }
2651
2652        @Override
2653        public void sendSessionEvent(String event, Bundle extras) {
2654            if (android.os.Build.VERSION.SDK_INT < 23) {
2655                int size = mExtraControllerCallbacks.beginBroadcast();
2656                for (int i = size - 1; i >= 0; i--) {
2657                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
2658                    try {
2659                        cb.onEvent(event, extras);
2660                    } catch (RemoteException e) {
2661                    }
2662                }
2663                mExtraControllerCallbacks.finishBroadcast();
2664            }
2665            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
2666        }
2667
2668        @Override
2669        public void release() {
2670            mDestroyed = true;
2671            MediaSessionCompatApi21.release(mSessionObj);
2672        }
2673
2674        @Override
2675        public Token getSessionToken() {
2676            return mToken;
2677        }
2678
2679        @Override
2680        public void setPlaybackState(PlaybackStateCompat state) {
2681            mPlaybackState = state;
2682            int size = mExtraControllerCallbacks.beginBroadcast();
2683            for (int i = size - 1; i >= 0; i--) {
2684                IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
2685                try {
2686                    cb.onPlaybackStateChanged(state);
2687                } catch (RemoteException e) {
2688                }
2689            }
2690            mExtraControllerCallbacks.finishBroadcast();
2691            MediaSessionCompatApi21.setPlaybackState(mSessionObj,
2692                    state == null ? null : state.getPlaybackState());
2693        }
2694
2695        @Override
2696        public void setMetadata(MediaMetadataCompat metadata) {
2697            MediaSessionCompatApi21.setMetadata(mSessionObj,
2698                    metadata == null ? null : metadata.getMediaMetadata());
2699        }
2700
2701        @Override
2702        public void setSessionActivity(PendingIntent pi) {
2703            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
2704        }
2705
2706        @Override
2707        public void setMediaButtonReceiver(PendingIntent mbr) {
2708            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
2709        }
2710
2711        @Override
2712        public void setQueue(List<QueueItem> queue) {
2713            List<Object> queueObjs = null;
2714            if (queue != null) {
2715                queueObjs = new ArrayList<>();
2716                for (QueueItem item : queue) {
2717                    queueObjs.add(item.getQueueItem());
2718                }
2719            }
2720            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
2721        }
2722
2723        @Override
2724        public void setQueueTitle(CharSequence title) {
2725            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
2726        }
2727
2728        @Override
2729        public void setRatingType(@RatingCompat.Style int type) {
2730            if (android.os.Build.VERSION.SDK_INT < 22) {
2731                mRatingType = type;
2732            } else {
2733                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
2734            }
2735        }
2736
2737        @Override
2738        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
2739            if (mRepeatMode != repeatMode) {
2740                mRepeatMode = repeatMode;
2741                int size = mExtraControllerCallbacks.beginBroadcast();
2742                for (int i = size - 1; i >= 0; i--) {
2743                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
2744                    try {
2745                        cb.onRepeatModeChanged(repeatMode);
2746                    } catch (RemoteException e) {
2747                    }
2748                }
2749                mExtraControllerCallbacks.finishBroadcast();
2750            }
2751        }
2752
2753        @Override
2754        public void setShuffleModeEnabled(boolean enabled) {
2755            if (mShuffleModeEnabled != enabled) {
2756                mShuffleModeEnabled = enabled;
2757                int size = mExtraControllerCallbacks.beginBroadcast();
2758                for (int i = size - 1; i >= 0; i--) {
2759                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
2760                    try {
2761                        cb.onShuffleModeChanged(enabled);
2762                    } catch (RemoteException e) {
2763                    }
2764                }
2765                mExtraControllerCallbacks.finishBroadcast();
2766            }
2767        }
2768
2769        @Override
2770        public void setExtras(Bundle extras) {
2771            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
2772        }
2773
2774        @Override
2775        public Object getMediaSession() {
2776            return mSessionObj;
2777        }
2778
2779        @Override
2780        public Object getRemoteControlClient() {
2781            return null;
2782        }
2783
2784        @Override
2785        public String getCallingPackage() {
2786            if (android.os.Build.VERSION.SDK_INT < 24) {
2787                return null;
2788            } else {
2789                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
2790            }
2791        }
2792
2793        class ExtraSession extends IMediaSession.Stub {
2794            @Override
2795            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
2796                // Will not be called.
2797                throw new AssertionError();
2798            }
2799
2800            @Override
2801            public boolean sendMediaButton(KeyEvent mediaButton) {
2802                // Will not be called.
2803                throw new AssertionError();
2804            }
2805
2806            @Override
2807            public void registerCallbackListener(IMediaControllerCallback cb) {
2808                if (!mDestroyed) {
2809                    mExtraControllerCallbacks.register(cb);
2810                }
2811            }
2812
2813            @Override
2814            public void unregisterCallbackListener(IMediaControllerCallback cb) {
2815                mExtraControllerCallbacks.unregister(cb);
2816            }
2817
2818            @Override
2819            public String getPackageName() {
2820                // Will not be called.
2821                throw new AssertionError();
2822            }
2823
2824            @Override
2825            public String getTag() {
2826                // Will not be called.
2827                throw new AssertionError();
2828            }
2829
2830            @Override
2831            public PendingIntent getLaunchPendingIntent() {
2832                // Will not be called.
2833                throw new AssertionError();
2834            }
2835
2836            @Override
2837            @SessionFlags
2838            public long getFlags() {
2839                // Will not be called.
2840                throw new AssertionError();
2841            }
2842
2843            @Override
2844            public ParcelableVolumeInfo getVolumeAttributes() {
2845                // Will not be called.
2846                throw new AssertionError();
2847            }
2848
2849            @Override
2850            public void adjustVolume(int direction, int flags, String packageName) {
2851                // Will not be called.
2852                throw new AssertionError();
2853            }
2854
2855            @Override
2856            public void setVolumeTo(int value, int flags, String packageName) {
2857                // Will not be called.
2858                throw new AssertionError();
2859            }
2860
2861            @Override
2862            public void prepare() throws RemoteException {
2863                // Will not be called.
2864                throw new AssertionError();
2865            }
2866
2867            @Override
2868            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2869                // Will not be called.
2870                throw new AssertionError();
2871            }
2872
2873            @Override
2874            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
2875                // Will not be called.
2876                throw new AssertionError();
2877            }
2878
2879            @Override
2880            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
2881                // Will not be called.
2882                throw new AssertionError();
2883            }
2884
2885            @Override
2886            public void play() throws RemoteException {
2887                // Will not be called.
2888                throw new AssertionError();
2889            }
2890
2891            @Override
2892            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
2893                // Will not be called.
2894                throw new AssertionError();
2895            }
2896
2897            @Override
2898            public void playFromSearch(String query, Bundle extras) throws RemoteException {
2899                // Will not be called.
2900                throw new AssertionError();
2901            }
2902
2903            @Override
2904            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
2905                // Will not be called.
2906                throw new AssertionError();
2907            }
2908
2909            @Override
2910            public void skipToQueueItem(long id) {
2911                // Will not be called.
2912                throw new AssertionError();
2913            }
2914
2915            @Override
2916            public void pause() throws RemoteException {
2917                // Will not be called.
2918                throw new AssertionError();
2919            }
2920
2921            @Override
2922            public void stop() throws RemoteException {
2923                // Will not be called.
2924                throw new AssertionError();
2925            }
2926
2927            @Override
2928            public void next() throws RemoteException {
2929                // Will not be called.
2930                throw new AssertionError();
2931            }
2932
2933            @Override
2934            public void previous() throws RemoteException {
2935                // Will not be called.
2936                throw new AssertionError();
2937            }
2938
2939            @Override
2940            public void fastForward() throws RemoteException {
2941                // Will not be called.
2942                throw new AssertionError();
2943            }
2944
2945            @Override
2946            public void rewind() throws RemoteException {
2947                // Will not be called.
2948                throw new AssertionError();
2949            }
2950
2951            @Override
2952            public void seekTo(long pos) throws RemoteException {
2953                // Will not be called.
2954                throw new AssertionError();
2955            }
2956
2957            @Override
2958            public void rate(RatingCompat rating) throws RemoteException {
2959                // Will not be called.
2960                throw new AssertionError();
2961            }
2962
2963            @Override
2964            public void setRepeatMode(int repeatMode) throws RemoteException {
2965                // Will not be called.
2966                throw new AssertionError();
2967            }
2968
2969            @Override
2970            public void setShuffleModeEnabled(boolean enabled) throws RemoteException {
2971                // Will not be called.
2972                throw new AssertionError();
2973            }
2974
2975            @Override
2976            public void sendCustomAction(String action, Bundle args) throws RemoteException {
2977                // Will not be called.
2978                throw new AssertionError();
2979            }
2980
2981            @Override
2982            public MediaMetadataCompat getMetadata() {
2983                // Will not be called.
2984                throw new AssertionError();
2985            }
2986
2987            @Override
2988            public PlaybackStateCompat getPlaybackState() {
2989                return mPlaybackState;
2990            }
2991
2992            @Override
2993            public List<QueueItem> getQueue() {
2994                // Will not be called.
2995                return null;
2996            }
2997
2998            @Override
2999            public void addQueueItem(MediaDescriptionCompat descriptionCompat) {
3000                // Will not be called.
3001                throw new AssertionError();
3002            }
3003
3004            @Override
3005            public void addQueueItemAt(MediaDescriptionCompat descriptionCompat, int index) {
3006                // Will not be called.
3007                throw new AssertionError();
3008            }
3009
3010            @Override
3011            public void removeQueueItem(MediaDescriptionCompat description) {
3012                // Will not be called.
3013                throw new AssertionError();
3014            }
3015
3016            @Override
3017            public void removeQueueItemAt(int index) {
3018                // Will not be called.
3019                throw new AssertionError();
3020            }
3021
3022            @Override
3023            public CharSequence getQueueTitle() {
3024                // Will not be called.
3025                throw new AssertionError();
3026            }
3027
3028            @Override
3029            public Bundle getExtras() {
3030                // Will not be called.
3031                throw new AssertionError();
3032            }
3033
3034            @Override
3035            @RatingCompat.Style
3036            public int getRatingType() {
3037                return mRatingType;
3038            }
3039
3040            @Override
3041            @PlaybackStateCompat.RepeatMode
3042            public int getRepeatMode() {
3043                return mRepeatMode;
3044            }
3045
3046            @Override
3047            public boolean isShuffleModeEnabled() {
3048                return mShuffleModeEnabled;
3049            }
3050
3051            @Override
3052            public boolean isTransportControlEnabled() {
3053                // Will not be called.
3054                throw new AssertionError();
3055            }
3056        }
3057    }
3058}
3059