MediaSessionRecord.java revision 73e23e229dd1a2d25687b1c6a63c708665378e41
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 com.android.server.media;
18
19import android.app.ActivityManager;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.media.routeprovider.RouteRequest;
23import android.media.session.ISessionController;
24import android.media.session.ISessionControllerCallback;
25import android.media.session.ISession;
26import android.media.session.ISessionCallback;
27import android.media.session.MediaController;
28import android.media.session.RemoteVolumeProvider;
29import android.media.session.RouteCommand;
30import android.media.session.RouteInfo;
31import android.media.session.RouteOptions;
32import android.media.session.RouteEvent;
33import android.media.session.MediaSession;
34import android.media.session.MediaSessionInfo;
35import android.media.session.RouteInterface;
36import android.media.session.PlaybackState;
37import android.media.AudioManager;
38import android.media.MediaMetadata;
39import android.media.Rating;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.Message;
45import android.os.RemoteException;
46import android.os.ResultReceiver;
47import android.os.SystemClock;
48import android.os.UserHandle;
49import android.text.TextUtils;
50import android.util.Log;
51import android.util.Pair;
52import android.util.Slog;
53import android.view.KeyEvent;
54
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.List;
58import java.util.UUID;
59
60/**
61 * This is the system implementation of a Session. Apps will interact with the
62 * MediaSession wrapper class instead.
63 */
64public class MediaSessionRecord implements IBinder.DeathRecipient {
65    private static final String TAG = "MediaSessionRecord";
66
67    /**
68     * These are the playback states that count as currently active.
69     */
70    private static final int[] ACTIVE_STATES = {
71            PlaybackState.STATE_FAST_FORWARDING,
72            PlaybackState.STATE_REWINDING,
73            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
74            PlaybackState.STATE_SKIPPING_TO_NEXT,
75            PlaybackState.STATE_BUFFERING,
76            PlaybackState.STATE_CONNECTING,
77            PlaybackState.STATE_PLAYING };
78
79    /**
80     * The length of time a session will still be considered active after
81     * pausing in ms.
82     */
83    private static final int ACTIVE_BUFFER = 30000;
84
85    private final MessageHandler mHandler;
86
87    private final int mOwnerPid;
88    private final int mOwnerUid;
89    private final int mUserId;
90    private final MediaSessionInfo mSessionInfo;
91    private final String mTag;
92    private final ControllerStub mController;
93    private final SessionStub mSession;
94    private final SessionCb mSessionCb;
95    private final MediaSessionService mService;
96
97    private final Object mLock = new Object();
98    private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
99            new ArrayList<ISessionControllerCallback>();
100    private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
101
102    private RouteInfo mRoute;
103    private RouteOptions mRequest;
104    private RouteConnectionRecord mConnection;
105    // TODO define a RouteState class with relevant info
106    private int mRouteState;
107    private long mFlags;
108
109    // TransportPerformer fields
110
111    private MediaMetadata mMetadata;
112    private PlaybackState mPlaybackState;
113    private int mRatingType;
114    private long mLastActiveTime;
115    // End TransportPerformer fields
116
117    // Volume handling fields
118    private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
119    private int mAudioStream = AudioManager.STREAM_MUSIC;
120    private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
121    private int mMaxVolume = 0;
122    private int mCurrentVolume = 0;
123    // End volume handling fields
124
125    private boolean mIsActive = false;
126    private boolean mDestroyed = false;
127
128    public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
129            ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
130        mOwnerPid = ownerPid;
131        mOwnerUid = ownerUid;
132        mUserId = userId;
133        mSessionInfo = new MediaSessionInfo(UUID.randomUUID().toString(), ownerPackageName,
134                ownerPid);
135        mTag = tag;
136        mController = new ControllerStub();
137        mSession = new SessionStub();
138        mSessionCb = new SessionCb(cb);
139        mService = service;
140        mHandler = new MessageHandler(handler.getLooper());
141    }
142
143    /**
144     * Get the binder for the {@link MediaSession}.
145     *
146     * @return The session binder apps talk to.
147     */
148    public ISession getSessionBinder() {
149        return mSession;
150    }
151
152    /**
153     * Get the binder for the {@link MediaController}.
154     *
155     * @return The controller binder apps talk to.
156     */
157    public ISessionController getControllerBinder() {
158        return mController;
159    }
160
161    /**
162     * Get the set of route requests this session is interested in.
163     *
164     * @return The list of RouteRequests
165     */
166    public List<RouteRequest> getRouteRequests() {
167        return mRequests;
168    }
169
170    /**
171     * Get the route this session is currently on.
172     *
173     * @return The route the session is on.
174     */
175    public RouteInfo getRoute() {
176        return mRoute;
177    }
178
179    /**
180     * Get the info for this session.
181     *
182     * @return Info that identifies this session.
183     */
184    public MediaSessionInfo getSessionInfo() {
185        return mSessionInfo;
186    }
187
188    /**
189     * Get this session's flags.
190     *
191     * @return The flags for this session.
192     */
193    public long getFlags() {
194        return mFlags;
195    }
196
197    /**
198     * Check if this session has the specified flag.
199     *
200     * @param flag The flag to check.
201     * @return True if this session has that flag set, false otherwise.
202     */
203    public boolean hasFlag(int flag) {
204        return (mFlags & flag) != 0;
205    }
206
207    /**
208     * Get the user id this session was created for.
209     *
210     * @return The user id for this session.
211     */
212    public int getUserId() {
213        return mUserId;
214    }
215
216    /**
217     * Check if this session has system priorty and should receive media buttons
218     * before any other sessions.
219     *
220     * @return True if this is a system priority session, false otherwise
221     */
222    public boolean isSystemPriority() {
223        return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
224    }
225
226    /**
227     * Set the selected route. This does not connect to the route, just notifies
228     * the app that a new route has been selected.
229     *
230     * @param route The route that was selected.
231     */
232    public void selectRoute(RouteInfo route) {
233        synchronized (mLock) {
234            if (route != mRoute) {
235                disconnect(MediaSession.DISCONNECT_REASON_ROUTE_CHANGED);
236            }
237            mRoute = route;
238        }
239        mSessionCb.sendRouteChange(route);
240    }
241
242    /**
243     * Update the state of the route this session is using and notify the
244     * session.
245     *
246     * @param state The new state of the route.
247     */
248    public void setRouteState(int state) {
249        mSessionCb.sendRouteStateChange(state);
250    }
251
252    /**
253     * Send an event to this session from the route it is using.
254     *
255     * @param event The event to send.
256     */
257    public void sendRouteEvent(RouteEvent event) {
258        mSessionCb.sendRouteEvent(event);
259    }
260
261    /**
262     * Send a volume adjustment to the session owner.
263     *
264     * @param delta The amount to adjust the volume by.
265     */
266    public void adjustVolumeBy(int delta) {
267        if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
268            // Nothing to do, the volume cannot be changed
269            return;
270        }
271        mSessionCb.adjustVolumeBy(delta);
272    }
273
274    public void setVolumeTo(int value) {
275        if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
276            // Nothing to do. The volume can't be set directly.
277            return;
278        }
279        mSessionCb.setVolumeTo(value);
280    }
281
282    /**
283     * Set the connection to use for the selected route and notify the app it is
284     * now connected.
285     *
286     * @param route The route the connection is to.
287     * @param request The request that was used to connect.
288     * @param connection The connection to the route.
289     * @return True if this connection is still valid, false if it is stale.
290     */
291    public boolean setRouteConnected(RouteInfo route, RouteOptions request,
292            RouteConnectionRecord connection) {
293        synchronized (mLock) {
294            if (mDestroyed) {
295                Log.i(TAG, "setRouteConnected: session has been destroyed");
296                connection.disconnect();
297                return false;
298            }
299            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
300                Log.w(TAG, "setRouteConnected: connected route is stale");
301                connection.disconnect();
302                return false;
303            }
304            if (request != mRequest) {
305                Log.w(TAG, "setRouteConnected: connection request is stale");
306                connection.disconnect();
307                return false;
308            }
309            mConnection = connection;
310            mConnection.setListener(mConnectionListener);
311            mSessionCb.sendRouteConnected();
312        }
313        return true;
314    }
315
316    /**
317     * Check if this session has been set to active by the app.
318     *
319     * @return True if the session is active, false otherwise.
320     */
321    public boolean isActive() {
322        return mIsActive && !mDestroyed;
323    }
324
325    /**
326     * Check if the session is currently performing playback. This will also
327     * return true if the session was recently paused.
328     *
329     * @param includeRecentlyActive True if playback that was recently paused
330     *            should count, false if it shouldn't.
331     * @return True if the session is performing playback, false otherwise.
332     */
333    public boolean isPlaybackActive(boolean includeRecentlyActive) {
334        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
335        if (isActiveState(state)) {
336            return true;
337        }
338        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
339            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
340            if (inactiveTime < ACTIVE_BUFFER) {
341                return true;
342            }
343        }
344        return false;
345    }
346
347    /**
348     * Get the type of playback, either local or remote.
349     *
350     * @return The current type of playback.
351     */
352    public int getPlaybackType() {
353        return mPlaybackType;
354    }
355
356    /**
357     * Get the local audio stream being used. Only valid if playback type is
358     * local.
359     *
360     * @return The audio stream the session is using.
361     */
362    public int getAudioStream() {
363        return mAudioStream;
364    }
365
366    /**
367     * Get the type of volume control. Only valid if playback type is remote.
368     *
369     * @return The volume control type being used.
370     */
371    public int getVolumeControl() {
372        return mVolumeControlType;
373    }
374
375    /**
376     * Get the max volume that can be set. Only valid if playback type is
377     * remote.
378     *
379     * @return The max volume that can be set.
380     */
381    public int getMaxVolume() {
382        return mMaxVolume;
383    }
384
385    /**
386     * Get the current volume for this session. Only valid if playback type is
387     * remote.
388     *
389     * @return The current volume of the remote playback.
390     */
391    public int getCurrentVolume() {
392        return mCurrentVolume;
393    }
394
395    /**
396     * @return True if this session is currently connected to a route.
397     */
398    public boolean isConnected() {
399        return mConnection != null;
400    }
401
402    public void disconnect(int reason) {
403        synchronized (mLock) {
404            if (!mDestroyed) {
405                disconnectLocked(reason);
406            }
407        }
408    }
409
410    private void disconnectLocked(int reason) {
411        if (mConnection != null) {
412            mConnection.setListener(null);
413            mConnection.disconnect();
414            mConnection = null;
415            pushDisconnected(reason);
416        }
417    }
418
419    public boolean isTransportControlEnabled() {
420        return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
421    }
422
423    @Override
424    public void binderDied() {
425        mService.sessionDied(this);
426    }
427
428    /**
429     * Finish cleaning up this session, including disconnecting if connected and
430     * removing the death observer from the callback binder.
431     */
432    public void onDestroy() {
433        synchronized (mLock) {
434            if (mDestroyed) {
435                return;
436            }
437            if (isConnected()) {
438                disconnectLocked(MediaSession.DISCONNECT_REASON_SESSION_DESTROYED);
439            }
440            mRoute = null;
441            mRequest = null;
442            mDestroyed = true;
443        }
444    }
445
446    public ISessionCallback getCallback() {
447        return mSessionCb.mCb;
448    }
449
450    public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
451        mSessionCb.sendMediaButton(ke, sequenceId, cb);
452    }
453
454    public void dump(PrintWriter pw, String prefix) {
455        pw.println(prefix + mTag + " " + this);
456
457        final String indent = prefix + "  ";
458        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
459                + ", userId=" + mUserId);
460        pw.println(indent + "info=" + mSessionInfo.toString());
461        pw.println(indent + "active=" + mIsActive);
462        pw.println(indent + "flags=" + mFlags);
463        pw.println(indent + "rating type=" + mRatingType);
464        pw.println(indent + "controllers: " + mControllerCallbacks.size());
465        pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
466        pw.println(indent + "metadata:" + getShortMetadataString());
467        pw.println(indent + "route requests {");
468        int size = mRequests.size();
469        for (int i = 0; i < size; i++) {
470            pw.println(indent + "  " + mRequests.get(i).toString());
471        }
472        pw.println(indent + "}");
473        pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString()));
474        pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString()));
475        pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString()));
476    }
477
478    private boolean isActiveState(int state) {
479        for (int i = 0; i < ACTIVE_STATES.length; i++) {
480            if (ACTIVE_STATES[i] == state) {
481                return true;
482            }
483        }
484        return false;
485    }
486
487    private String getShortMetadataString() {
488        int fields = mMetadata == null ? 0 : mMetadata.size();
489        String title = mMetadata == null ? null : mMetadata
490                .getString(MediaMetadata.METADATA_KEY_TITLE);
491        return "size=" + fields + ", title=" + title;
492    }
493
494    private void pushDisconnected(int reason) {
495        synchronized (mLock) {
496            mSessionCb.sendRouteDisconnected(reason);
497        }
498    }
499
500    private void pushPlaybackStateUpdate() {
501        synchronized (mLock) {
502            if (mDestroyed) {
503                return;
504            }
505            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
506                ISessionControllerCallback cb = mControllerCallbacks.get(i);
507                try {
508                    cb.onPlaybackStateChanged(mPlaybackState);
509                } catch (RemoteException e) {
510                    Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e);
511                    mControllerCallbacks.remove(i);
512                }
513            }
514        }
515    }
516
517    private void pushMetadataUpdate() {
518        synchronized (mLock) {
519            if (mDestroyed) {
520                return;
521            }
522            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
523                ISessionControllerCallback cb = mControllerCallbacks.get(i);
524                try {
525                    cb.onMetadataChanged(mMetadata);
526                } catch (RemoteException e) {
527                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e);
528                    mControllerCallbacks.remove(i);
529                }
530            }
531        }
532    }
533
534    private void pushRouteUpdate() {
535        synchronized (mLock) {
536            if (mDestroyed) {
537                return;
538            }
539            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
540                ISessionControllerCallback cb = mControllerCallbacks.get(i);
541                try {
542                    cb.onRouteChanged(mRoute);
543                } catch (RemoteException e) {
544                    Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
545                    mControllerCallbacks.remove(i);
546                }
547            }
548        }
549    }
550
551    private void pushEvent(String event, Bundle data) {
552        synchronized (mLock) {
553            if (mDestroyed) {
554                return;
555            }
556            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
557                ISessionControllerCallback cb = mControllerCallbacks.get(i);
558                try {
559                    cb.onEvent(event, data);
560                } catch (RemoteException e) {
561                    Log.w(TAG, "Error with callback in pushEvent.", e);
562                }
563            }
564        }
565    }
566
567    private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
568        synchronized (mLock) {
569            if (mDestroyed) {
570                return;
571            }
572            if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
573                if (cb != null) {
574                    cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
575                    return;
576                }
577            }
578            if (mConnection != null) {
579                mConnection.sendCommand(command, cb);
580            } else if (cb != null) {
581                cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
582            }
583        }
584    }
585
586    private PlaybackState getStateWithUpdatedPosition() {
587        PlaybackState state = mPlaybackState;
588        long duration = -1;
589        if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
590            duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
591        }
592        PlaybackState result = null;
593        if (state != null) {
594            if (state.getState() == PlaybackState.STATE_PLAYING
595                    || state.getState() == PlaybackState.STATE_FAST_FORWARDING
596                    || state.getState() == PlaybackState.STATE_REWINDING) {
597                long updateTime = state.getLastPositionUpdateTime();
598                if (updateTime > 0) {
599                    long position = (long) (state.getPlaybackRate()
600                            * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
601                    if (duration >= 0 && position > duration) {
602                        position = duration;
603                    } else if (position < 0) {
604                        position = 0;
605                    }
606                    result = new PlaybackState(state);
607                    result.setState(state.getState(), position, state.getPlaybackRate());
608                }
609            }
610        }
611        return result == null ? state : result;
612    }
613
614    private final RouteConnectionRecord.Listener mConnectionListener
615            = new RouteConnectionRecord.Listener() {
616        @Override
617        public void onEvent(RouteEvent event) {
618            RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
619                    event.getEvent(), event.getExtras());
620            mSessionCb.sendRouteEvent(eventForSession);
621        }
622
623        @Override
624        public void disconnect() {
625            MediaSessionRecord.this.disconnect(MediaSession.DISCONNECT_REASON_PROVIDER_DISCONNECTED);
626        }
627    };
628
629    private final class SessionStub extends ISession.Stub {
630        @Override
631        public void destroy() {
632            mService.destroySession(MediaSessionRecord.this);
633        }
634
635        @Override
636        public void sendEvent(String event, Bundle data) {
637            mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
638        }
639
640        @Override
641        public ISessionController getController() {
642            return mController;
643        }
644
645        @Override
646        public void setActive(boolean active) {
647            mIsActive = active;
648            mService.updateSession(MediaSessionRecord.this);
649            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
650        }
651
652        @Override
653        public void setFlags(int flags) {
654            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
655                int pid = getCallingPid();
656                int uid = getCallingUid();
657                mService.enforcePhoneStatePermission(pid, uid);
658            }
659            mFlags = flags;
660            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
661        }
662
663        @Override
664        public void setMetadata(MediaMetadata metadata) {
665            mMetadata = metadata;
666            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
667        }
668
669        @Override
670        public void setPlaybackState(PlaybackState state) {
671            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
672            int newState = state == null ? 0 : state.getState();
673            if (isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
674                mLastActiveTime = SystemClock.elapsedRealtime();
675            }
676            mPlaybackState = state;
677            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
678            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
679        }
680
681        @Override
682        public void setRatingType(int type) {
683            mRatingType = type;
684        }
685
686        @Override
687        public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
688            mHandler.post(MessageHandler.MSG_SEND_COMMAND,
689                    new Pair<RouteCommand, ResultReceiver>(command, cb));
690        }
691
692        @Override
693        public boolean setRoute(RouteInfo route) throws RemoteException {
694            // TODO decide if allowed to set route and if the route exists
695            return false;
696        }
697
698        @Override
699        public void connectToRoute(RouteInfo route, RouteOptions request)
700                throws RemoteException {
701            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
702                throw new RemoteException("RouteInfo does not match current route");
703            }
704            mService.connectToRoute(MediaSessionRecord.this, route, request);
705            mRequest = request;
706        }
707
708        @Override
709        public void disconnectFromRoute(RouteInfo route) {
710            if (route != null && mRoute != null
711                    && TextUtils.equals(route.getId(), mRoute.getId())) {
712                disconnect(MediaSession.DISCONNECT_REASON_SESSION_DISCONNECTED);
713            }
714        }
715
716        @Override
717        public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
718            mRequests.clear();
719            for (int i = options.size() - 1; i >= 0; i--) {
720                RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
721                        false);
722                mRequests.add(request);
723            }
724        }
725
726        @Override
727        public void setCurrentVolume(int volume) {
728            mCurrentVolume = volume;
729        }
730
731        @Override
732        public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
733            switch(type) {
734                case MediaSession.VOLUME_TYPE_LOCAL:
735                    mPlaybackType = type;
736                    int audioStream = arg1;
737                    if (isValidStream(audioStream)) {
738                        mAudioStream = audioStream;
739                    } else {
740                        Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream");
741                        mAudioStream = AudioManager.STREAM_MUSIC;
742                    }
743                    break;
744                case MediaSession.VOLUME_TYPE_REMOTE:
745                    mPlaybackType = type;
746                    mVolumeControlType = arg1;
747                    mMaxVolume = arg2;
748                    break;
749                default:
750                    throw new IllegalArgumentException("Volume handling type " + type
751                            + " not recognized.");
752            }
753        }
754
755        private boolean isValidStream(int stream) {
756            return stream >= AudioManager.STREAM_VOICE_CALL
757                    && stream <= AudioManager.STREAM_NOTIFICATION;
758        }
759    }
760
761    class SessionCb {
762        private final ISessionCallback mCb;
763
764        public SessionCb(ISessionCallback cb) {
765            mCb = cb;
766        }
767
768        public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
769            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
770            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
771            try {
772                mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
773                return true;
774            } catch (RemoteException e) {
775                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
776            }
777            return false;
778        }
779
780        public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
781            try {
782                mCb.onCommand(command, extras, cb);
783            } catch (RemoteException e) {
784                Slog.e(TAG, "Remote failure in sendCommand.", e);
785            }
786        }
787
788        public void sendRouteChange(RouteInfo route) {
789            try {
790                mCb.onRequestRouteChange(route);
791            } catch (RemoteException e) {
792                Slog.e(TAG, "Remote failure in sendRouteChange.", e);
793            }
794        }
795
796        public void sendRouteStateChange(int state) {
797            try {
798                mCb.onRouteStateChange(state);
799            } catch (RemoteException e) {
800                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
801            }
802        }
803
804        public void sendRouteEvent(RouteEvent event) {
805            try {
806                mCb.onRouteEvent(event);
807            } catch (RemoteException e) {
808                Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
809            }
810        }
811
812        public void sendRouteConnected() {
813            try {
814                mCb.onRouteConnected(mRoute, mRequest);
815            } catch (RemoteException e) {
816                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
817            }
818        }
819
820        public void sendRouteDisconnected(int reason) {
821            try {
822                mCb.onRouteDisconnected(mRoute, reason);
823            } catch (RemoteException e) {
824                Slog.e(TAG, "Remote failure in sendRouteDisconnected");
825            }
826        }
827
828        public void play() {
829            try {
830                mCb.onPlay();
831            } catch (RemoteException e) {
832                Slog.e(TAG, "Remote failure in play.", e);
833            }
834        }
835
836        public void pause() {
837            try {
838                mCb.onPause();
839            } catch (RemoteException e) {
840                Slog.e(TAG, "Remote failure in pause.", e);
841            }
842        }
843
844        public void stop() {
845            try {
846                mCb.onStop();
847            } catch (RemoteException e) {
848                Slog.e(TAG, "Remote failure in stop.", e);
849            }
850        }
851
852        public void next() {
853            try {
854                mCb.onNext();
855            } catch (RemoteException e) {
856                Slog.e(TAG, "Remote failure in next.", e);
857            }
858        }
859
860        public void previous() {
861            try {
862                mCb.onPrevious();
863            } catch (RemoteException e) {
864                Slog.e(TAG, "Remote failure in previous.", e);
865            }
866        }
867
868        public void fastForward() {
869            try {
870                mCb.onFastForward();
871            } catch (RemoteException e) {
872                Slog.e(TAG, "Remote failure in fastForward.", e);
873            }
874        }
875
876        public void rewind() {
877            try {
878                mCb.onRewind();
879            } catch (RemoteException e) {
880                Slog.e(TAG, "Remote failure in rewind.", e);
881            }
882        }
883
884        public void seekTo(long pos) {
885            try {
886                mCb.onSeekTo(pos);
887            } catch (RemoteException e) {
888                Slog.e(TAG, "Remote failure in seekTo.", e);
889            }
890        }
891
892        public void rate(Rating rating) {
893            try {
894                mCb.onRate(rating);
895            } catch (RemoteException e) {
896                Slog.e(TAG, "Remote failure in rate.", e);
897            }
898        }
899
900        public void adjustVolumeBy(int delta) {
901            try {
902                mCb.onAdjustVolumeBy(delta);
903            } catch (RemoteException e) {
904                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
905            }
906        }
907
908        public void setVolumeTo(int value) {
909            try {
910                mCb.onSetVolumeTo(value);
911            } catch (RemoteException e) {
912                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
913            }
914        }
915    }
916
917    class ControllerStub extends ISessionController.Stub {
918        @Override
919        public void sendCommand(String command, Bundle extras, ResultReceiver cb)
920                throws RemoteException {
921            mSessionCb.sendCommand(command, extras, cb);
922        }
923
924        @Override
925        public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
926            return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
927        }
928
929        @Override
930        public void registerCallbackListener(ISessionControllerCallback cb) {
931            synchronized (mLock) {
932                if (!mControllerCallbacks.contains(cb)) {
933                    mControllerCallbacks.add(cb);
934                }
935            }
936        }
937
938        @Override
939        public void unregisterCallbackListener(ISessionControllerCallback cb)
940                throws RemoteException {
941            synchronized (mLock) {
942                mControllerCallbacks.remove(cb);
943            }
944        }
945
946        @Override
947        public MediaSessionInfo getSessionInfo() {
948            return mSessionInfo;
949        }
950
951        @Override
952        public long getFlags() {
953            return mFlags;
954        }
955
956        @Override
957        public void play() throws RemoteException {
958            mSessionCb.play();
959        }
960
961        @Override
962        public void pause() throws RemoteException {
963            mSessionCb.pause();
964        }
965
966        @Override
967        public void stop() throws RemoteException {
968            mSessionCb.stop();
969        }
970
971        @Override
972        public void next() throws RemoteException {
973            mSessionCb.next();
974        }
975
976        @Override
977        public void previous() throws RemoteException {
978            mSessionCb.previous();
979        }
980
981        @Override
982        public void fastForward() throws RemoteException {
983            mSessionCb.fastForward();
984        }
985
986        @Override
987        public void rewind() throws RemoteException {
988            mSessionCb.rewind();
989        }
990
991        @Override
992        public void seekTo(long pos) throws RemoteException {
993            mSessionCb.seekTo(pos);
994        }
995
996        @Override
997        public void rate(Rating rating) throws RemoteException {
998            mSessionCb.rate(rating);
999        }
1000
1001
1002        @Override
1003        public MediaMetadata getMetadata() {
1004            return mMetadata;
1005        }
1006
1007        @Override
1008        public PlaybackState getPlaybackState() {
1009            return getStateWithUpdatedPosition();
1010        }
1011
1012        @Override
1013        public int getRatingType() {
1014            return mRatingType;
1015        }
1016
1017        @Override
1018        public boolean isTransportControlEnabled() {
1019            return MediaSessionRecord.this.isTransportControlEnabled();
1020        }
1021
1022        @Override
1023        public void showRoutePicker() {
1024            mService.showRoutePickerForSession(MediaSessionRecord.this);
1025        }
1026    }
1027
1028    private class MessageHandler extends Handler {
1029        private static final int MSG_UPDATE_METADATA = 1;
1030        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1031        private static final int MSG_UPDATE_ROUTE = 3;
1032        private static final int MSG_SEND_EVENT = 4;
1033        private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
1034        private static final int MSG_SEND_COMMAND = 6;
1035        private static final int MSG_UPDATE_SESSION_STATE = 7;
1036
1037        public MessageHandler(Looper looper) {
1038            super(looper);
1039        }
1040        @Override
1041        public void handleMessage(Message msg) {
1042            switch (msg.what) {
1043                case MSG_UPDATE_METADATA:
1044                    pushMetadataUpdate();
1045                    break;
1046                case MSG_UPDATE_PLAYBACK_STATE:
1047                    pushPlaybackStateUpdate();
1048                    break;
1049                case MSG_UPDATE_ROUTE:
1050                    pushRouteUpdate();
1051                    break;
1052                case MSG_SEND_EVENT:
1053                    pushEvent((String) msg.obj, msg.getData());
1054                    break;
1055                case MSG_SEND_COMMAND:
1056                    Pair<RouteCommand, ResultReceiver> cmd =
1057                            (Pair<RouteCommand, ResultReceiver>) msg.obj;
1058                    pushRouteCommand(cmd.first, cmd.second);
1059                    break;
1060                case MSG_UPDATE_SESSION_STATE:
1061                    // TODO add session state
1062                    break;
1063            }
1064        }
1065
1066        public void post(int what) {
1067            post(what, null);
1068        }
1069
1070        public void post(int what, Object obj) {
1071            obtainMessage(what, obj).sendToTarget();
1072        }
1073
1074        public void post(int what, Object obj, Bundle data) {
1075            Message msg = obtainMessage(what, obj);
1076            msg.setData(data);
1077            msg.sendToTarget();
1078        }
1079    }
1080
1081}
1082