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