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