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