MediaSession.java revision 22188f118754c3b31c13d2f94daf718f111d92af
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media.session;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.Activity;
23import android.app.PendingIntent;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ParceledListSlice;
27import android.media.AudioAttributes;
28import android.media.MediaDescription;
29import android.media.MediaMetadata;
30import android.media.Rating;
31import android.media.VolumeProvider;
32import android.net.Uri;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Looper;
36import android.os.Message;
37import android.os.Parcel;
38import android.os.Parcelable;
39import android.os.RemoteException;
40import android.os.ResultReceiver;
41import android.os.UserHandle;
42import android.service.media.MediaBrowserService;
43import android.text.TextUtils;
44import android.util.Log;
45import android.view.KeyEvent;
46
47import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49import java.lang.ref.WeakReference;
50import java.util.List;
51
52/**
53 * Allows interaction with media controllers, volume keys, media buttons, and
54 * transport controls.
55 * <p>
56 * A MediaSession should be created when an app wants to publish media playback
57 * information or handle media keys. In general an app only needs one session
58 * for all playback, though multiple sessions can be created to provide finer
59 * grain controls of media.
60 * <p>
61 * Once a session is created the owner of the session may pass its
62 * {@link #getSessionToken() session token} to other processes to allow them to
63 * create a {@link MediaController} to interact with the session.
64 * <p>
65 * To receive commands, media keys, and other events a {@link Callback} must be
66 * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
67 * setActive(true)} must be called.
68 * <p>
69 * When an app is finished performing playback it must call {@link #release()}
70 * to clean up the session and notify any controllers.
71 * <p>
72 * MediaSession objects are thread safe.
73 */
74public final class MediaSession {
75    private static final String TAG = "MediaSession";
76
77    /**
78     * Set this flag on the session to indicate that it can handle media button
79     * events.
80     */
81    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
82
83    /**
84     * Set this flag on the session to indicate that it handles transport
85     * control commands through its {@link Callback}.
86     */
87    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
88
89    /**
90     * System only flag for a session that needs to have priority over all other
91     * sessions. This flag ensures this session will receive media button events
92     * regardless of the current ordering in the system.
93     *
94     * @hide
95     */
96    public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
97
98    /** @hide */
99    @Retention(RetentionPolicy.SOURCE)
100    @IntDef(flag = true, value = {
101            FLAG_HANDLES_MEDIA_BUTTONS,
102            FLAG_HANDLES_TRANSPORT_CONTROLS,
103            FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
104    public @interface SessionFlags { }
105
106    private final Object mLock = new Object();
107    private final int mMaxBitmapSize;
108
109    private final MediaSession.Token mSessionToken;
110    private final MediaController mController;
111    private final ISession mBinder;
112    private final CallbackStub mCbStub;
113
114    private CallbackMessageHandler mCallback;
115    private VolumeProvider mVolumeProvider;
116    private PlaybackState mPlaybackState;
117
118    private boolean mActive = false;
119
120    /**
121     * Creates a new session. The session will automatically be registered with
122     * the system but will not be published until {@link #setActive(boolean)
123     * setActive(true)} is called. You must call {@link #release()} when
124     * finished with the session.
125     *
126     * @param context The context to use to create the session.
127     * @param tag A short name for debugging purposes.
128     */
129    public MediaSession(@NonNull Context context, @NonNull String tag) {
130        this(context, tag, UserHandle.myUserId());
131    }
132
133    /**
134     * Creates a new session as the specified user. To create a session as a
135     * user other than your own you must hold the
136     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
137     * permission.
138     *
139     * @param context The context to use to create the session.
140     * @param tag A short name for debugging purposes.
141     * @param userId The user id to create the session as.
142     * @hide
143     */
144    public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
145        if (context == null) {
146            throw new IllegalArgumentException("context cannot be null.");
147        }
148        if (TextUtils.isEmpty(tag)) {
149            throw new IllegalArgumentException("tag cannot be null or empty");
150        }
151        mMaxBitmapSize = context.getResources().getDimensionPixelSize(
152                com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
153        mCbStub = new CallbackStub(this);
154        MediaSessionManager manager = (MediaSessionManager) context
155                .getSystemService(Context.MEDIA_SESSION_SERVICE);
156        try {
157            mBinder = manager.createSession(mCbStub, tag, userId);
158            mSessionToken = new Token(mBinder.getController());
159            mController = new MediaController(context, mSessionToken);
160        } catch (RemoteException e) {
161            throw new RuntimeException("Remote error creating session.", e);
162        }
163    }
164
165    /**
166     * Set the callback to receive updates for the MediaSession. This includes
167     * media button events and transport controls. The caller's thread will be
168     * used to post updates.
169     * <p>
170     * Set the callback to null to stop receiving updates.
171     *
172     * @param callback The callback object
173     */
174    public void setCallback(@Nullable Callback callback) {
175        setCallback(callback, null);
176    }
177
178    /**
179     * Set the callback to receive updates for the MediaSession. This includes
180     * media button events and transport controls.
181     * <p>
182     * Set the callback to null to stop receiving updates.
183     *
184     * @param callback The callback to receive updates on.
185     * @param handler The handler that events should be posted on.
186     */
187    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
188        synchronized (mLock) {
189            if (callback == null) {
190                if (mCallback != null) {
191                    mCallback.mCallback.mSession = null;
192                }
193                mCallback = null;
194                return;
195            }
196            if (mCallback != null) {
197                // We're updating the callback, clear the session from the old
198                // one.
199                mCallback.mCallback.mSession = null;
200            }
201            if (handler == null) {
202                handler = new Handler();
203            }
204            callback.mSession = this;
205            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
206                    callback);
207            mCallback = msgHandler;
208        }
209    }
210
211    /**
212     * Set an intent for launching UI for this Session. This can be used as a
213     * quick link to an ongoing media screen. The intent should be for an
214     * activity that may be started using {@link Activity#startActivity(Intent)}.
215     *
216     * @param pi The intent to launch to show UI for this Session.
217     */
218    public void setSessionActivity(@Nullable PendingIntent pi) {
219        try {
220            mBinder.setLaunchPendingIntent(pi);
221        } catch (RemoteException e) {
222            Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
223        }
224    }
225
226    /**
227     * Set a pending intent for your media button receiver to allow restarting
228     * playback after the session has been stopped. If your app is started in
229     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
230     * the pending intent.
231     *
232     * @param mbr The {@link PendingIntent} to send the media button event to.
233     */
234    public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
235        try {
236            mBinder.setMediaButtonReceiver(mbr);
237        } catch (RemoteException e) {
238            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
239        }
240    }
241
242    /**
243     * Set any flags for the session.
244     *
245     * @param flags The flags to set for this session.
246     */
247    public void setFlags(@SessionFlags int flags) {
248        try {
249            mBinder.setFlags(flags);
250        } catch (RemoteException e) {
251            Log.wtf(TAG, "Failure in setFlags.", e);
252        }
253    }
254
255    /**
256     * Set the attributes for this session's audio. This will affect the
257     * system's volume handling for this session. If
258     * {@link #setPlaybackToRemote} was previously called it will stop receiving
259     * volume commands and the system will begin sending volume changes to the
260     * appropriate stream.
261     * <p>
262     * By default sessions use attributes for media.
263     *
264     * @param attributes The {@link AudioAttributes} for this session's audio.
265     */
266    public void setPlaybackToLocal(AudioAttributes attributes) {
267        if (attributes == null) {
268            throw new IllegalArgumentException("Attributes cannot be null for local playback.");
269        }
270        try {
271            mBinder.setPlaybackToLocal(attributes);
272        } catch (RemoteException e) {
273            Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
274        }
275    }
276
277    /**
278     * Configure this session to use remote volume handling. This must be called
279     * to receive volume button events, otherwise the system will adjust the
280     * appropriate stream volume for this session. If
281     * {@link #setPlaybackToLocal} was previously called the system will stop
282     * handling volume changes for this session and pass them to the volume
283     * provider instead.
284     *
285     * @param volumeProvider The provider that will handle volume changes. May
286     *            not be null.
287     */
288    public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
289        if (volumeProvider == null) {
290            throw new IllegalArgumentException("volumeProvider may not be null!");
291        }
292        synchronized (mLock) {
293            mVolumeProvider = volumeProvider;
294        }
295        volumeProvider.setCallback(new VolumeProvider.Callback() {
296            @Override
297            public void onVolumeChanged(VolumeProvider volumeProvider) {
298                notifyRemoteVolumeChanged(volumeProvider);
299            }
300        });
301
302        try {
303            mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
304                    volumeProvider.getMaxVolume());
305            mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
306        } catch (RemoteException e) {
307            Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
308        }
309    }
310
311    /**
312     * Set if this session is currently active and ready to receive commands. If
313     * set to false your session's controller may not be discoverable. You must
314     * set the session to active before it can start receiving media button
315     * events or transport commands.
316     *
317     * @param active Whether this session is active or not.
318     */
319    public void setActive(boolean active) {
320        if (mActive == active) {
321            return;
322        }
323        try {
324            mBinder.setActive(active);
325            mActive = active;
326        } catch (RemoteException e) {
327            Log.wtf(TAG, "Failure in setActive.", e);
328        }
329    }
330
331    /**
332     * Get the current active state of this session.
333     *
334     * @return True if the session is active, false otherwise.
335     */
336    public boolean isActive() {
337        return mActive;
338    }
339
340    /**
341     * Send a proprietary event to all MediaControllers listening to this
342     * Session. It's up to the Controller/Session owner to determine the meaning
343     * of any events.
344     *
345     * @param event The name of the event to send
346     * @param extras Any extras included with the event
347     */
348    public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
349        if (TextUtils.isEmpty(event)) {
350            throw new IllegalArgumentException("event cannot be null or empty");
351        }
352        try {
353            mBinder.sendEvent(event, extras);
354        } catch (RemoteException e) {
355            Log.wtf(TAG, "Error sending event", e);
356        }
357    }
358
359    /**
360     * This must be called when an app has finished performing playback. If
361     * playback is expected to start again shortly the session can be left open,
362     * but it must be released if your activity or service is being destroyed.
363     */
364    public void release() {
365        try {
366            mBinder.destroy();
367        } catch (RemoteException e) {
368            Log.wtf(TAG, "Error releasing session: ", e);
369        }
370    }
371
372    /**
373     * Retrieve a token object that can be used by apps to create a
374     * {@link MediaController} for interacting with this session. The owner of
375     * the session is responsible for deciding how to distribute these tokens.
376     *
377     * @return A token that can be used to create a MediaController for this
378     *         session
379     */
380    public @NonNull Token getSessionToken() {
381        return mSessionToken;
382    }
383
384    /**
385     * Get a controller for this session. This is a convenience method to avoid
386     * having to cache your own controller in process.
387     *
388     * @return A controller for this session.
389     */
390    public @NonNull MediaController getController() {
391        return mController;
392    }
393
394    /**
395     * Update the current playback state.
396     *
397     * @param state The current state of playback
398     */
399    public void setPlaybackState(@Nullable PlaybackState state) {
400        mPlaybackState = state;
401        try {
402            mBinder.setPlaybackState(state);
403        } catch (RemoteException e) {
404            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
405        }
406    }
407
408    /**
409     * Update the current metadata. New metadata can be created using
410     * {@link android.media.MediaMetadata.Builder}.
411     *
412     * @param metadata The new metadata
413     */
414    public void setMetadata(@Nullable MediaMetadata metadata) {
415        if (metadata != null ) {
416            metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
417        }
418        try {
419            mBinder.setMetadata(metadata);
420        } catch (RemoteException e) {
421            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
422        }
423    }
424
425    /**
426     * Update the list of items in the play queue. It is an ordered list and
427     * should contain the current item, and previous or upcoming items if they
428     * exist. Specify null if there is no current play queue.
429     * <p>
430     * The queue should be of reasonable size. If the play queue is unbounded
431     * within your app, it is better to send a reasonable amount in a sliding
432     * window instead.
433     *
434     * @param queue A list of items in the play queue.
435     */
436    public void setQueue(@Nullable List<QueueItem> queue) {
437        try {
438            mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
439        } catch (RemoteException e) {
440            Log.wtf("Dead object in setQueue.", e);
441        }
442    }
443
444    /**
445     * Set the title of the play queue. The UI should display this title along
446     * with the play queue itself.
447     * e.g. "Play Queue", "Now Playing", or an album name.
448     *
449     * @param title The title of the play queue.
450     */
451    public void setQueueTitle(@Nullable CharSequence title) {
452        try {
453            mBinder.setQueueTitle(title);
454        } catch (RemoteException e) {
455            Log.wtf("Dead object in setQueueTitle.", e);
456        }
457    }
458
459    /**
460     * Set the style of rating used by this session. Apps trying to set the
461     * rating should use this style. Must be one of the following:
462     * <ul>
463     * <li>{@link Rating#RATING_NONE}</li>
464     * <li>{@link Rating#RATING_3_STARS}</li>
465     * <li>{@link Rating#RATING_4_STARS}</li>
466     * <li>{@link Rating#RATING_5_STARS}</li>
467     * <li>{@link Rating#RATING_HEART}</li>
468     * <li>{@link Rating#RATING_PERCENTAGE}</li>
469     * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
470     * </ul>
471     */
472    public void setRatingType(int type) {
473        try {
474            mBinder.setRatingType(type);
475        } catch (RemoteException e) {
476            Log.e(TAG, "Error in setRatingType.", e);
477        }
478    }
479
480    /**
481     * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
482     * be made as to how a {@link MediaController} will handle these extras.
483     * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
484     *
485     * @param extras The extras associated with the {@link MediaSession}.
486     */
487    public void setExtras(@Nullable Bundle extras) {
488        try {
489            mBinder.setExtras(extras);
490        } catch (RemoteException e) {
491            Log.wtf("Dead object in setExtras.", e);
492        }
493    }
494
495    /**
496     * Notify the system that the remote volume changed.
497     *
498     * @param provider The provider that is handling volume changes.
499     * @hide
500     */
501    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
502        synchronized (mLock) {
503            if (provider == null || provider != mVolumeProvider) {
504                Log.w(TAG, "Received update from stale volume provider");
505                return;
506            }
507        }
508        try {
509            mBinder.setCurrentVolume(provider.getCurrentVolume());
510        } catch (RemoteException e) {
511            Log.e(TAG, "Error in notifyVolumeChanged", e);
512        }
513    }
514
515    private void dispatchPrepare() {
516        postToCallback(CallbackMessageHandler.MSG_PREPARE);
517    }
518
519    private void dispatchPrepareFromMediaId(String mediaId, Bundle extras) {
520        postToCallback(CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
521    }
522
523    private void dispatchPrepareFromSearch(String query, Bundle extras) {
524        postToCallback(CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
525    }
526
527    private void dispatchPrepareFromUri(Uri uri, Bundle extras) {
528        postToCallback(CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
529    }
530
531    private void dispatchPlay() {
532        postToCallback(CallbackMessageHandler.MSG_PLAY);
533    }
534
535    private void dispatchPlayFromMediaId(String mediaId, Bundle extras) {
536        postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
537    }
538
539    private void dispatchPlayFromSearch(String query, Bundle extras) {
540        postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
541    }
542
543    private void dispatchPlayFromUri(Uri uri, Bundle extras) {
544        postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
545    }
546
547    private void dispatchSkipToItem(long id) {
548        postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id);
549    }
550
551    private void dispatchPause() {
552        postToCallback(CallbackMessageHandler.MSG_PAUSE);
553    }
554
555    private void dispatchStop() {
556        postToCallback(CallbackMessageHandler.MSG_STOP);
557    }
558
559    private void dispatchNext() {
560        postToCallback(CallbackMessageHandler.MSG_NEXT);
561    }
562
563    private void dispatchPrevious() {
564        postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
565    }
566
567    private void dispatchFastForward() {
568        postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
569    }
570
571    private void dispatchRewind() {
572        postToCallback(CallbackMessageHandler.MSG_REWIND);
573    }
574
575    private void dispatchSeekTo(long pos) {
576        postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
577    }
578
579    private void dispatchRate(Rating rating) {
580        postToCallback(CallbackMessageHandler.MSG_RATE, rating);
581    }
582
583    private void dispatchCustomAction(String action, Bundle args) {
584        postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
585    }
586
587    private void dispatchMediaButton(Intent mediaButtonIntent) {
588        postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
589    }
590
591    private void dispatchAdjustVolume(int direction) {
592        postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction);
593    }
594
595    private void dispatchSetVolumeTo(int volume) {
596        postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume);
597    }
598
599    private void postToCallback(int what) {
600        postToCallback(what, null);
601    }
602
603    private void postCommand(String command, Bundle args, ResultReceiver resultCb) {
604        Command cmd = new Command(command, args, resultCb);
605        postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd);
606    }
607
608    private void postToCallback(int what, Object obj) {
609        postToCallback(what, obj, null);
610    }
611
612    private void postToCallback(int what, Object obj, Bundle extras) {
613        synchronized (mLock) {
614            if (mCallback != null) {
615                mCallback.post(what, obj, extras);
616            }
617        }
618    }
619
620    /**
621     * Return true if this is considered an active playback state.
622     *
623     * @hide
624     */
625    public static boolean isActiveState(int state) {
626        switch (state) {
627            case PlaybackState.STATE_FAST_FORWARDING:
628            case PlaybackState.STATE_REWINDING:
629            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
630            case PlaybackState.STATE_SKIPPING_TO_NEXT:
631            case PlaybackState.STATE_BUFFERING:
632            case PlaybackState.STATE_CONNECTING:
633            case PlaybackState.STATE_PLAYING:
634                return true;
635        }
636        return false;
637    }
638
639    /**
640     * Represents an ongoing session. This may be passed to apps by the session
641     * owner to allow them to create a {@link MediaController} to communicate with
642     * the session.
643     */
644    public static final class Token implements Parcelable {
645
646        private ISessionController mBinder;
647
648        /**
649         * @hide
650         */
651        public Token(ISessionController binder) {
652            mBinder = binder;
653        }
654
655        @Override
656        public int describeContents() {
657            return 0;
658        }
659
660        @Override
661        public void writeToParcel(Parcel dest, int flags) {
662            dest.writeStrongBinder(mBinder.asBinder());
663        }
664
665        @Override
666        public int hashCode() {
667            final int prime = 31;
668            int result = 1;
669            result = prime * result + ((mBinder == null) ? 0 : mBinder.asBinder().hashCode());
670            return result;
671        }
672
673        @Override
674        public boolean equals(Object obj) {
675            if (this == obj)
676                return true;
677            if (obj == null)
678                return false;
679            if (getClass() != obj.getClass())
680                return false;
681            Token other = (Token) obj;
682            if (mBinder == null) {
683                if (other.mBinder != null)
684                    return false;
685            } else if (!mBinder.asBinder().equals(other.mBinder.asBinder()))
686                return false;
687            return true;
688        }
689
690        ISessionController getBinder() {
691            return mBinder;
692        }
693
694        public static final Parcelable.Creator<Token> CREATOR
695                = new Parcelable.Creator<Token>() {
696            @Override
697            public Token createFromParcel(Parcel in) {
698                return new Token(ISessionController.Stub.asInterface(in.readStrongBinder()));
699            }
700
701            @Override
702            public Token[] newArray(int size) {
703                return new Token[size];
704            }
705        };
706    }
707
708    /**
709     * Receives media buttons, transport controls, and commands from controllers
710     * and the system. A callback may be set using {@link #setCallback}.
711     */
712    public abstract static class Callback {
713        private MediaSession mSession;
714
715        public Callback() {
716        }
717
718        /**
719         * Called when a controller has sent a command to this session.
720         * The owner of the session may handle custom commands but is not
721         * required to.
722         *
723         * @param command The command name.
724         * @param args Optional parameters for the command, may be null.
725         * @param cb A result receiver to which a result may be sent by the command, may be null.
726         */
727        public void onCommand(@NonNull String command, @Nullable Bundle args,
728                @Nullable ResultReceiver cb) {
729        }
730
731        /**
732         * Called when a media button is pressed and this session has the
733         * highest priority or a controller sends a media button event to the
734         * session. The default behavior will call the relevant method if the
735         * action for it was set.
736         * <p>
737         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
738         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
739         *
740         * @param mediaButtonIntent an intent containing the KeyEvent as an
741         *            extra
742         * @return True if the event was handled, false otherwise.
743         */
744        public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
745            if (mSession != null
746                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
747                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
748                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
749                    PlaybackState state = mSession.mPlaybackState;
750                    long validActions = state == null ? 0 : state.getActions();
751                    switch (ke.getKeyCode()) {
752                        case KeyEvent.KEYCODE_MEDIA_PLAY:
753                            if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
754                                onPlay();
755                                return true;
756                            }
757                            break;
758                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
759                            if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
760                                onPause();
761                                return true;
762                            }
763                            break;
764                        case KeyEvent.KEYCODE_MEDIA_NEXT:
765                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
766                                onSkipToNext();
767                                return true;
768                            }
769                            break;
770                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
771                            if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
772                                onSkipToPrevious();
773                                return true;
774                            }
775                            break;
776                        case KeyEvent.KEYCODE_MEDIA_STOP:
777                            if ((validActions & PlaybackState.ACTION_STOP) != 0) {
778                                onStop();
779                                return true;
780                            }
781                            break;
782                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
783                            if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
784                                onFastForward();
785                                return true;
786                            }
787                            break;
788                        case KeyEvent.KEYCODE_MEDIA_REWIND:
789                            if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
790                                onRewind();
791                                return true;
792                            }
793                            break;
794                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
795                        case KeyEvent.KEYCODE_HEADSETHOOK:
796                            boolean isPlaying = state == null ? false
797                                    : state.getState() == PlaybackState.STATE_PLAYING;
798                            boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
799                                    | PlaybackState.ACTION_PLAY)) != 0;
800                            boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
801                                    | PlaybackState.ACTION_PAUSE)) != 0;
802                            if (isPlaying && canPause) {
803                                onPause();
804                                return true;
805                            } else if (!isPlaying && canPlay) {
806                                onPlay();
807                                return true;
808                            }
809                            break;
810                    }
811                }
812            }
813            return false;
814        }
815
816        /**
817         * Override to handle requests to prepare playback. The state of playback should be updated
818         * to {@link PlaybackState#STATE_PAUSED} after the preparation is done. Override
819         * {@link #onPlay} to handle requests for starting playback of prepared content.
820         */
821        public void onPrepare() {
822        }
823
824        /**
825         * Override to handle requests to prepare for playing a specific mediaId that was provided
826         * by your app's {@link MediaBrowserService}. The state of playback should be updated
827         * to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback of
828         * the prepared content should start in the implementation of {@link #onPlay}. Override
829         * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
830         */
831        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
832        }
833
834        /**
835         * Override to handle requests to prepare playback from a search query. An
836         * empty query indicates that the app may prepare any music. The
837         * implementation should attempt to make a smart choice about what to
838         * play. The state of playback should be updated to {@link PlaybackState#STATE_PAUSED}
839         * after the preparation is done. The playback of the prepared content should start
840         * in the implementation of {@link #onPlay}. Override {@link #onPlayFromSearch}
841         * to handle requests for starting playback without preparation.
842         */
843        public void onPrepareFromSearch(String query, Bundle extras) {
844        }
845
846        /**
847         * Override to handle requests to prepare a specific media item represented by a URI.
848         * The state of playback should be updated to {@link PlaybackState#STATE_PAUSED}
849         * after the preparation is done. The playback of the prepared content should start in
850         * the implementation of {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
851         * for starting playback without preparation.
852         */
853        public void onPrepareFromUri(Uri uri, Bundle extras) {
854        }
855
856        /**
857         * Override to handle requests to begin playback.
858         */
859        public void onPlay() {
860        }
861
862        /**
863         * Override to handle requests to begin playback from a search query. An
864         * empty query indicates that the app may play any music. The
865         * implementation should attempt to make a smart choice about what to
866         * play.
867         */
868        public void onPlayFromSearch(String query, Bundle extras) {
869        }
870
871        /**
872         * Override to handle requests to play a specific mediaId that was
873         * provided by your app's {@link MediaBrowserService}.
874         */
875        public void onPlayFromMediaId(String mediaId, Bundle extras) {
876        }
877
878        /**
879         * Override to handle requests to play a specific media item represented by a URI.
880         */
881        public void onPlayFromUri(Uri uri, Bundle extras) {
882        }
883
884        /**
885         * Override to handle requests to play an item with a given id from the
886         * play queue.
887         */
888        public void onSkipToQueueItem(long id) {
889        }
890
891        /**
892         * Override to handle requests to pause playback.
893         */
894        public void onPause() {
895        }
896
897        /**
898         * Override to handle requests to skip to the next media item.
899         */
900        public void onSkipToNext() {
901        }
902
903        /**
904         * Override to handle requests to skip to the previous media item.
905         */
906        public void onSkipToPrevious() {
907        }
908
909        /**
910         * Override to handle requests to fast forward.
911         */
912        public void onFastForward() {
913        }
914
915        /**
916         * Override to handle requests to rewind.
917         */
918        public void onRewind() {
919        }
920
921        /**
922         * Override to handle requests to stop playback.
923         */
924        public void onStop() {
925        }
926
927        /**
928         * Override to handle requests to seek to a specific position in ms.
929         *
930         * @param pos New position to move to, in milliseconds.
931         */
932        public void onSeekTo(long pos) {
933        }
934
935        /**
936         * Override to handle the item being rated.
937         *
938         * @param rating
939         */
940        public void onSetRating(@NonNull Rating rating) {
941        }
942
943        /**
944         * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
945         * performed.
946         *
947         * @param action The action that was originally sent in the
948         *               {@link PlaybackState.CustomAction}.
949         * @param extras Optional extras specified by the {@link MediaController}.
950         */
951        public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
952        }
953    }
954
955    /**
956     * @hide
957     */
958    public static class CallbackStub extends ISessionCallback.Stub {
959        private WeakReference<MediaSession> mMediaSession;
960
961        public CallbackStub(MediaSession session) {
962            mMediaSession = new WeakReference<MediaSession>(session);
963        }
964
965        @Override
966        public void onCommand(String command, Bundle args, ResultReceiver cb) {
967            MediaSession session = mMediaSession.get();
968            if (session != null) {
969                session.postCommand(command, args, cb);
970            }
971        }
972
973        @Override
974        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
975                ResultReceiver cb) {
976            MediaSession session = mMediaSession.get();
977            try {
978                if (session != null) {
979                    session.dispatchMediaButton(mediaButtonIntent);
980                }
981            } finally {
982                if (cb != null) {
983                    cb.send(sequenceNumber, null);
984                }
985            }
986        }
987
988        @Override
989        public void onPrepare() {
990            MediaSession session = mMediaSession.get();
991            if (session != null) {
992                session.dispatchPrepare();
993            }
994        }
995
996        @Override
997        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
998            MediaSession session = mMediaSession.get();
999            if (session != null) {
1000                session.dispatchPrepareFromMediaId(mediaId, extras);
1001            }
1002        }
1003
1004        @Override
1005        public void onPrepareFromSearch(String query, Bundle extras) {
1006            MediaSession session = mMediaSession.get();
1007            if (session != null) {
1008                session.dispatchPrepareFromSearch(query, extras);
1009            }
1010        }
1011
1012        @Override
1013        public void onPrepareFromUri(Uri uri, Bundle extras) {
1014            MediaSession session = mMediaSession.get();
1015            if (session != null) {
1016                session.dispatchPrepareFromUri(uri, extras);
1017            }
1018        }
1019
1020        @Override
1021        public void onPlay() {
1022            MediaSession session = mMediaSession.get();
1023            if (session != null) {
1024                session.dispatchPlay();
1025            }
1026        }
1027
1028        @Override
1029        public void onPlayFromMediaId(String mediaId, Bundle extras) {
1030            MediaSession session = mMediaSession.get();
1031            if (session != null) {
1032                session.dispatchPlayFromMediaId(mediaId, extras);
1033            }
1034        }
1035
1036        @Override
1037        public void onPlayFromSearch(String query, Bundle extras) {
1038            MediaSession session = mMediaSession.get();
1039            if (session != null) {
1040                session.dispatchPlayFromSearch(query, extras);
1041            }
1042        }
1043
1044        @Override
1045        public void onPlayFromUri(Uri uri, Bundle extras) {
1046            MediaSession session = mMediaSession.get();
1047            if (session != null) {
1048                session.dispatchPlayFromUri(uri, extras);
1049            }
1050        }
1051
1052        @Override
1053        public void onSkipToTrack(long id) {
1054            MediaSession session = mMediaSession.get();
1055            if (session != null) {
1056                session.dispatchSkipToItem(id);
1057            }
1058        }
1059
1060        @Override
1061        public void onPause() {
1062            MediaSession session = mMediaSession.get();
1063            if (session != null) {
1064                session.dispatchPause();
1065            }
1066        }
1067
1068        @Override
1069        public void onStop() {
1070            MediaSession session = mMediaSession.get();
1071            if (session != null) {
1072                session.dispatchStop();
1073            }
1074        }
1075
1076        @Override
1077        public void onNext() {
1078            MediaSession session = mMediaSession.get();
1079            if (session != null) {
1080                session.dispatchNext();
1081            }
1082        }
1083
1084        @Override
1085        public void onPrevious() {
1086            MediaSession session = mMediaSession.get();
1087            if (session != null) {
1088                session.dispatchPrevious();
1089            }
1090        }
1091
1092        @Override
1093        public void onFastForward() {
1094            MediaSession session = mMediaSession.get();
1095            if (session != null) {
1096                session.dispatchFastForward();
1097            }
1098        }
1099
1100        @Override
1101        public void onRewind() {
1102            MediaSession session = mMediaSession.get();
1103            if (session != null) {
1104                session.dispatchRewind();
1105            }
1106        }
1107
1108        @Override
1109        public void onSeekTo(long pos) {
1110            MediaSession session = mMediaSession.get();
1111            if (session != null) {
1112                session.dispatchSeekTo(pos);
1113            }
1114        }
1115
1116        @Override
1117        public void onRate(Rating rating) {
1118            MediaSession session = mMediaSession.get();
1119            if (session != null) {
1120                session.dispatchRate(rating);
1121            }
1122        }
1123
1124        @Override
1125        public void onCustomAction(String action, Bundle args) {
1126            MediaSession session = mMediaSession.get();
1127            if (session != null) {
1128                session.dispatchCustomAction(action, args);
1129            }
1130        }
1131
1132        @Override
1133        public void onAdjustVolume(int direction) {
1134            MediaSession session = mMediaSession.get();
1135            if (session != null) {
1136                session.dispatchAdjustVolume(direction);
1137            }
1138        }
1139
1140        @Override
1141        public void onSetVolumeTo(int value) {
1142            MediaSession session = mMediaSession.get();
1143            if (session != null) {
1144                session.dispatchSetVolumeTo(value);
1145            }
1146        }
1147
1148    }
1149
1150    /**
1151     * A single item that is part of the play queue. It contains a description
1152     * of the item and its id in the queue.
1153     */
1154    public static final class QueueItem implements Parcelable {
1155        /**
1156         * This id is reserved. No items can be explicitly asigned this id.
1157         */
1158        public static final int UNKNOWN_ID = -1;
1159
1160        private final MediaDescription mDescription;
1161        private final long mId;
1162
1163        /**
1164         * Create a new {@link MediaSession.QueueItem}.
1165         *
1166         * @param description The {@link MediaDescription} for this item.
1167         * @param id An identifier for this item. It must be unique within the
1168         *            play queue and cannot be {@link #UNKNOWN_ID}.
1169         */
1170        public QueueItem(MediaDescription description, long id) {
1171            if (description == null) {
1172                throw new IllegalArgumentException("Description cannot be null.");
1173            }
1174            if (id == UNKNOWN_ID) {
1175                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1176            }
1177            mDescription = description;
1178            mId = id;
1179        }
1180
1181        private QueueItem(Parcel in) {
1182            mDescription = MediaDescription.CREATOR.createFromParcel(in);
1183            mId = in.readLong();
1184        }
1185
1186        /**
1187         * Get the description for this item.
1188         */
1189        public MediaDescription getDescription() {
1190            return mDescription;
1191        }
1192
1193        /**
1194         * Get the queue id for this item.
1195         */
1196        public long getQueueId() {
1197            return mId;
1198        }
1199
1200        @Override
1201        public void writeToParcel(Parcel dest, int flags) {
1202            mDescription.writeToParcel(dest, flags);
1203            dest.writeLong(mId);
1204        }
1205
1206        @Override
1207        public int describeContents() {
1208            return 0;
1209        }
1210
1211        public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() {
1212
1213            @Override
1214            public MediaSession.QueueItem createFromParcel(Parcel p) {
1215                return new MediaSession.QueueItem(p);
1216            }
1217
1218            @Override
1219            public MediaSession.QueueItem[] newArray(int size) {
1220                return new MediaSession.QueueItem[size];
1221            }
1222        };
1223
1224        @Override
1225        public String toString() {
1226            return "MediaSession.QueueItem {" +
1227                    "Description=" + mDescription +
1228                    ", Id=" + mId + " }";
1229        }
1230    }
1231
1232    private static final class Command {
1233        public final String command;
1234        public final Bundle extras;
1235        public final ResultReceiver stub;
1236
1237        public Command(String command, Bundle extras, ResultReceiver stub) {
1238            this.command = command;
1239            this.extras = extras;
1240            this.stub = stub;
1241        }
1242    }
1243
1244    private class CallbackMessageHandler extends Handler {
1245
1246        private static final int MSG_COMMAND = 1;
1247        private static final int MSG_MEDIA_BUTTON = 2;
1248        private static final int MSG_PREPARE = 3;
1249        private static final int MSG_PREPARE_MEDIA_ID = 4;
1250        private static final int MSG_PREPARE_SEARCH = 5;
1251        private static final int MSG_PREPARE_URI = 6;
1252        private static final int MSG_PLAY = 7;
1253        private static final int MSG_PLAY_MEDIA_ID = 8;
1254        private static final int MSG_PLAY_SEARCH = 9;
1255        private static final int MSG_PLAY_URI = 10;
1256        private static final int MSG_SKIP_TO_ITEM = 11;
1257        private static final int MSG_PAUSE = 12;
1258        private static final int MSG_STOP = 13;
1259        private static final int MSG_NEXT = 14;
1260        private static final int MSG_PREVIOUS = 15;
1261        private static final int MSG_FAST_FORWARD = 16;
1262        private static final int MSG_REWIND = 17;
1263        private static final int MSG_SEEK_TO = 18;
1264        private static final int MSG_RATE = 19;
1265        private static final int MSG_CUSTOM_ACTION = 20;
1266        private static final int MSG_ADJUST_VOLUME = 21;
1267        private static final int MSG_SET_VOLUME = 22;
1268
1269        private MediaSession.Callback mCallback;
1270
1271        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
1272            super(looper, null, true);
1273            mCallback = callback;
1274        }
1275
1276        public void post(int what, Object obj, Bundle bundle) {
1277            Message msg = obtainMessage(what, obj);
1278            msg.setData(bundle);
1279            msg.sendToTarget();
1280        }
1281
1282        public void post(int what, Object obj) {
1283            obtainMessage(what, obj).sendToTarget();
1284        }
1285
1286        public void post(int what) {
1287            post(what, null);
1288        }
1289
1290        public void post(int what, Object obj, int arg1) {
1291            obtainMessage(what, arg1, 0, obj).sendToTarget();
1292        }
1293
1294        @Override
1295        public void handleMessage(Message msg) {
1296            VolumeProvider vp;
1297            switch (msg.what) {
1298                case MSG_COMMAND:
1299                    Command cmd = (Command) msg.obj;
1300                    mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
1301                    break;
1302                case MSG_MEDIA_BUTTON:
1303                    mCallback.onMediaButtonEvent((Intent) msg.obj);
1304                    break;
1305                case MSG_PREPARE:
1306                    mCallback.onPrepare();
1307                    break;
1308                case MSG_PREPARE_MEDIA_ID:
1309                    mCallback.onPrepareFromMediaId((String) msg.obj, msg.getData());
1310                    break;
1311                case MSG_PREPARE_SEARCH:
1312                    mCallback.onPrepareFromSearch((String) msg.obj, msg.getData());
1313                    break;
1314                case MSG_PREPARE_URI:
1315                    mCallback.onPrepareFromUri((Uri) msg.obj, msg.getData());
1316                    break;
1317                case MSG_PLAY:
1318                    mCallback.onPlay();
1319                    break;
1320                case MSG_PLAY_MEDIA_ID:
1321                    mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
1322                    break;
1323                case MSG_PLAY_SEARCH:
1324                    mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
1325                    break;
1326                case MSG_PLAY_URI:
1327                    mCallback.onPlayFromUri((Uri) msg.obj, msg.getData());
1328                    break;
1329                case MSG_SKIP_TO_ITEM:
1330                    mCallback.onSkipToQueueItem((Long) msg.obj);
1331                    break;
1332                case MSG_PAUSE:
1333                    mCallback.onPause();
1334                    break;
1335                case MSG_STOP:
1336                    mCallback.onStop();
1337                    break;
1338                case MSG_NEXT:
1339                    mCallback.onSkipToNext();
1340                    break;
1341                case MSG_PREVIOUS:
1342                    mCallback.onSkipToPrevious();
1343                    break;
1344                case MSG_FAST_FORWARD:
1345                    mCallback.onFastForward();
1346                    break;
1347                case MSG_REWIND:
1348                    mCallback.onRewind();
1349                    break;
1350                case MSG_SEEK_TO:
1351                    mCallback.onSeekTo((Long) msg.obj);
1352                    break;
1353                case MSG_RATE:
1354                    mCallback.onSetRating((Rating) msg.obj);
1355                    break;
1356                case MSG_CUSTOM_ACTION:
1357                    mCallback.onCustomAction((String) msg.obj, msg.getData());
1358                    break;
1359                case MSG_ADJUST_VOLUME:
1360                    synchronized (mLock) {
1361                        vp = mVolumeProvider;
1362                    }
1363                    if (vp != null) {
1364                        vp.onAdjustVolume((int) msg.obj);
1365                    }
1366                    break;
1367                case MSG_SET_VOLUME:
1368                    synchronized (mLock) {
1369                        vp = mVolumeProvider;
1370                    }
1371                    if (vp != null) {
1372                        vp.onSetVolumeTo((int) msg.obj);
1373                    }
1374                    break;
1375            }
1376        }
1377    }
1378}
1379