1
2/*
3 * Copyright (C) 2014 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package android.support.v4.media.session;
19
20import android.app.Activity;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.graphics.Bitmap;
29import android.media.AudioManager;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.IBinder;
34import android.os.Looper;
35import android.os.Message;
36import android.os.Parcel;
37import android.os.Parcelable;
38import android.os.RemoteCallbackList;
39import android.os.RemoteException;
40import android.os.ResultReceiver;
41import android.os.SystemClock;
42import android.support.annotation.IntDef;
43import android.support.v4.media.MediaDescriptionCompat;
44import android.support.v4.media.MediaMetadataCompat;
45import android.support.v4.media.RatingCompat;
46import android.support.v4.media.VolumeProviderCompat;
47import android.text.TextUtils;
48import android.util.Log;
49import android.view.KeyEvent;
50
51import java.lang.annotation.Retention;
52import java.lang.annotation.RetentionPolicy;
53import java.util.ArrayList;
54import java.util.List;
55
56/**
57 * Allows interaction with media controllers, volume keys, media buttons, and
58 * transport controls.
59 * <p>
60 * A MediaSession should be created when an app wants to publish media playback
61 * information or handle media keys. In general an app only needs one session
62 * for all playback, though multiple sessions can be created to provide finer
63 * grain controls of media.
64 * <p>
65 * Once a session is created the owner of the session may pass its
66 * {@link #getSessionToken() session token} to other processes to allow them to
67 * create a {@link MediaControllerCompat} to interact with the session.
68 * <p>
69 * To receive commands, media keys, and other events a {@link Callback} must be
70 * set with {@link #setCallback(Callback)}.
71 * <p>
72 * When an app is finished performing playback it must call {@link #release()}
73 * to clean up the session and notify any controllers.
74 * <p>
75 * MediaSessionCompat objects are not thread safe and all calls should be made
76 * from the same thread.
77 * <p>
78 * This is a helper for accessing features in
79 * {@link android.media.session.MediaSession} introduced after API level 4 in a
80 * backwards compatible fashion.
81 */
82public class MediaSessionCompat {
83    private static final String TAG = "MediaSessionCompat";
84
85    private final MediaSessionImpl mImpl;
86    private final MediaControllerCompat mController;
87    private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
88
89    /**
90     * @hide
91     */
92    @IntDef(flag=true, value={FLAG_HANDLES_MEDIA_BUTTONS, FLAG_HANDLES_TRANSPORT_CONTROLS})
93    @Retention(RetentionPolicy.SOURCE)
94    public @interface SessionFlags {}
95
96    /**
97     * Set this flag on the session to indicate that it can handle media button
98     * events.
99     */
100    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
101
102    /**
103     * Set this flag on the session to indicate that it handles transport
104     * control commands through its {@link Callback}.
105     */
106    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
107
108    /**
109     * Custom action to invoke playFromUri() for the forward compatibility.
110     */
111    static final String ACTION_PLAY_FROM_URI =
112            "android.support.v4.media.session.action.PLAY_FROM_URI";
113
114    /**
115     * Custom action to invoke prepare() for the forward compatibility.
116     */
117    static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
118
119    /**
120     * Custom action to invoke prepareFromMediaId() for the forward compatibility.
121     */
122    static final String ACTION_PREPARE_FROM_MEDIA_ID =
123            "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
124
125    /**
126     * Custom action to invoke prepareFromSearch() for the forward compatibility.
127     */
128    static final String ACTION_PREPARE_FROM_SEARCH =
129            "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
130
131    /**
132     * Custom action to invoke prepareFromUri() for the forward compatibility.
133     */
134    static final String ACTION_PREPARE_FROM_URI =
135            "android.support.v4.media.session.action.PREPARE_FROM_URI";
136
137    /**
138     * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
139     */
140    static final String ACTION_ARGUMENT_MEDIA_ID =
141            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
142
143    /**
144     * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
145     */
146    static final String ACTION_ARGUMENT_QUERY =
147            "android.support.v4.media.session.action.ARGUMENT_QUERY";
148
149    /**
150     * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
151     * indicating URI to play.
152     */
153    static final String ACTION_ARGUMENT_URI =
154            "android.support.v4.media.session.action.ARGUMENT_URI";
155
156    /**
157     * Argument for use with various actions indicating extra bundle.
158     */
159    static final String ACTION_ARGUMENT_EXTRAS =
160            "android.support.v4.media.session.action.ARGUMENT_EXTRAS";
161
162    /**
163     * Creates a new session. You must call {@link #release()} when finished with the session.
164     * <p>
165     * The session will automatically be registered with the system but will not be published
166     * until {@link #setActive(boolean) setActive(true)} is called.
167     * </p><p>
168     * For API 20 or earlier, note that a media button receiver is required for handling
169     * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
170     * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more
171     * details.
172     * </p>
173     * @param context The context to use to create the session.
174     * @param tag A short name for debugging purposes.
175     */
176    public MediaSessionCompat(Context context, String tag) {
177        this(context, tag, null, null);
178    }
179
180    /**
181     * Creates a new session with a specified media button receiver (a component name and/or
182     * a pending intent). You must call {@link #release()} when finished with the session.
183     * <p>
184     * The session will automatically be registered with the system but will not be published
185     * until {@link #setActive(boolean) setActive(true)} is called. Note that {@code mbrComponent}
186     * and {@code mrbIntent} are only used for API 20 or earlier. If you  want to set a media button
187     * receiver in API 21 or later, call {@link #setMediaButtonReceiver}.
188     * </p><p>
189     * For API 20 or earlier, the new session will use the given {@code mbrComponent}.
190     * If null, this will attempt to find an appropriate {@link BroadcastReceiver} that handles
191     * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest. See {@link MediaButtonReceiver} for
192     * more details.
193     * </p>
194     * @param context The context to use to create the session.
195     * @param tag A short name for debugging purposes.
196     * @param mbrComponent The component name for your media button receiver.
197     * @param mbrIntent The PendingIntent for your receiver component that handles
198     *            media button events. This is optional and will be used on between
199     *            {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
200     *            {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the
201     *            component name.
202     */
203    public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent,
204            PendingIntent mbrIntent) {
205        if (context == null) {
206            throw new IllegalArgumentException("context must not be null");
207        }
208        if (TextUtils.isEmpty(tag)) {
209            throw new IllegalArgumentException("tag must not be null or empty");
210        }
211
212        if (android.os.Build.VERSION.SDK_INT >= 21) {
213            mImpl = new MediaSessionImplApi21(context, tag);
214        } else {
215            mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent);
216        }
217        mController = new MediaControllerCompat(context, this);
218    }
219
220    private MediaSessionCompat(Context context, MediaSessionImpl impl) {
221        mImpl = impl;
222        mController = new MediaControllerCompat(context, this);
223    }
224
225    /**
226     * Add a callback to receive updates on for the MediaSession. This includes
227     * media button and volume events. The caller's thread will be used to post
228     * events.
229     *
230     * @param callback The callback object
231     */
232    public void setCallback(Callback callback) {
233        setCallback(callback, null);
234    }
235
236    /**
237     * Set the callback to receive updates for the MediaSession. This includes
238     * media button and volume events. Set the callback to null to stop
239     * receiving events.
240     *
241     * @param callback The callback to receive updates on.
242     * @param handler The handler that events should be posted on.
243     */
244    public void setCallback(Callback callback, Handler handler) {
245        mImpl.setCallback(callback, handler != null ? handler : new Handler());
246    }
247
248    /**
249     * Set an intent for launching UI for this Session. This can be used as a
250     * quick link to an ongoing media screen. The intent should be for an
251     * activity that may be started using
252     * {@link Activity#startActivity(Intent)}.
253     *
254     * @param pi The intent to launch to show UI for this Session.
255     */
256    public void setSessionActivity(PendingIntent pi) {
257        mImpl.setSessionActivity(pi);
258    }
259
260    /**
261     * Set a pending intent for your media button receiver to allow restarting
262     * playback after the session has been stopped. If your app is started in
263     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
264     * the pending intent.
265     * <p>
266     * This method will only work on
267     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
268     * platform versions must include the media button receiver in the
269     * constructor.
270     *
271     * @param mbr The {@link PendingIntent} to send the media button event to.
272     */
273    public void setMediaButtonReceiver(PendingIntent mbr) {
274        mImpl.setMediaButtonReceiver(mbr);
275    }
276
277    /**
278     * Set any flags for the session.
279     *
280     * @param flags The flags to set for this session.
281     */
282    public void setFlags(@SessionFlags int flags) {
283        mImpl.setFlags(flags);
284    }
285
286    /**
287     * Set the stream this session is playing on. This will affect the system's
288     * volume handling for this session. If {@link #setPlaybackToRemote} was
289     * previously called it will stop receiving volume commands and the system
290     * will begin sending volume changes to the appropriate stream.
291     * <p>
292     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
293     *
294     * @param stream The {@link AudioManager} stream this session is playing on.
295     */
296    public void setPlaybackToLocal(int stream) {
297        mImpl.setPlaybackToLocal(stream);
298    }
299
300    /**
301     * Configure this session to use remote volume handling. This must be called
302     * to receive volume button events, otherwise the system will adjust the
303     * current stream volume for this session. If {@link #setPlaybackToLocal}
304     * was previously called that stream will stop receiving volume changes for
305     * this session.
306     * <p>
307     * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
308     * this will only allow an app to handle volume commands sent directly to
309     * the session by a {@link MediaControllerCompat}. System routing of volume
310     * keys will not use the volume provider.
311     *
312     * @param volumeProvider The provider that will handle volume changes. May
313     *            not be null.
314     */
315    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
316        if (volumeProvider == null) {
317            throw new IllegalArgumentException("volumeProvider may not be null!");
318        }
319        mImpl.setPlaybackToRemote(volumeProvider);
320    }
321
322    /**
323     * Set if this session is currently active and ready to receive commands. If
324     * set to false your session's controller may not be discoverable. You must
325     * set the session to active before it can start receiving media button
326     * events or transport commands.
327     * <p>
328     * On platforms earlier than
329     * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
330     * a media button event receiver should be set via the constructor to
331     * receive media button events.
332     *
333     * @param active Whether this session is active or not.
334     */
335    public void setActive(boolean active) {
336        mImpl.setActive(active);
337        for (OnActiveChangeListener listener : mActiveListeners) {
338            listener.onActiveChanged();
339        }
340    }
341
342    /**
343     * Get the current active state of this session.
344     *
345     * @return True if the session is active, false otherwise.
346     */
347    public boolean isActive() {
348        return mImpl.isActive();
349    }
350
351    /**
352     * Send a proprietary event to all MediaControllers listening to this
353     * Session. It's up to the Controller/Session owner to determine the meaning
354     * of any events.
355     *
356     * @param event The name of the event to send
357     * @param extras Any extras included with the event
358     */
359    public void sendSessionEvent(String event, Bundle extras) {
360        if (TextUtils.isEmpty(event)) {
361            throw new IllegalArgumentException("event cannot be null or empty");
362        }
363        mImpl.sendSessionEvent(event, extras);
364    }
365
366    /**
367     * This must be called when an app has finished performing playback. If
368     * playback is expected to start again shortly the session can be left open,
369     * but it must be released if your activity or service is being destroyed.
370     */
371    public void release() {
372        mImpl.release();
373    }
374
375    /**
376     * Retrieve a token object that can be used by apps to create a
377     * {@link MediaControllerCompat} for interacting with this session. The
378     * owner of the session is responsible for deciding how to distribute these
379     * tokens.
380     * <p>
381     * On platform versions before
382     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
383     * used within your app as there is no way to guarantee other apps are using
384     * the same version of the support library.
385     *
386     * @return A token that can be used to create a media controller for this
387     *         session.
388     */
389    public Token getSessionToken() {
390        return mImpl.getSessionToken();
391    }
392
393    /**
394     * Get a controller for this session. This is a convenience method to avoid
395     * having to cache your own controller in process.
396     *
397     * @return A controller for this session.
398     */
399    public MediaControllerCompat getController() {
400        return mController;
401    }
402
403    /**
404     * Update the current playback state.
405     *
406     * @param state The current state of playback
407     */
408    public void setPlaybackState(PlaybackStateCompat state) {
409        mImpl.setPlaybackState(state);
410    }
411
412    /**
413     * Update the current metadata. New metadata can be created using
414     * {@link android.media.MediaMetadata.Builder}.
415     *
416     * @param metadata The new metadata
417     */
418    public void setMetadata(MediaMetadataCompat metadata) {
419        mImpl.setMetadata(metadata);
420    }
421
422    /**
423     * Update the list of items in the play queue. It is an ordered list and
424     * should contain the current item, and previous or upcoming items if they
425     * exist. Specify null if there is no current play queue.
426     * <p>
427     * The queue should be of reasonable size. If the play queue is unbounded
428     * within your app, it is better to send a reasonable amount in a sliding
429     * window instead.
430     *
431     * @param queue A list of items in the play queue.
432     */
433    public void setQueue(List<QueueItem> queue) {
434        mImpl.setQueue(queue);
435    }
436
437    /**
438     * Set the title of the play queue. The UI should display this title along
439     * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
440     * name.
441     *
442     * @param title The title of the play queue.
443     */
444    public void setQueueTitle(CharSequence title) {
445        mImpl.setQueueTitle(title);
446    }
447
448    /**
449     * Set the style of rating used by this session. Apps trying to set the
450     * rating should use this style. Must be one of the following:
451     * <ul>
452     * <li>{@link RatingCompat#RATING_NONE}</li>
453     * <li>{@link RatingCompat#RATING_3_STARS}</li>
454     * <li>{@link RatingCompat#RATING_4_STARS}</li>
455     * <li>{@link RatingCompat#RATING_5_STARS}</li>
456     * <li>{@link RatingCompat#RATING_HEART}</li>
457     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
458     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
459     * </ul>
460     */
461    public void setRatingType(@RatingCompat.Style int type) {
462        mImpl.setRatingType(type);
463    }
464
465    /**
466     * Set some extras that can be associated with the
467     * {@link MediaSessionCompat}. No assumptions should be made as to how a
468     * {@link MediaControllerCompat} will handle these extras. Keys should be
469     * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
470     *
471     * @param extras The extras associated with the session.
472     */
473    public void setExtras(Bundle extras) {
474        mImpl.setExtras(extras);
475    }
476
477    /**
478     * Gets the underlying framework {@link android.media.session.MediaSession}
479     * object.
480     * <p>
481     * This method is only supported on API 21+.
482     * </p>
483     *
484     * @return The underlying {@link android.media.session.MediaSession} object,
485     *         or null if none.
486     */
487    public Object getMediaSession() {
488        return mImpl.getMediaSession();
489    }
490
491    /**
492     * Gets the underlying framework {@link android.media.RemoteControlClient}
493     * object.
494     * <p>
495     * This method is only supported on APIs 14-20. On API 21+
496     * {@link #getMediaSession()} should be used instead.
497     *
498     * @return The underlying {@link android.media.RemoteControlClient} object,
499     *         or null if none.
500     */
501    public Object getRemoteControlClient() {
502        return mImpl.getRemoteControlClient();
503    }
504
505    /**
506     * Returns the name of the package that sent the last media button, transport control, or
507     * command from controllers and the system. This is only valid while in a request callback, such
508     * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
509     *
510     * @hide
511     */
512    public String getCallingPackage() {
513        return mImpl.getCallingPackage();
514    }
515
516    /**
517     * Adds a listener to be notified when the active status of this session
518     * changes. This is primarily used by the support library and should not be
519     * needed by apps.
520     *
521     * @param listener The listener to add.
522     */
523    public void addOnActiveChangeListener(OnActiveChangeListener listener) {
524        if (listener == null) {
525            throw new IllegalArgumentException("Listener may not be null");
526        }
527        mActiveListeners.add(listener);
528    }
529
530    /**
531     * Stops the listener from being notified when the active status of this
532     * session changes.
533     *
534     * @param listener The listener to remove.
535     */
536    public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
537        if (listener == null) {
538            throw new IllegalArgumentException("Listener may not be null");
539        }
540        mActiveListeners.remove(listener);
541    }
542
543    /**
544     * Obtain a compat wrapper for an existing MediaSession.
545     *
546     * @param mediaSession The {@link android.media.session.MediaSession} to
547     *            wrap.
548     * @return A compat wrapper for the provided session.
549     */
550    public static MediaSessionCompat obtain(Context context, Object mediaSession) {
551        return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
552    }
553
554    /**
555     * Receives transport controls, media buttons, and commands from controllers
556     * and the system. The callback may be set using {@link #setCallback}.
557     */
558    public abstract static class Callback {
559        final Object mCallbackObj;
560
561        public Callback() {
562            if (android.os.Build.VERSION.SDK_INT >= 24) {
563                mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
564            } else if (android.os.Build.VERSION.SDK_INT >= 23) {
565                mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
566            } else if (android.os.Build.VERSION.SDK_INT >= 21) {
567                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
568            } else {
569                mCallbackObj = null;
570            }
571        }
572
573        /**
574         * Called when a controller has sent a custom command to this session.
575         * The owner of the session may handle custom commands but is not
576         * required to.
577         *
578         * @param command The command name.
579         * @param extras Optional parameters for the command, may be null.
580         * @param cb A result receiver to which a result may be sent by the command, may be null.
581         */
582        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
583        }
584
585        /**
586         * Override to handle media button events.
587         *
588         * @param mediaButtonEvent The media button event intent.
589         * @return True if the event was handled, false otherwise.
590         */
591        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
592            return false;
593        }
594
595        /**
596         * Override to handle requests to prepare playback. During the preparation, a session
597         * should not hold audio focus in order to allow other session play seamlessly.
598         * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
599         * after the preparation is done.
600         */
601        public void onPrepare() {
602        }
603
604        /**
605         * Override to handle requests to prepare for playing a specific mediaId that was provided
606         * by your app. During the preparation, a session should not hold audio focus in order to
607         * allow other session play seamlessly. The state of playback should be updated to
608         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
609         * of the prepared content should start in the implementation of {@link #onPlay}. Override
610         * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
611         */
612        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
613        }
614
615        /**
616         * Override to handle requests to prepare playback from a search query. An
617         * empty query indicates that the app may prepare any music. The
618         * implementation should attempt to make a smart choice about what to
619         * play. During the preparation, a session should not hold audio focus in order to allow
620         * other session play seamlessly. The state of playback should be updated to
621         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
622         * The playback of the prepared content should start in the implementation of
623         * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
624         * starting playback without preparation.
625         */
626        public void onPrepareFromSearch(String query, Bundle extras) {
627        }
628
629        /**
630         * Override to handle requests to prepare a specific media item represented by a URI.
631         * During the preparation, a session should not hold audio focus in order to allow other
632         * session play seamlessly. The state of playback should be updated to
633         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
634         * the prepared content should start in the implementation of {@link #onPlay}. Override
635         * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
636         */
637        public void onPrepareFromUri(Uri uri, Bundle extras) {
638        }
639
640        /**
641         * Override to handle requests to begin playback.
642         */
643        public void onPlay() {
644        }
645
646        /**
647         * Override to handle requests to play a specific mediaId that was
648         * provided by your app.
649         */
650        public void onPlayFromMediaId(String mediaId, Bundle extras) {
651        }
652
653        /**
654         * Override to handle requests to begin playback from a search query. An
655         * empty query indicates that the app may play any music. The
656         * implementation should attempt to make a smart choice about what to
657         * play.
658         */
659        public void onPlayFromSearch(String query, Bundle extras) {
660        }
661
662        /**
663         * Override to handle requests to play a specific media item represented by a URI.
664         */
665        public void onPlayFromUri(Uri uri, Bundle extras) {
666        }
667
668        /**
669         * Override to handle requests to play an item with a given id from the
670         * play queue.
671         */
672        public void onSkipToQueueItem(long id) {
673        }
674
675        /**
676         * Override to handle requests to pause playback.
677         */
678        public void onPause() {
679        }
680
681        /**
682         * Override to handle requests to skip to the next media item.
683         */
684        public void onSkipToNext() {
685        }
686
687        /**
688         * Override to handle requests to skip to the previous media item.
689         */
690        public void onSkipToPrevious() {
691        }
692
693        /**
694         * Override to handle requests to fast forward.
695         */
696        public void onFastForward() {
697        }
698
699        /**
700         * Override to handle requests to rewind.
701         */
702        public void onRewind() {
703        }
704
705        /**
706         * Override to handle requests to stop playback.
707         */
708        public void onStop() {
709        }
710
711        /**
712         * Override to handle requests to seek to a specific position in ms.
713         *
714         * @param pos New position to move to, in milliseconds.
715         */
716        public void onSeekTo(long pos) {
717        }
718
719        /**
720         * Override to handle the item being rated.
721         *
722         * @param rating
723         */
724        public void onSetRating(RatingCompat rating) {
725        }
726
727        /**
728         * Called when a {@link MediaControllerCompat} wants a
729         * {@link PlaybackStateCompat.CustomAction} to be performed.
730         *
731         * @param action The action that was originally sent in the
732         *            {@link PlaybackStateCompat.CustomAction}.
733         * @param extras Optional extras specified by the
734         *            {@link MediaControllerCompat}.
735         */
736        public void onCustomAction(String action, Bundle extras) {
737        }
738
739        private class StubApi21 implements MediaSessionCompatApi21.Callback {
740
741            @Override
742            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
743                Callback.this.onCommand(command, extras, cb);
744            }
745
746            @Override
747            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
748                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
749            }
750
751            @Override
752            public void onPlay() {
753                Callback.this.onPlay();
754            }
755
756            @Override
757            public void onPlayFromMediaId(String mediaId, Bundle extras) {
758                Callback.this.onPlayFromMediaId(mediaId, extras);
759            }
760
761            @Override
762            public void onPlayFromSearch(String search, Bundle extras) {
763                Callback.this.onPlayFromSearch(search, extras);
764            }
765
766            @Override
767            public void onSkipToQueueItem(long id) {
768                Callback.this.onSkipToQueueItem(id);
769            }
770
771            @Override
772            public void onPause() {
773                Callback.this.onPause();
774            }
775
776            @Override
777            public void onSkipToNext() {
778                Callback.this.onSkipToNext();
779            }
780
781            @Override
782            public void onSkipToPrevious() {
783                Callback.this.onSkipToPrevious();
784            }
785
786            @Override
787            public void onFastForward() {
788                Callback.this.onFastForward();
789            }
790
791            @Override
792            public void onRewind() {
793                Callback.this.onRewind();
794            }
795
796            @Override
797            public void onStop() {
798                Callback.this.onStop();
799            }
800
801            @Override
802            public void onSeekTo(long pos) {
803                Callback.this.onSeekTo(pos);
804            }
805
806            @Override
807            public void onSetRating(Object ratingObj) {
808                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
809            }
810
811            @Override
812            public void onCustomAction(String action, Bundle extras) {
813                if (action.equals(ACTION_PLAY_FROM_URI)) {
814                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
815                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
816                    Callback.this.onPlayFromUri(uri, bundle);
817                } else if (action.equals(ACTION_PREPARE)) {
818                    Callback.this.onPrepare();
819                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
820                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
821                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
822                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
823                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
824                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
825                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
826                    Callback.this.onPrepareFromSearch(query, bundle);
827                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
828                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
829                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
830                    Callback.this.onPrepareFromUri(uri, bundle);
831                } else {
832                    Callback.this.onCustomAction(action, extras);
833                }
834            }
835        }
836
837        private class StubApi23 extends StubApi21 implements MediaSessionCompatApi23.Callback {
838
839            @Override
840            public void onPlayFromUri(Uri uri, Bundle extras) {
841                Callback.this.onPlayFromUri(uri, extras);
842            }
843        }
844
845        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
846
847            @Override
848            public void onPrepare() {
849                Callback.this.onPrepare();
850            }
851
852            @Override
853            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
854                Callback.this.onPrepareFromMediaId(mediaId, extras);
855            }
856
857            @Override
858            public void onPrepareFromSearch(String query, Bundle extras) {
859                Callback.this.onPrepareFromSearch(query, extras);
860            }
861
862            @Override
863            public void onPrepareFromUri(Uri uri, Bundle extras) {
864                Callback.this.onPrepareFromUri(uri, extras);
865            }
866        }
867    }
868
869    /**
870     * Represents an ongoing session. This may be passed to apps by the session
871     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
872     * the session.
873     */
874    public static final class Token implements Parcelable {
875        private final Object mInner;
876
877        Token(Object inner) {
878            mInner = inner;
879        }
880
881        /**
882         * Creates a compat Token from a framework
883         * {@link android.media.session.MediaSession.Token} object.
884         * <p>
885         * This method is only supported on
886         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
887         * </p>
888         *
889         * @param token The framework token object.
890         * @return A compat Token for use with {@link MediaControllerCompat}.
891         */
892        public static Token fromToken(Object token) {
893            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
894                return null;
895            }
896            return new Token(MediaSessionCompatApi21.verifyToken(token));
897        }
898
899        @Override
900        public int describeContents() {
901            return 0;
902        }
903
904        @Override
905        public void writeToParcel(Parcel dest, int flags) {
906            if (android.os.Build.VERSION.SDK_INT >= 21) {
907                dest.writeParcelable((Parcelable) mInner, flags);
908            } else {
909                dest.writeStrongBinder((IBinder) mInner);
910            }
911        }
912
913        /**
914         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
915         * <p>
916         * This method is only supported on API 21+.
917         * </p>
918         *
919         * @return The underlying {@link android.media.session.MediaSession.Token} object,
920         * or null if none.
921         */
922        public Object getToken() {
923            return mInner;
924        }
925
926        public static final Parcelable.Creator<Token> CREATOR
927                = new Parcelable.Creator<Token>() {
928            @Override
929            public Token createFromParcel(Parcel in) {
930                Object inner;
931                if (android.os.Build.VERSION.SDK_INT >= 21) {
932                    inner = in.readParcelable(null);
933                } else {
934                    inner = in.readStrongBinder();
935                }
936                return new Token(inner);
937            }
938
939            @Override
940            public Token[] newArray(int size) {
941                return new Token[size];
942            }
943        };
944    }
945
946    /**
947     * A single item that is part of the play queue. It contains a description
948     * of the item and its id in the queue.
949     */
950    public static final class QueueItem implements Parcelable {
951        /**
952         * This id is reserved. No items can be explicitly asigned this id.
953         */
954        public static final int UNKNOWN_ID = -1;
955
956        private final MediaDescriptionCompat mDescription;
957        private final long mId;
958
959        private Object mItem;
960
961        /**
962         * Create a new {@link MediaSessionCompat.QueueItem}.
963         *
964         * @param description The {@link MediaDescriptionCompat} for this item.
965         * @param id An identifier for this item. It must be unique within the
966         *            play queue and cannot be {@link #UNKNOWN_ID}.
967         */
968        public QueueItem(MediaDescriptionCompat description, long id) {
969            this(null, description, id);
970        }
971
972        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
973            if (description == null) {
974                throw new IllegalArgumentException("Description cannot be null.");
975            }
976            if (id == UNKNOWN_ID) {
977                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
978            }
979            mDescription = description;
980            mId = id;
981            mItem = queueItem;
982        }
983
984        private QueueItem(Parcel in) {
985            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
986            mId = in.readLong();
987        }
988
989        /**
990         * Get the description for this item.
991         */
992        public MediaDescriptionCompat getDescription() {
993            return mDescription;
994        }
995
996        /**
997         * Get the queue id for this item.
998         */
999        public long getQueueId() {
1000            return mId;
1001        }
1002
1003        @Override
1004        public void writeToParcel(Parcel dest, int flags) {
1005            mDescription.writeToParcel(dest, flags);
1006            dest.writeLong(mId);
1007        }
1008
1009        @Override
1010        public int describeContents() {
1011            return 0;
1012        }
1013
1014        /**
1015         * Get the underlying
1016         * {@link android.media.session.MediaSession.QueueItem}.
1017         * <p>
1018         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
1019         * is returned.
1020         *
1021         * @return The underlying
1022         *         {@link android.media.session.MediaSession.QueueItem} or null.
1023         */
1024        public Object getQueueItem() {
1025            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
1026                return mItem;
1027            }
1028            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
1029                    mId);
1030            return mItem;
1031        }
1032
1033        /**
1034         * Obtain a compat wrapper for an existing QueueItem.
1035         *
1036         * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to
1037         *            wrap.
1038         * @return A compat wrapper for the provided item.
1039         */
1040        public static QueueItem obtain(Object queueItem) {
1041            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
1042            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
1043                    descriptionObj);
1044            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
1045            return new QueueItem(queueItem, description, id);
1046        }
1047
1048        public static final Creator<MediaSessionCompat.QueueItem> CREATOR
1049                = new Creator<MediaSessionCompat.QueueItem>() {
1050
1051            @Override
1052            public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
1053                return new MediaSessionCompat.QueueItem(p);
1054            }
1055
1056            @Override
1057            public MediaSessionCompat.QueueItem[] newArray(int size) {
1058                return new MediaSessionCompat.QueueItem[size];
1059            }
1060        };
1061
1062        @Override
1063        public String toString() {
1064            return "MediaSession.QueueItem {" +
1065                    "Description=" + mDescription +
1066                    ", Id=" + mId + " }";
1067        }
1068    }
1069
1070    /**
1071     * This is a wrapper for {@link ResultReceiver} for sending over aidl
1072     * interfaces. The framework version was not exposed to aidls until
1073     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
1074     */
1075    static final class ResultReceiverWrapper implements Parcelable {
1076        private ResultReceiver mResultReceiver;
1077
1078        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
1079            mResultReceiver = resultReceiver;
1080        }
1081
1082        ResultReceiverWrapper(Parcel in) {
1083            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
1084        }
1085
1086        public static final Creator<ResultReceiverWrapper>
1087                CREATOR = new Creator<ResultReceiverWrapper>() {
1088            @Override
1089            public ResultReceiverWrapper createFromParcel(Parcel p) {
1090                return new ResultReceiverWrapper(p);
1091            }
1092
1093            @Override
1094            public ResultReceiverWrapper[] newArray(int size) {
1095                return new ResultReceiverWrapper[size];
1096            }
1097        };
1098
1099        @Override
1100        public int describeContents() {
1101            return 0;
1102        }
1103
1104        @Override
1105        public void writeToParcel(Parcel dest, int flags) {
1106            mResultReceiver.writeToParcel(dest, flags);
1107        }
1108    }
1109
1110    public interface OnActiveChangeListener {
1111        void onActiveChanged();
1112    }
1113
1114    interface MediaSessionImpl {
1115        void setCallback(Callback callback, Handler handler);
1116        void setFlags(@SessionFlags int flags);
1117        void setPlaybackToLocal(int stream);
1118        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
1119        void setActive(boolean active);
1120        boolean isActive();
1121        void sendSessionEvent(String event, Bundle extras);
1122        void release();
1123        Token getSessionToken();
1124        void setPlaybackState(PlaybackStateCompat state);
1125        void setMetadata(MediaMetadataCompat metadata);
1126
1127        void setSessionActivity(PendingIntent pi);
1128
1129        void setMediaButtonReceiver(PendingIntent mbr);
1130        void setQueue(List<QueueItem> queue);
1131        void setQueueTitle(CharSequence title);
1132
1133        void setRatingType(@RatingCompat.Style int type);
1134        void setExtras(Bundle extras);
1135
1136        Object getMediaSession();
1137
1138        Object getRemoteControlClient();
1139
1140        String getCallingPackage();
1141    }
1142
1143    static class MediaSessionImplBase implements MediaSessionImpl {
1144        private final Context mContext;
1145        private final ComponentName mMediaButtonReceiverComponentName;
1146        private final PendingIntent mMediaButtonReceiverIntent;
1147        private final Object mRccObj;
1148        private final MediaSessionStub mStub;
1149        private final Token mToken;
1150        private final String mPackageName;
1151        private final String mTag;
1152        private final AudioManager mAudioManager;
1153
1154        private final Object mLock = new Object();
1155        private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
1156                = new RemoteCallbackList<>();
1157
1158        private MessageHandler mHandler;
1159        private boolean mDestroyed = false;
1160        private boolean mIsActive = false;
1161        private boolean mIsRccRegistered = false;
1162        private boolean mIsMbrRegistered = false;
1163        private volatile Callback mCallback;
1164
1165        private @SessionFlags int mFlags;
1166
1167        private MediaMetadataCompat mMetadata;
1168        private PlaybackStateCompat mState;
1169        private PendingIntent mSessionActivity;
1170        private List<QueueItem> mQueue;
1171        private CharSequence mQueueTitle;
1172        private @RatingCompat.Style int mRatingType;
1173        private Bundle mExtras;
1174
1175        private int mVolumeType;
1176        private int mLocalStream;
1177        private VolumeProviderCompat mVolumeProvider;
1178
1179        private VolumeProviderCompat.Callback mVolumeCallback
1180                = new VolumeProviderCompat.Callback() {
1181            @Override
1182            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
1183                if (mVolumeProvider != volumeProvider) {
1184                    return;
1185                }
1186                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1187                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
1188                        volumeProvider.getCurrentVolume());
1189                sendVolumeInfoChanged(info);
1190            }
1191        };
1192
1193        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
1194                PendingIntent mbrIntent) {
1195            if (mbrComponent == null) {
1196                Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1197                queryIntent.setPackage(context.getPackageName());
1198                PackageManager pm = context.getPackageManager();
1199                List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
1200                // If none are found, assume we are running on a newer platform version that does
1201                // not require a media button receiver ComponentName. Later code will double check
1202                // this assumption and throw an error if needed
1203                if (resolveInfos.size() == 1) {
1204                    ResolveInfo resolveInfo = resolveInfos.get(0);
1205                    mbrComponent = new ComponentName(resolveInfo.activityInfo.packageName,
1206                            resolveInfo.activityInfo.name);
1207                } else if (resolveInfos.size() > 1) {
1208                    Log.w(TAG, "More than one BroadcastReceiver that handles "
1209                            + Intent.ACTION_MEDIA_BUTTON + " was found, using null. Provide a "
1210                            + "specific ComponentName to use as this session's media button "
1211                            + "receiver");
1212                }
1213            }
1214            if (mbrComponent != null && mbrIntent == null) {
1215                // construct a PendingIntent for the media button
1216                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1217                // the associated intent will be handled by the component being registered
1218                mediaButtonIntent.setComponent(mbrComponent);
1219                mbrIntent = PendingIntent.getBroadcast(context,
1220                        0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
1221            }
1222            if (mbrComponent == null) {
1223                throw new IllegalArgumentException(
1224                        "MediaButtonReceiver component may not be null.");
1225            }
1226            mContext = context;
1227            mPackageName = context.getPackageName();
1228            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1229            mTag = tag;
1230            mMediaButtonReceiverComponentName = mbrComponent;
1231            mMediaButtonReceiverIntent = mbrIntent;
1232            mStub = new MediaSessionStub();
1233            mToken = new Token(mStub);
1234
1235            mRatingType = RatingCompat.RATING_NONE;
1236            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1237            mLocalStream = AudioManager.STREAM_MUSIC;
1238            if (android.os.Build.VERSION.SDK_INT >= 14) {
1239                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
1240            } else {
1241                mRccObj = null;
1242            }
1243        }
1244
1245        @Override
1246        public void setCallback(Callback callback, Handler handler) {
1247            mCallback = callback;
1248            if (callback == null) {
1249                // There's nothing to unregister on API < 18 since media buttons
1250                // all go through the media button receiver
1251                if (android.os.Build.VERSION.SDK_INT >= 18) {
1252                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
1253                }
1254                if (android.os.Build.VERSION.SDK_INT >= 19) {
1255                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
1256                }
1257            } else {
1258                if (handler == null) {
1259                    handler = new Handler();
1260                }
1261                synchronized (mLock) {
1262                    mHandler = new MessageHandler(handler.getLooper());
1263                }
1264                MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
1265                    @Override
1266                    public void onSetRating(Object ratingObj) {
1267                        postToHandler(MessageHandler.MSG_RATE,
1268                                RatingCompat.fromRating(ratingObj));
1269                    }
1270
1271                    @Override
1272                    public void onSeekTo(long pos) {
1273                        postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1274                    }
1275                };
1276                if (android.os.Build.VERSION.SDK_INT >= 18) {
1277                    Object onPositionUpdateObj = MediaSessionCompatApi18
1278                            .createPlaybackPositionUpdateListener(cb19);
1279                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
1280                            onPositionUpdateObj);
1281                }
1282                if (android.os.Build.VERSION.SDK_INT >= 19) {
1283                    Object onMetadataUpdateObj = MediaSessionCompatApi19
1284                            .createMetadataUpdateListener(cb19);
1285                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
1286                            onMetadataUpdateObj);
1287                }
1288            }
1289        }
1290
1291        private void postToHandler(int what) {
1292            postToHandler(what, null);
1293        }
1294
1295        private void postToHandler(int what, Object obj) {
1296            postToHandler(what, obj, null);
1297        }
1298
1299        private void postToHandler(int what, Object obj, Bundle extras) {
1300            synchronized (mLock) {
1301                if (mHandler != null) {
1302                    mHandler.post(what, obj, extras);
1303                }
1304            }
1305        }
1306
1307        @Override
1308        public void setFlags(@SessionFlags int flags) {
1309            synchronized (mLock) {
1310                mFlags = flags;
1311            }
1312            update();
1313        }
1314
1315        @Override
1316        public void setPlaybackToLocal(int stream) {
1317            if (mVolumeProvider != null) {
1318                mVolumeProvider.setCallback(null);
1319            }
1320            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1321            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1322                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
1323                    mAudioManager.getStreamMaxVolume(mLocalStream),
1324                    mAudioManager.getStreamVolume(mLocalStream));
1325            sendVolumeInfoChanged(info);
1326        }
1327
1328        @Override
1329        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
1330            if (volumeProvider == null) {
1331                throw new IllegalArgumentException("volumeProvider may not be null");
1332            }
1333            if (mVolumeProvider != null) {
1334                mVolumeProvider.setCallback(null);
1335            }
1336            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1337            mVolumeProvider = volumeProvider;
1338            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
1339                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
1340                    mVolumeProvider.getCurrentVolume());
1341            sendVolumeInfoChanged(info);
1342
1343            volumeProvider.setCallback(mVolumeCallback);
1344        }
1345
1346        @Override
1347        public void setActive(boolean active) {
1348            if (active == mIsActive) {
1349                return;
1350            }
1351            mIsActive = active;
1352            if (update()) {
1353                setMetadata(mMetadata);
1354                setPlaybackState(mState);
1355            }
1356        }
1357
1358        @Override
1359        public boolean isActive() {
1360            return mIsActive;
1361        }
1362
1363        @Override
1364        public void sendSessionEvent(String event, Bundle extras) {
1365            sendEvent(event, extras);
1366        }
1367
1368        @Override
1369        public void release() {
1370            mIsActive = false;
1371            mDestroyed = true;
1372            update();
1373            sendSessionDestroyed();
1374        }
1375
1376        @Override
1377        public Token getSessionToken() {
1378            return mToken;
1379        }
1380
1381        @Override
1382        public void setPlaybackState(PlaybackStateCompat state) {
1383            synchronized (mLock) {
1384                mState = state;
1385            }
1386            sendState(state);
1387            if (!mIsActive) {
1388                // Don't set the state until after the RCC is registered
1389                return;
1390            }
1391            if (state == null) {
1392                if (android.os.Build.VERSION.SDK_INT >= 14) {
1393                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1394                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, 0);
1395                }
1396            } else {
1397                // Set state
1398                if (android.os.Build.VERSION.SDK_INT >= 18) {
1399                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
1400                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
1401                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1402                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
1403                }
1404
1405                // Set transport control flags
1406                if (android.os.Build.VERSION.SDK_INT >= 19) {
1407                    MediaSessionCompatApi19.setTransportControlFlags(mRccObj, state.getActions());
1408                } else if (android.os.Build.VERSION.SDK_INT >= 18) {
1409                    MediaSessionCompatApi18.setTransportControlFlags(mRccObj, state.getActions());
1410                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1411                    MediaSessionCompatApi14.setTransportControlFlags(mRccObj, state.getActions());
1412                }
1413            }
1414        }
1415
1416        /**
1417         * Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the metadata if
1418         * they exist. If there is no bitmap in the metadata, this method just returns the given
1419         * metadata.
1420         *
1421         * @param metadata A {@link MediaMetadataCompat} to be cloned.
1422         * @return A newly cloned metadata if it contains bitmaps. Otherwise, the given metadata
1423         *         will be returned.
1424         */
1425        private MediaMetadataCompat cloneMetadataIfNeeded(MediaMetadataCompat metadata) {
1426            if (metadata == null) {
1427                return null;
1428            } else if (!metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ART)
1429                    && !metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)) {
1430                return metadata;
1431            }
1432            MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(metadata);
1433            Bitmap artBitmap = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
1434            if (artBitmap != null) {
1435                builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART,
1436                        artBitmap.copy(artBitmap.getConfig(), false));
1437            }
1438            Bitmap albumArtBitmap = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
1439            if (albumArtBitmap != null) {
1440                builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
1441                        albumArtBitmap.copy(albumArtBitmap.getConfig(), false));
1442            }
1443            return builder.build();
1444        }
1445
1446        @Override
1447        public void setMetadata(MediaMetadataCompat metadata) {
1448            if (android.os.Build.VERSION.SDK_INT >= 14 && metadata != null) {
1449                // Clone bitmaps in metadata for protecting them to be recycled by RCC.
1450                metadata = cloneMetadataIfNeeded(metadata);
1451            }
1452            synchronized (mLock) {
1453                mMetadata = metadata;
1454            }
1455            sendMetadata(metadata);
1456            if (!mIsActive) {
1457                // Don't set metadata until after the rcc has been registered
1458                return;
1459            }
1460            if (android.os.Build.VERSION.SDK_INT >= 19) {
1461                MediaSessionCompatApi19.setMetadata(mRccObj,
1462                        metadata == null ? null : metadata.getBundle(),
1463                        mState == null ? 0 : mState.getActions());
1464            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
1465                MediaSessionCompatApi14.setMetadata(mRccObj,
1466                        metadata == null ? null : metadata.getBundle());
1467            }
1468        }
1469
1470        @Override
1471        public void setSessionActivity(PendingIntent pi) {
1472            synchronized (mLock) {
1473                mSessionActivity = pi;
1474            }
1475        }
1476
1477        @Override
1478        public void setMediaButtonReceiver(PendingIntent mbr) {
1479            // Do nothing, changing this is not supported before API 21.
1480        }
1481
1482        @Override
1483        public void setQueue(List<QueueItem> queue) {
1484            mQueue = queue;
1485            sendQueue(queue);
1486        }
1487
1488        @Override
1489        public void setQueueTitle(CharSequence title) {
1490            mQueueTitle = title;
1491            sendQueueTitle(title);
1492        }
1493
1494        @Override
1495        public Object getMediaSession() {
1496            return null;
1497        }
1498
1499        @Override
1500        public Object getRemoteControlClient() {
1501            return mRccObj;
1502        }
1503
1504        @Override
1505        public String getCallingPackage() {
1506            return null;
1507        }
1508
1509        @Override
1510        public void setRatingType(@RatingCompat.Style int type) {
1511            mRatingType = type;
1512        }
1513
1514        @Override
1515        public void setExtras(Bundle extras) {
1516            mExtras = extras;
1517        }
1518
1519        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
1520        private boolean update() {
1521            boolean registeredRcc = false;
1522            if (mIsActive) {
1523                // On API 8+ register a MBR if it's supported, unregister it
1524                // if support was removed.
1525                if (android.os.Build.VERSION.SDK_INT >= 8) {
1526                    if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
1527                        if (android.os.Build.VERSION.SDK_INT >= 18) {
1528                            MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
1529                                    mMediaButtonReceiverIntent,
1530                                    mMediaButtonReceiverComponentName);
1531                        } else {
1532                            MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext,
1533                                    mMediaButtonReceiverComponentName);
1534                        }
1535                        mIsMbrRegistered = true;
1536                    } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
1537                        if (android.os.Build.VERSION.SDK_INT >= 18) {
1538                            MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1539                                    mMediaButtonReceiverIntent,
1540                                    mMediaButtonReceiverComponentName);
1541                        } else {
1542                            MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
1543                                    mMediaButtonReceiverComponentName);
1544                        }
1545                        mIsMbrRegistered = false;
1546                    }
1547                }
1548                // On API 14+ register a RCC if it's supported, unregister it if
1549                // not.
1550                if (android.os.Build.VERSION.SDK_INT >= 14) {
1551                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
1552                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
1553                        mIsRccRegistered = true;
1554                        registeredRcc = true;
1555                    } else if (mIsRccRegistered
1556                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
1557                        // RCC keeps the state while the system resets its state internally when
1558                        // we register RCC. Reset the state so that the states in RCC and the system
1559                        // are in sync when we re-register the RCC.
1560                        MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1561                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1562                        mIsRccRegistered = false;
1563                    }
1564                }
1565            } else {
1566                // When inactive remove any registered components.
1567                if (mIsMbrRegistered) {
1568                    if (android.os.Build.VERSION.SDK_INT >= 18) {
1569                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
1570                                mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
1571                    } else {
1572                        MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
1573                                mMediaButtonReceiverComponentName);
1574                    }
1575                    mIsMbrRegistered = false;
1576                }
1577                if (mIsRccRegistered) {
1578                    // RCC keeps the state while the system resets its state internally when
1579                    // we register RCC. Reset the state so that the states in RCC and the system
1580                    // are in sync when we re-register the RCC.
1581                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
1582                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
1583                    mIsRccRegistered = false;
1584                }
1585            }
1586            return registeredRcc;
1587        }
1588
1589        private void adjustVolume(int direction, int flags) {
1590            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1591                if (mVolumeProvider != null) {
1592                    mVolumeProvider.onAdjustVolume(direction);
1593                }
1594            } else {
1595                mAudioManager.adjustStreamVolume(mLocalStream, direction, flags);
1596            }
1597        }
1598
1599        private void setVolumeTo(int value, int flags) {
1600            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1601                if (mVolumeProvider != null) {
1602                    mVolumeProvider.onSetVolumeTo(value);
1603                }
1604            } else {
1605                mAudioManager.setStreamVolume(mLocalStream, value, flags);
1606            }
1607        }
1608
1609        private PlaybackStateCompat getStateWithUpdatedPosition() {
1610            PlaybackStateCompat state;
1611            long duration = -1;
1612            synchronized (mLock) {
1613                state = mState;
1614                if (mMetadata != null
1615                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
1616                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
1617                }
1618            }
1619
1620            PlaybackStateCompat result = null;
1621            if (state != null) {
1622                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
1623                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
1624                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
1625                    long updateTime = state.getLastPositionUpdateTime();
1626                    long currentTime = SystemClock.elapsedRealtime();
1627                    if (updateTime > 0) {
1628                        long position = (long) (state.getPlaybackSpeed()
1629                                * (currentTime - updateTime)) + state.getPosition();
1630                        if (duration >= 0 && position > duration) {
1631                            position = duration;
1632                        } else if (position < 0) {
1633                            position = 0;
1634                        }
1635                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
1636                                state);
1637                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
1638                                currentTime);
1639                        result = builder.build();
1640                    }
1641                }
1642            }
1643            return result == null ? state : result;
1644        }
1645
1646        private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
1647            int size = mControllerCallbacks.beginBroadcast();
1648            for (int i = size - 1; i >= 0; i--) {
1649                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1650                try {
1651                    cb.onVolumeInfoChanged(info);
1652                } catch (RemoteException e) {
1653                }
1654            }
1655            mControllerCallbacks.finishBroadcast();
1656        }
1657
1658        private void sendSessionDestroyed() {
1659            int size = mControllerCallbacks.beginBroadcast();
1660            for (int i = size - 1; i >= 0; i--) {
1661                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1662                try {
1663                    cb.onSessionDestroyed();
1664                } catch (RemoteException e) {
1665                }
1666            }
1667            mControllerCallbacks.finishBroadcast();
1668            mControllerCallbacks.kill();
1669        }
1670
1671        private void sendEvent(String event, Bundle extras) {
1672            int size = mControllerCallbacks.beginBroadcast();
1673            for (int i = size - 1; i >= 0; i--) {
1674                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1675                try {
1676                    cb.onEvent(event, extras);
1677                } catch (RemoteException e) {
1678                }
1679            }
1680            mControllerCallbacks.finishBroadcast();
1681        }
1682
1683        private void sendState(PlaybackStateCompat state) {
1684            int size = mControllerCallbacks.beginBroadcast();
1685            for (int i = size - 1; i >= 0; i--) {
1686                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1687                try {
1688                    cb.onPlaybackStateChanged(state);
1689                } catch (RemoteException e) {
1690                }
1691            }
1692            mControllerCallbacks.finishBroadcast();
1693        }
1694
1695        private void sendMetadata(MediaMetadataCompat metadata) {
1696            int size = mControllerCallbacks.beginBroadcast();
1697            for (int i = size - 1; i >= 0; i--) {
1698                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1699                try {
1700                    cb.onMetadataChanged(metadata);
1701                } catch (RemoteException e) {
1702                }
1703            }
1704            mControllerCallbacks.finishBroadcast();
1705        }
1706
1707        private void sendQueue(List<QueueItem> queue) {
1708            int size = mControllerCallbacks.beginBroadcast();
1709            for (int i = size - 1; i >= 0; i--) {
1710                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1711                try {
1712                    cb.onQueueChanged(queue);
1713                } catch (RemoteException e) {
1714                }
1715            }
1716            mControllerCallbacks.finishBroadcast();
1717        }
1718
1719        private void sendQueueTitle(CharSequence queueTitle) {
1720            int size = mControllerCallbacks.beginBroadcast();
1721            for (int i = size - 1; i >= 0; i--) {
1722                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
1723                try {
1724                    cb.onQueueTitleChanged(queueTitle);
1725                } catch (RemoteException e) {
1726                }
1727            }
1728            mControllerCallbacks.finishBroadcast();
1729        }
1730
1731        class MediaSessionStub extends IMediaSession.Stub {
1732            @Override
1733            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
1734                postToHandler(MessageHandler.MSG_COMMAND,
1735                        new Command(command, args, cb.mResultReceiver));
1736            }
1737
1738            @Override
1739            public boolean sendMediaButton(KeyEvent mediaButton) {
1740                boolean handlesMediaButtons =
1741                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
1742                if (handlesMediaButtons) {
1743                    postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
1744                }
1745                return handlesMediaButtons;
1746            }
1747
1748            @Override
1749            public void registerCallbackListener(IMediaControllerCallback cb) {
1750                // If this session is already destroyed tell the caller and
1751                // don't add them.
1752                if (mDestroyed) {
1753                    try {
1754                        cb.onSessionDestroyed();
1755                    } catch (Exception e) {
1756                        // ignored
1757                    }
1758                    return;
1759                }
1760                mControllerCallbacks.register(cb);
1761            }
1762
1763            @Override
1764            public void unregisterCallbackListener(IMediaControllerCallback cb) {
1765                mControllerCallbacks.unregister(cb);
1766            }
1767
1768            @Override
1769            public String getPackageName() {
1770                // mPackageName is final so doesn't need synchronize block
1771                return mPackageName;
1772            }
1773
1774            @Override
1775            public String getTag() {
1776                // mTag is final so doesn't need synchronize block
1777                return mTag;
1778            }
1779
1780            @Override
1781            public PendingIntent getLaunchPendingIntent() {
1782                synchronized (mLock) {
1783                    return mSessionActivity;
1784                }
1785            }
1786
1787            @Override
1788            @SessionFlags
1789            public long getFlags() {
1790                synchronized (mLock) {
1791                    return mFlags;
1792                }
1793            }
1794
1795            @Override
1796            public ParcelableVolumeInfo getVolumeAttributes() {
1797                int controlType;
1798                int max;
1799                int current;
1800                int stream;
1801                int volumeType;
1802                synchronized (mLock) {
1803                    volumeType = mVolumeType;
1804                    stream = mLocalStream;
1805                    VolumeProviderCompat vp = mVolumeProvider;
1806                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1807                        controlType = vp.getVolumeControl();
1808                        max = vp.getMaxVolume();
1809                        current = vp.getCurrentVolume();
1810                    } else {
1811                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
1812                        max = mAudioManager.getStreamMaxVolume(stream);
1813                        current = mAudioManager.getStreamVolume(stream);
1814                    }
1815                }
1816                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
1817            }
1818
1819            @Override
1820            public void adjustVolume(int direction, int flags, String packageName) {
1821                MediaSessionImplBase.this.adjustVolume(direction, flags);
1822            }
1823
1824            @Override
1825            public void setVolumeTo(int value, int flags, String packageName) {
1826                MediaSessionImplBase.this.setVolumeTo(value, flags);
1827            }
1828
1829            @Override
1830            public void prepare() throws RemoteException {
1831                postToHandler(MessageHandler.MSG_PREPARE);
1832            }
1833
1834            @Override
1835            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1836                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
1837            }
1838
1839            @Override
1840            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
1841                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
1842            }
1843
1844            @Override
1845            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
1846                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
1847            }
1848
1849            @Override
1850            public void play() throws RemoteException {
1851                postToHandler(MessageHandler.MSG_PLAY);
1852            }
1853
1854            @Override
1855            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1856                postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
1857            }
1858
1859            @Override
1860            public void playFromSearch(String query, Bundle extras) throws RemoteException {
1861                postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
1862            }
1863
1864            @Override
1865            public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1866                postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
1867            }
1868
1869            @Override
1870            public void skipToQueueItem(long id) {
1871                postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
1872            }
1873
1874            @Override
1875            public void pause() throws RemoteException {
1876                postToHandler(MessageHandler.MSG_PAUSE);
1877            }
1878
1879            @Override
1880            public void stop() throws RemoteException {
1881                postToHandler(MessageHandler.MSG_STOP);
1882            }
1883
1884            @Override
1885            public void next() throws RemoteException {
1886                postToHandler(MessageHandler.MSG_NEXT);
1887            }
1888
1889            @Override
1890            public void previous() throws RemoteException {
1891                postToHandler(MessageHandler.MSG_PREVIOUS);
1892            }
1893
1894            @Override
1895            public void fastForward() throws RemoteException {
1896                postToHandler(MessageHandler.MSG_FAST_FORWARD);
1897            }
1898
1899            @Override
1900            public void rewind() throws RemoteException {
1901                postToHandler(MessageHandler.MSG_REWIND);
1902            }
1903
1904            @Override
1905            public void seekTo(long pos) throws RemoteException {
1906                postToHandler(MessageHandler.MSG_SEEK_TO, pos);
1907            }
1908
1909            @Override
1910            public void rate(RatingCompat rating) throws RemoteException {
1911                postToHandler(MessageHandler.MSG_RATE, rating);
1912            }
1913
1914            @Override
1915            public void sendCustomAction(String action, Bundle args)
1916                    throws RemoteException {
1917                postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
1918            }
1919
1920            @Override
1921            public MediaMetadataCompat getMetadata() {
1922                return mMetadata;
1923            }
1924
1925            @Override
1926            public PlaybackStateCompat getPlaybackState() {
1927                return getStateWithUpdatedPosition();
1928            }
1929
1930            @Override
1931            public List<QueueItem> getQueue() {
1932                synchronized (mLock) {
1933                    return mQueue;
1934                }
1935            }
1936
1937            @Override
1938            public CharSequence getQueueTitle() {
1939                return mQueueTitle;
1940            }
1941
1942            @Override
1943            public Bundle getExtras() {
1944                synchronized (mLock) {
1945                    return mExtras;
1946                }
1947            }
1948
1949            @Override
1950            @RatingCompat.Style
1951            public int getRatingType() {
1952                return mRatingType;
1953            }
1954
1955            @Override
1956            public boolean isTransportControlEnabled() {
1957                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
1958            }
1959        }
1960
1961        private static final class Command {
1962            public final String command;
1963            public final Bundle extras;
1964            public final ResultReceiver stub;
1965
1966            public Command(String command, Bundle extras, ResultReceiver stub) {
1967                this.command = command;
1968                this.extras = extras;
1969                this.stub = stub;
1970            }
1971        }
1972
1973        private class MessageHandler extends Handler {
1974
1975            private static final int MSG_COMMAND = 1;
1976            private static final int MSG_ADJUST_VOLUME = 2;
1977            private static final int MSG_PREPARE = 3;
1978            private static final int MSG_PREPARE_MEDIA_ID = 4;
1979            private static final int MSG_PREPARE_SEARCH = 5;
1980            private static final int MSG_PREPARE_URI = 6;
1981            private static final int MSG_PLAY = 7;
1982            private static final int MSG_PLAY_MEDIA_ID = 8;
1983            private static final int MSG_PLAY_SEARCH = 9;
1984            private static final int MSG_PLAY_URI = 10;
1985            private static final int MSG_SKIP_TO_ITEM = 11;
1986            private static final int MSG_PAUSE = 12;
1987            private static final int MSG_STOP = 13;
1988            private static final int MSG_NEXT = 14;
1989            private static final int MSG_PREVIOUS = 15;
1990            private static final int MSG_FAST_FORWARD = 16;
1991            private static final int MSG_REWIND = 17;
1992            private static final int MSG_SEEK_TO = 18;
1993            private static final int MSG_RATE = 19;
1994            private static final int MSG_CUSTOM_ACTION = 20;
1995            private static final int MSG_MEDIA_BUTTON = 21;
1996            private static final int MSG_SET_VOLUME = 22;
1997
1998            // KeyEvent constants only available on API 11+
1999            private static final int KEYCODE_MEDIA_PAUSE = 127;
2000            private static final int KEYCODE_MEDIA_PLAY = 126;
2001
2002            public MessageHandler(Looper looper) {
2003                super(looper);
2004            }
2005
2006            public void post(int what, Object obj, Bundle bundle) {
2007                Message msg = obtainMessage(what, obj);
2008                msg.setData(bundle);
2009                msg.sendToTarget();
2010            }
2011
2012            public void post(int what, Object obj) {
2013                obtainMessage(what, obj).sendToTarget();
2014            }
2015
2016            public void post(int what) {
2017                post(what, null);
2018            }
2019
2020            public void post(int what, Object obj, int arg1) {
2021                obtainMessage(what, arg1, 0, obj).sendToTarget();
2022            }
2023
2024            @Override
2025            public void handleMessage(Message msg) {
2026                MediaSessionCompat.Callback cb = mCallback;
2027                if (cb == null) {
2028                    return;
2029                }
2030                switch (msg.what) {
2031                    case MSG_COMMAND:
2032                        Command cmd = (Command) msg.obj;
2033                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
2034                        break;
2035                    case MSG_MEDIA_BUTTON:
2036                        KeyEvent keyEvent = (KeyEvent) msg.obj;
2037                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
2038                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
2039                        // Let the Callback handle events first before using the default behavior
2040                        if (!cb.onMediaButtonEvent(intent)) {
2041                            onMediaButtonEvent(keyEvent, cb);
2042                        }
2043                        break;
2044                    case MSG_PREPARE:
2045                        cb.onPrepare();
2046                        break;
2047                    case MSG_PREPARE_MEDIA_ID:
2048                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
2049                        break;
2050                    case MSG_PREPARE_SEARCH:
2051                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
2052                        break;
2053                    case MSG_PREPARE_URI:
2054                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
2055                        break;
2056                    case MSG_PLAY:
2057                        cb.onPlay();
2058                        break;
2059                    case MSG_PLAY_MEDIA_ID:
2060                        cb.onPlayFromMediaId((String) msg.obj, msg.getData());
2061                        break;
2062                    case MSG_PLAY_SEARCH:
2063                        cb.onPlayFromSearch((String) msg.obj, msg.getData());
2064                        break;
2065                    case MSG_PLAY_URI:
2066                        cb.onPlayFromUri((Uri) msg.obj, msg.getData());
2067                        break;
2068                    case MSG_SKIP_TO_ITEM:
2069                        cb.onSkipToQueueItem((Long) msg.obj);
2070                        break;
2071                    case MSG_PAUSE:
2072                        cb.onPause();
2073                        break;
2074                    case MSG_STOP:
2075                        cb.onStop();
2076                        break;
2077                    case MSG_NEXT:
2078                        cb.onSkipToNext();
2079                        break;
2080                    case MSG_PREVIOUS:
2081                        cb.onSkipToPrevious();
2082                        break;
2083                    case MSG_FAST_FORWARD:
2084                        cb.onFastForward();
2085                        break;
2086                    case MSG_REWIND:
2087                        cb.onRewind();
2088                        break;
2089                    case MSG_SEEK_TO:
2090                        cb.onSeekTo((Long) msg.obj);
2091                        break;
2092                    case MSG_RATE:
2093                        cb.onSetRating((RatingCompat) msg.obj);
2094                        break;
2095                    case MSG_CUSTOM_ACTION:
2096                        cb.onCustomAction((String) msg.obj, msg.getData());
2097                        break;
2098                    case MSG_ADJUST_VOLUME:
2099                        adjustVolume((int) msg.obj, 0);
2100                        break;
2101                    case MSG_SET_VOLUME:
2102                        setVolumeTo((int) msg.obj, 0);
2103                        break;
2104                }
2105            }
2106
2107            private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
2108                if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
2109                    return;
2110                }
2111                long validActions = mState == null ? 0 : mState.getActions();
2112                switch (ke.getKeyCode()) {
2113                    // Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
2114                    case KEYCODE_MEDIA_PLAY:
2115                        if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
2116                            cb.onPlay();
2117                        }
2118                        break;
2119                    // Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
2120                    case KEYCODE_MEDIA_PAUSE:
2121                        if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
2122                            cb.onPause();
2123                        }
2124                        break;
2125                    case KeyEvent.KEYCODE_MEDIA_NEXT:
2126                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
2127                            cb.onSkipToNext();
2128                        }
2129                        break;
2130                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2131                        if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
2132                            cb.onSkipToPrevious();
2133                        }
2134                        break;
2135                    case KeyEvent.KEYCODE_MEDIA_STOP:
2136                        if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
2137                            cb.onStop();
2138                        }
2139                        break;
2140                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
2141                        if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
2142                            cb.onFastForward();
2143                        }
2144                        break;
2145                    case KeyEvent.KEYCODE_MEDIA_REWIND:
2146                        if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
2147                            cb.onRewind();
2148                        }
2149                        break;
2150                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2151                    case KeyEvent.KEYCODE_HEADSETHOOK:
2152                        boolean isPlaying = mState != null
2153                                && mState.getState() == PlaybackStateCompat.STATE_PLAYING;
2154                        boolean canPlay = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2155                                | PlaybackStateCompat.ACTION_PLAY)) != 0;
2156                        boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
2157                                | PlaybackStateCompat.ACTION_PAUSE)) != 0;
2158                        if (isPlaying && canPause) {
2159                            cb.onPause();
2160                        } else if (!isPlaying && canPlay) {
2161                            cb.onPlay();
2162                        }
2163                        break;
2164                }
2165            }
2166        }
2167    }
2168
2169    static class MediaSessionImplApi21 implements MediaSessionImpl {
2170        private final Object mSessionObj;
2171        private final Token mToken;
2172
2173        private PendingIntent mMediaButtonIntent;
2174
2175        public MediaSessionImplApi21(Context context, String tag) {
2176            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
2177            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2178        }
2179
2180        public MediaSessionImplApi21(Object mediaSession) {
2181            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
2182            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
2183        }
2184
2185        @Override
2186        public void setCallback(Callback callback, Handler handler) {
2187            MediaSessionCompatApi21.setCallback(mSessionObj,
2188                    callback == null ? null : callback.mCallbackObj, handler);
2189        }
2190
2191        @Override
2192        public void setFlags(@SessionFlags int flags) {
2193            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
2194        }
2195
2196        @Override
2197        public void setPlaybackToLocal(int stream) {
2198            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
2199        }
2200
2201        @Override
2202        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
2203            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
2204                    volumeProvider.getVolumeProvider());
2205        }
2206
2207        @Override
2208        public void setActive(boolean active) {
2209            MediaSessionCompatApi21.setActive(mSessionObj, active);
2210        }
2211
2212        @Override
2213        public boolean isActive() {
2214            return MediaSessionCompatApi21.isActive(mSessionObj);
2215        }
2216
2217        @Override
2218        public void sendSessionEvent(String event, Bundle extras) {
2219            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
2220        }
2221
2222        @Override
2223        public void release() {
2224            MediaSessionCompatApi21.release(mSessionObj);
2225        }
2226
2227        @Override
2228        public Token getSessionToken() {
2229            return mToken;
2230        }
2231
2232        @Override
2233        public void setPlaybackState(PlaybackStateCompat state) {
2234            MediaSessionCompatApi21.setPlaybackState(mSessionObj,
2235                    state == null ? null : state.getPlaybackState());
2236        }
2237
2238        @Override
2239        public void setMetadata(MediaMetadataCompat metadata) {
2240            MediaSessionCompatApi21.setMetadata(mSessionObj,
2241                    metadata == null ? null : metadata.getMediaMetadata());
2242        }
2243
2244        @Override
2245        public void setSessionActivity(PendingIntent pi) {
2246            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
2247        }
2248
2249        @Override
2250        public void setMediaButtonReceiver(PendingIntent mbr) {
2251            mMediaButtonIntent = mbr;
2252            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
2253        }
2254
2255        @Override
2256        public void setQueue(List<QueueItem> queue) {
2257            List<Object> queueObjs = null;
2258            if (queue != null) {
2259                queueObjs = new ArrayList<>();
2260                for (QueueItem item : queue) {
2261                    queueObjs.add(item.getQueueItem());
2262                }
2263            }
2264            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
2265        }
2266
2267        @Override
2268        public void setQueueTitle(CharSequence title) {
2269            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
2270        }
2271
2272        @Override
2273        public void setRatingType(@RatingCompat.Style int type) {
2274            if (android.os.Build.VERSION.SDK_INT < 22) {
2275                // TODO figure out 21 implementation
2276            } else {
2277                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
2278            }
2279        }
2280
2281        @Override
2282        public void setExtras(Bundle extras) {
2283            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
2284        }
2285
2286        @Override
2287        public Object getMediaSession() {
2288            return mSessionObj;
2289        }
2290
2291        @Override
2292        public Object getRemoteControlClient() {
2293            return null;
2294        }
2295
2296        @Override
2297        public String getCallingPackage() {
2298            if (android.os.Build.VERSION.SDK_INT < 24) {
2299                return null;
2300            } else {
2301                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
2302            }
2303        }
2304    }
2305}
2306