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