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