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