MediaSession.java revision 1a937b04e63539cb1fab1bde601031d415c7156f
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.NonNull;
20import android.annotation.Nullable;
21import android.app.PendingIntent;
22import android.content.ComponentName;
23import android.content.Intent;
24import android.media.AudioManager;
25import android.media.MediaMetadata;
26import android.media.Rating;
27import android.media.VolumeProvider;
28import android.media.routing.MediaRouter;
29import android.media.session.ISessionController;
30import android.media.session.ISession;
31import android.media.session.ISessionCallback;
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.text.TextUtils;
41import android.util.ArrayMap;
42import android.util.Log;
43
44import java.lang.ref.WeakReference;
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * Allows interaction with media controllers, volume keys, media buttons, and
50 * transport controls.
51 * <p>
52 * A MediaSession should be created when an app wants to publish media playback
53 * information or handle media keys. In general an app only needs one session
54 * for all playback, though multiple sessions can be created to provide finer
55 * grain controls of media.
56 * <p>
57 * Once a session is created the owner of the session may pass its
58 * {@link #getSessionToken() session token} to other processes to allow them to
59 * create a {@link MediaController} to interact with the session.
60 * <p>
61 * To receive commands, media keys, and other events a {@link Callback} must be
62 * set with {@link #addCallback(Callback)}. To receive transport control
63 * commands a {@link TransportControlsCallback} must be set with
64 * {@link #addTransportControlsCallback}.
65 * <p>
66 * When an app is finished performing playback it must call {@link #release()}
67 * to clean up the session and notify any controllers.
68 * <p>
69 * MediaSession objects are thread safe.
70 */
71public final class MediaSession {
72    private static final String TAG = "MediaSession";
73
74    /**
75     * Set this flag on the session to indicate that it can handle media button
76     * events.
77     */
78    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
79
80    /**
81     * Set this flag on the session to indicate that it handles transport
82     * control commands through a {@link TransportControlsCallback}.
83     * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
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    /**
97     * The session uses local playback.
98     */
99    public static final int PLAYBACK_TYPE_LOCAL = 1;
100
101    /**
102     * The session uses remote playback.
103     */
104    public static final int PLAYBACK_TYPE_REMOTE = 2;
105
106    private final Object mLock = new Object();
107
108    private final MediaSession.Token mSessionToken;
109    private final ISession mBinder;
110    private final CallbackStub mCbStub;
111
112    private final ArrayList<CallbackMessageHandler> mCallbacks
113            = new ArrayList<CallbackMessageHandler>();
114    private final ArrayList<TransportMessageHandler> mTransportCallbacks
115            = new ArrayList<TransportMessageHandler>();
116
117    private VolumeProvider mVolumeProvider;
118
119    private boolean mActive = false;
120
121    /**
122     * @hide
123     */
124    public MediaSession(ISession binder, CallbackStub cbStub) {
125        mBinder = binder;
126        mCbStub = cbStub;
127        ISessionController controllerBinder = null;
128        try {
129            controllerBinder = mBinder.getController();
130        } catch (RemoteException e) {
131            throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
132        }
133        mSessionToken = new Token(controllerBinder);
134    }
135
136    /**
137     * Add a callback to receive updates on for the MediaSession. This includes
138     * media button and volume events. The caller's thread will be used to post
139     * events.
140     *
141     * @param callback The callback object
142     */
143    public void addCallback(@NonNull Callback callback) {
144        addCallback(callback, null);
145    }
146
147    /**
148     * Add a callback to receive updates for the MediaSession. This includes
149     * media button and volume events.
150     *
151     * @param callback The callback to receive updates on.
152     * @param handler The handler that events should be posted on.
153     */
154    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
155        if (callback == null) {
156            throw new IllegalArgumentException("Callback cannot be null");
157        }
158        synchronized (mLock) {
159            if (getHandlerForCallbackLocked(callback) != null) {
160                Log.w(TAG, "Callback is already added, ignoring");
161                return;
162            }
163            if (handler == null) {
164                handler = new Handler();
165            }
166            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
167                    callback);
168            mCallbacks.add(msgHandler);
169        }
170    }
171
172    /**
173     * Remove a callback. It will no longer receive updates.
174     *
175     * @param callback The callback to remove.
176     */
177    public void removeCallback(@NonNull Callback callback) {
178        synchronized (mLock) {
179            removeCallbackLocked(callback);
180        }
181    }
182
183    /**
184     * Set an intent for launching UI for this Session. This can be used as a
185     * quick link to an ongoing media screen.
186     *
187     * @param pi The intent to launch to show UI for this Session.
188     */
189    public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
190        // TODO
191    }
192
193    /**
194     * Associates a {@link MediaRouter} with this session to control the destination
195     * of media content.
196     * <p>
197     * A media router may only be associated with at most one session at a time.
198     * </p>
199     *
200     * @param router The media router, or null to remove the current association.
201     */
202    public void setMediaRouter(@Nullable MediaRouter router) {
203        try {
204            mBinder.setMediaRouter(router != null ? router.getBinder() : null);
205        } catch (RemoteException e) {
206            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
207        }
208    }
209
210    /**
211     * Set a media button event receiver component to use to restart playback
212     * after an app has been stopped.
213     *
214     * @param mbr The receiver component to send the media button event to.
215     * @hide
216     */
217    public void setMediaButtonReceiver(@Nullable ComponentName mbr) {
218        try {
219            mBinder.setMediaButtonReceiver(mbr);
220        } catch (RemoteException e) {
221            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
222        }
223    }
224
225    /**
226     * Set any flags for the session.
227     *
228     * @param flags The flags to set for this session.
229     */
230    public void setFlags(int flags) {
231        try {
232            mBinder.setFlags(flags);
233        } catch (RemoteException e) {
234            Log.wtf(TAG, "Failure in setFlags.", e);
235        }
236    }
237
238    /**
239     * Set the stream this session is playing on. This will affect the system's
240     * volume handling for this session. If {@link #setPlaybackToRemote} was
241     * previously called it will stop receiving volume commands and the system
242     * will begin sending volume changes to the appropriate stream.
243     * <p>
244     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
245     *
246     * @param stream The {@link AudioManager} stream this session is playing on.
247     */
248    public void setPlaybackToLocal(int stream) {
249        try {
250            mBinder.configureVolumeHandling(PLAYBACK_TYPE_LOCAL, stream, 0);
251        } catch (RemoteException e) {
252            Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
253        }
254    }
255
256    /**
257     * Configure this session to use remote volume handling. This must be called
258     * to receive volume button events, otherwise the system will adjust the
259     * current stream volume for this session. If {@link #setPlaybackToLocal}
260     * was previously called that stream will stop receiving volume changes for
261     * this session.
262     *
263     * @param volumeProvider The provider that will handle volume changes. May
264     *            not be null.
265     */
266    public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
267        if (volumeProvider == null) {
268            throw new IllegalArgumentException("volumeProvider may not be null!");
269        }
270        mVolumeProvider = volumeProvider;
271        volumeProvider.setCallback(new VolumeProvider.Callback() {
272            @Override
273            public void onVolumeChanged(VolumeProvider volumeProvider) {
274                notifyRemoteVolumeChanged(volumeProvider);
275            }
276        });
277
278        try {
279            mBinder.configureVolumeHandling(PLAYBACK_TYPE_REMOTE, volumeProvider.getVolumeControl(),
280                    volumeProvider.getMaxVolume());
281        } catch (RemoteException e) {
282            Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
283        }
284    }
285
286    /**
287     * Set if this session is currently active and ready to receive commands. If
288     * set to false your session's controller may not be discoverable. You must
289     * set the session to active before it can start receiving media button
290     * events or transport commands.
291     *
292     * @param active Whether this session is active or not.
293     */
294    public void setActive(boolean active) {
295        if (mActive == active) {
296            return;
297        }
298        try {
299            mBinder.setActive(active);
300            mActive = active;
301        } catch (RemoteException e) {
302            Log.wtf(TAG, "Failure in setActive.", e);
303        }
304    }
305
306    /**
307     * Get the current active state of this session.
308     *
309     * @return True if the session is active, false otherwise.
310     */
311    public boolean isActive() {
312        return mActive;
313    }
314
315    /**
316     * Send a proprietary event to all MediaControllers listening to this
317     * Session. It's up to the Controller/Session owner to determine the meaning
318     * of any events.
319     *
320     * @param event The name of the event to send
321     * @param extras Any extras included with the event
322     */
323    public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
324        if (TextUtils.isEmpty(event)) {
325            throw new IllegalArgumentException("event cannot be null or empty");
326        }
327        try {
328            mBinder.sendEvent(event, extras);
329        } catch (RemoteException e) {
330            Log.wtf(TAG, "Error sending event", e);
331        }
332    }
333
334    /**
335     * This must be called when an app has finished performing playback. If
336     * playback is expected to start again shortly the session can be left open,
337     * but it must be released if your activity or service is being destroyed.
338     */
339    public void release() {
340        try {
341            mBinder.destroy();
342        } catch (RemoteException e) {
343            Log.wtf(TAG, "Error releasing session: ", e);
344        }
345    }
346
347    /**
348     * Retrieve a token object that can be used by apps to create a
349     * {@link MediaController} for interacting with this session. The owner of
350     * the session is responsible for deciding how to distribute these tokens.
351     *
352     * @return A token that can be used to create a MediaController for this
353     *         session
354     */
355    public @NonNull Token getSessionToken() {
356        return mSessionToken;
357    }
358
359    /**
360     * Add a callback to receive transport controls on, such as play, rewind, or
361     * fast forward.
362     *
363     * @param callback The callback object
364     */
365    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) {
366        addTransportControlsCallback(callback, null);
367    }
368
369    /**
370     * Add a callback to receive transport controls on, such as play, rewind, or
371     * fast forward. The updates will be posted to the specified handler. If no
372     * handler is provided they will be posted to the caller's thread.
373     *
374     * @param callback The callback to receive updates on
375     * @param handler The handler to post the updates on
376     */
377    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback,
378            @Nullable Handler handler) {
379        if (callback == null) {
380            throw new IllegalArgumentException("Callback cannot be null");
381        }
382        synchronized (mLock) {
383            if (getTransportControlsHandlerForCallbackLocked(callback) != null) {
384                Log.w(TAG, "Callback is already added, ignoring");
385                return;
386            }
387            if (handler == null) {
388                handler = new Handler();
389            }
390            TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(),
391                    callback);
392            mTransportCallbacks.add(msgHandler);
393        }
394    }
395
396    /**
397     * Stop receiving transport controls on the specified callback. If an update
398     * has already been posted you may still receive it after this call returns.
399     *
400     * @param callback The callback to stop receiving updates on
401     */
402    public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) {
403        if (callback == null) {
404            throw new IllegalArgumentException("Callback cannot be null");
405        }
406        synchronized (mLock) {
407            removeTransportControlsCallbackLocked(callback);
408        }
409    }
410
411    /**
412     * Update the current playback state.
413     *
414     * @param state The current state of playback
415     */
416    public void setPlaybackState(@Nullable PlaybackState state) {
417        try {
418            mBinder.setPlaybackState(state);
419        } catch (RemoteException e) {
420            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
421        }
422    }
423
424    /**
425     * Update the current metadata. New metadata can be created using
426     * {@link android.media.MediaMetadata.Builder}.
427     *
428     * @param metadata The new metadata
429     */
430    public void setMetadata(@Nullable MediaMetadata metadata) {
431        try {
432            mBinder.setMetadata(metadata);
433        } catch (RemoteException e) {
434            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
435        }
436    }
437
438    /**
439     * Notify the system that the remote volume changed.
440     *
441     * @param provider The provider that is handling volume changes.
442     * @hide
443     */
444    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
445        if (provider == null || provider != mVolumeProvider) {
446            Log.w(TAG, "Received update from stale volume provider");
447            return;
448        }
449        try {
450            mBinder.setCurrentVolume(provider.onGetCurrentVolume());
451        } catch (RemoteException e) {
452            Log.e(TAG, "Error in notifyVolumeChanged", e);
453        }
454    }
455
456    private void dispatchPlay() {
457        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY);
458    }
459
460    private void dispatchPause() {
461        postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE);
462    }
463
464    private void dispatchStop() {
465        postToTransportCallbacks(TransportMessageHandler.MSG_STOP);
466    }
467
468    private void dispatchNext() {
469        postToTransportCallbacks(TransportMessageHandler.MSG_NEXT);
470    }
471
472    private void dispatchPrevious() {
473        postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS);
474    }
475
476    private void dispatchFastForward() {
477        postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD);
478    }
479
480    private void dispatchRewind() {
481        postToTransportCallbacks(TransportMessageHandler.MSG_REWIND);
482    }
483
484    private void dispatchSeekTo(long pos) {
485        postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos);
486    }
487
488    private void dispatchRate(Rating rating) {
489        postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating);
490    }
491
492    private TransportMessageHandler getTransportControlsHandlerForCallbackLocked(
493            TransportControlsCallback callback) {
494        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
495            TransportMessageHandler handler = mTransportCallbacks.get(i);
496            if (callback == handler.mCallback) {
497                return handler;
498            }
499        }
500        return null;
501    }
502
503    private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) {
504        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
505            if (callback == mTransportCallbacks.get(i).mCallback) {
506                mTransportCallbacks.remove(i);
507                return true;
508            }
509        }
510        return false;
511    }
512
513    private void postToTransportCallbacks(int what, Object obj) {
514        synchronized (mLock) {
515            for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
516                mTransportCallbacks.get(i).post(what, obj);
517            }
518        }
519    }
520
521    private void postToTransportCallbacks(int what) {
522        postToTransportCallbacks(what, null);
523    }
524
525    private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) {
526        if (cb == null) {
527            throw new IllegalArgumentException("Callback cannot be null");
528        }
529        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
530            CallbackMessageHandler handler = mCallbacks.get(i);
531            if (cb == handler.mCallback) {
532                return handler;
533            }
534        }
535        return null;
536    }
537
538    private boolean removeCallbackLocked(Callback cb) {
539        if (cb == null) {
540            throw new IllegalArgumentException("Callback cannot be null");
541        }
542        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
543            CallbackMessageHandler handler = mCallbacks.get(i);
544            if (cb == handler.mCallback) {
545                mCallbacks.remove(i);
546                return true;
547            }
548        }
549        return false;
550    }
551
552    private void postCommand(String command, Bundle extras, ResultReceiver resultCb) {
553        Command cmd = new Command(command, extras, resultCb);
554        synchronized (mLock) {
555            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
556                mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd);
557            }
558        }
559    }
560
561    private void postMediaButton(Intent mediaButtonIntent) {
562        synchronized (mLock) {
563            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
564                mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
565            }
566        }
567    }
568
569    /**
570     * Return true if this is considered an active playback state.
571     *
572     * @hide
573     */
574    public static boolean isActiveState(int state) {
575        switch (state) {
576            case PlaybackState.STATE_FAST_FORWARDING:
577            case PlaybackState.STATE_REWINDING:
578            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
579            case PlaybackState.STATE_SKIPPING_TO_NEXT:
580            case PlaybackState.STATE_BUFFERING:
581            case PlaybackState.STATE_CONNECTING:
582            case PlaybackState.STATE_PLAYING:
583                return true;
584        }
585        return false;
586    }
587
588    /**
589     * Represents an ongoing session. This may be passed to apps by the session
590     * owner to allow them to create a {@link MediaController} to communicate with
591     * the session.
592     */
593    public static final class Token implements Parcelable {
594        private ISessionController mBinder;
595
596        /**
597         * @hide
598         */
599        public Token(ISessionController binder) {
600            mBinder = binder;
601        }
602
603        @Override
604        public int describeContents() {
605            return 0;
606        }
607
608        @Override
609        public void writeToParcel(Parcel dest, int flags) {
610            dest.writeStrongBinder(mBinder.asBinder());
611        }
612
613        ISessionController getBinder() {
614            return mBinder;
615        }
616
617        public static final Parcelable.Creator<Token> CREATOR
618                = new Parcelable.Creator<Token>() {
619            @Override
620            public Token createFromParcel(Parcel in) {
621                return new Token(ISessionController.Stub.asInterface(in.readStrongBinder()));
622            }
623
624            @Override
625            public Token[] newArray(int size) {
626                return new Token[size];
627            }
628        };
629    }
630
631    /**
632     * Receives generic commands or updates from controllers and the system.
633     * Callbacks may be registered using {@link #addCallback}.
634     */
635    public abstract static class Callback {
636
637        public Callback() {
638        }
639
640        /**
641         * Called when a media button is pressed and this session has the
642         * highest priority or a controller sends a media button event to the
643         * session. TODO determine if using Intents identical to the ones
644         * RemoteControlClient receives is useful
645         * <p>
646         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
647         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
648         *
649         * @param mediaButtonIntent an intent containing the KeyEvent as an
650         *            extra
651         */
652        public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
653        }
654
655        /**
656         * Called when a controller has sent a custom command to this session.
657         * The owner of the session may handle custom commands but is not
658         * required to.
659         *
660         * @param command The command name.
661         * @param extras Optional parameters for the command, may be null.
662         * @param cb A result receiver to which a result may be sent by the command, may be null.
663         */
664        public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
665                @Nullable ResultReceiver cb) {
666        }
667    }
668
669    /**
670     * Receives transport control commands. Callbacks may be registered using
671     * {@link #addTransportControlsCallback}.
672     */
673    public static abstract class TransportControlsCallback {
674
675        /**
676         * Override to handle requests to begin playback.
677         */
678        public void onPlay() {
679        }
680
681        /**
682         * Override to handle requests to pause playback.
683         */
684        public void onPause() {
685        }
686
687        /**
688         * Override to handle requests to skip to the next media item.
689         */
690        public void onSkipToNext() {
691        }
692
693        /**
694         * Override to handle requests to skip to the previous media item.
695         */
696        public void onSkipToPrevious() {
697        }
698
699        /**
700         * Override to handle requests to fast forward.
701         */
702        public void onFastForward() {
703        }
704
705        /**
706         * Override to handle requests to rewind.
707         */
708        public void onRewind() {
709        }
710
711        /**
712         * Override to handle requests to stop playback.
713         */
714        public void onStop() {
715        }
716
717        /**
718         * Override to handle requests to seek to a specific position in ms.
719         *
720         * @param pos New position to move to, in milliseconds.
721         */
722        public void onSeekTo(long pos) {
723        }
724
725        /**
726         * Override to handle the item being rated.
727         *
728         * @param rating
729         */
730        public void onSetRating(@NonNull Rating rating) {
731        }
732    }
733
734    /**
735     * @hide
736     */
737    public static class CallbackStub extends ISessionCallback.Stub {
738        private WeakReference<MediaSession> mMediaSession;
739
740        public void setMediaSession(MediaSession session) {
741            mMediaSession = new WeakReference<MediaSession>(session);
742        }
743
744        @Override
745        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
746            MediaSession session = mMediaSession.get();
747            if (session != null) {
748                session.postCommand(command, extras, cb);
749            }
750        }
751
752        @Override
753        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
754                ResultReceiver cb) {
755            MediaSession session = mMediaSession.get();
756            try {
757                if (session != null) {
758                    session.postMediaButton(mediaButtonIntent);
759                }
760            } finally {
761                if (cb != null) {
762                    cb.send(sequenceNumber, null);
763                }
764            }
765        }
766
767        @Override
768        public void onPlay() {
769            MediaSession session = mMediaSession.get();
770            if (session != null) {
771                session.dispatchPlay();
772            }
773        }
774
775        @Override
776        public void onPause() {
777            MediaSession session = mMediaSession.get();
778            if (session != null) {
779                session.dispatchPause();
780            }
781        }
782
783        @Override
784        public void onStop() {
785            MediaSession session = mMediaSession.get();
786            if (session != null) {
787                session.dispatchStop();
788            }
789        }
790
791        @Override
792        public void onNext() {
793            MediaSession session = mMediaSession.get();
794            if (session != null) {
795                session.dispatchNext();
796            }
797        }
798
799        @Override
800        public void onPrevious() {
801            MediaSession session = mMediaSession.get();
802            if (session != null) {
803                session.dispatchPrevious();
804            }
805        }
806
807        @Override
808        public void onFastForward() {
809            MediaSession session = mMediaSession.get();
810            if (session != null) {
811                session.dispatchFastForward();
812            }
813        }
814
815        @Override
816        public void onRewind() {
817            MediaSession session = mMediaSession.get();
818            if (session != null) {
819                session.dispatchRewind();
820            }
821        }
822
823        @Override
824        public void onSeekTo(long pos) {
825            MediaSession session = mMediaSession.get();
826            if (session != null) {
827                session.dispatchSeekTo(pos);
828            }
829        }
830
831        @Override
832        public void onRate(Rating rating) {
833            MediaSession session = mMediaSession.get();
834            if (session != null) {
835                session.dispatchRate(rating);
836            }
837        }
838
839        @Override
840        public void onAdjustVolumeBy(int delta) {
841            MediaSession session = mMediaSession.get();
842            if (session != null) {
843                if (session.mVolumeProvider != null) {
844                    session.mVolumeProvider.onAdjustVolumeBy(delta);
845                }
846            }
847        }
848
849        @Override
850        public void onSetVolumeTo(int value) {
851            MediaSession session = mMediaSession.get();
852            if (session != null) {
853                if (session.mVolumeProvider != null) {
854                    session.mVolumeProvider.onSetVolumeTo(value);
855                }
856            }
857        }
858
859    }
860
861    private class CallbackMessageHandler extends Handler {
862        private static final int MSG_MEDIA_BUTTON = 1;
863        private static final int MSG_COMMAND = 2;
864
865        private MediaSession.Callback mCallback;
866
867        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
868            super(looper, null, true);
869            mCallback = callback;
870        }
871
872        @Override
873        public void handleMessage(Message msg) {
874            synchronized (mLock) {
875                if (mCallback == null) {
876                    return;
877                }
878                switch (msg.what) {
879                    case MSG_MEDIA_BUTTON:
880                        mCallback.onMediaButtonEvent((Intent) msg.obj);
881                        break;
882                    case MSG_COMMAND:
883                        Command cmd = (Command) msg.obj;
884                        mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub);
885                        break;
886                }
887            }
888        }
889
890        public void post(int what, Object obj) {
891            obtainMessage(what, obj).sendToTarget();
892        }
893
894        public void post(int what, Object obj, int arg1) {
895            obtainMessage(what, arg1, 0, obj).sendToTarget();
896        }
897    }
898
899    private static final class Command {
900        public final String command;
901        public final Bundle extras;
902        public final ResultReceiver stub;
903
904        public Command(String command, Bundle extras, ResultReceiver stub) {
905            this.command = command;
906            this.extras = extras;
907            this.stub = stub;
908        }
909    }
910
911    private class TransportMessageHandler extends Handler {
912        private static final int MSG_PLAY = 1;
913        private static final int MSG_PAUSE = 2;
914        private static final int MSG_STOP = 3;
915        private static final int MSG_NEXT = 4;
916        private static final int MSG_PREVIOUS = 5;
917        private static final int MSG_FAST_FORWARD = 6;
918        private static final int MSG_REWIND = 7;
919        private static final int MSG_SEEK_TO = 8;
920        private static final int MSG_RATE = 9;
921
922        private TransportControlsCallback mCallback;
923
924        public TransportMessageHandler(Looper looper, TransportControlsCallback cb) {
925            super(looper);
926            mCallback = cb;
927        }
928
929        public void post(int what, Object obj) {
930            obtainMessage(what, obj).sendToTarget();
931        }
932
933        public void post(int what) {
934            post(what, null);
935        }
936
937        @Override
938        public void handleMessage(Message msg) {
939            switch (msg.what) {
940                case MSG_PLAY:
941                    mCallback.onPlay();
942                    break;
943                case MSG_PAUSE:
944                    mCallback.onPause();
945                    break;
946                case MSG_STOP:
947                    mCallback.onStop();
948                    break;
949                case MSG_NEXT:
950                    mCallback.onSkipToNext();
951                    break;
952                case MSG_PREVIOUS:
953                    mCallback.onSkipToPrevious();
954                    break;
955                case MSG_FAST_FORWARD:
956                    mCallback.onFastForward();
957                    break;
958                case MSG_REWIND:
959                    mCallback.onRewind();
960                    break;
961                case MSG_SEEK_TO:
962                    mCallback.onSeekTo((Long) msg.obj);
963                    break;
964                case MSG_RATE:
965                    mCallback.onSetRating((Rating) msg.obj);
966                    break;
967            }
968        }
969    }
970}
971