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