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