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