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