MediaSessionRecord.java revision 9db9bf7034d7dcdf596dc22d521b18975d0dd2b9
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.routing.IMediaRouter;
23import android.media.routing.IMediaRouterDelegate;
24import android.media.routing.IMediaRouterStateCallback;
25import android.media.session.ISessionController;
26import android.media.session.ISessionControllerCallback;
27import android.media.session.ISession;
28import android.media.session.ISessionCallback;
29import android.media.session.MediaController;
30import android.media.session.MediaSession;
31import android.media.session.MediaSessionInfo;
32import android.media.session.PlaybackState;
33import android.media.session.ParcelableVolumeInfo;
34import android.media.AudioAttributes;
35import android.media.AudioManager;
36import android.media.MediaMetadata;
37import android.media.Rating;
38import android.media.VolumeProvider;
39import android.os.Binder;
40import android.os.Bundle;
41import android.os.DeadObjectException;
42import android.os.Handler;
43import android.os.IBinder;
44import android.os.Looper;
45import android.os.Message;
46import android.os.RemoteException;
47import android.os.ResultReceiver;
48import android.os.SystemClock;
49import android.text.TextUtils;
50import android.util.Log;
51import android.util.Pair;
52import android.util.Slog;
53import android.view.KeyEvent;
54
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.List;
58import java.util.UUID;
59
60/**
61 * This is the system implementation of a Session. Apps will interact with the
62 * MediaSession wrapper class instead.
63 */
64public class MediaSessionRecord implements IBinder.DeathRecipient {
65    private static final String TAG = "MediaSessionRecord";
66    private static final boolean DEBUG = false;
67
68    /**
69     * The length of time a session will still be considered active after
70     * pausing in ms.
71     */
72    private static final int ACTIVE_BUFFER = 30000;
73
74    /**
75     * The amount of time we'll send an assumed volume after the last volume
76     * command before reverting to the last reported volume.
77     */
78    private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
79
80    private final MessageHandler mHandler;
81
82    private final int mOwnerPid;
83    private final int mOwnerUid;
84    private final int mUserId;
85    private final MediaSessionInfo mSessionInfo;
86    private final String mTag;
87    private final ControllerStub mController;
88    private final SessionStub mSession;
89    private final SessionCb mSessionCb;
90    private final MediaSessionService mService;
91
92    private final Object mLock = new Object();
93    private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
94            new ArrayList<ISessionControllerCallback>();
95
96    private long mFlags;
97    private IMediaRouter mMediaRouter;
98    private ComponentName mMediaButtonReceiver;
99
100    // TransportPerformer fields
101
102    private MediaMetadata mMetadata;
103    private PlaybackState mPlaybackState;
104    private int mRatingType;
105    private long mLastActiveTime;
106    // End TransportPerformer fields
107
108    // Volume handling fields
109    private AudioAttributes mAudioAttrs;
110    private AudioManager mAudioManager;
111    private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL;
112    private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
113    private int mMaxVolume = 0;
114    private int mCurrentVolume = 0;
115    private int mOptimisticVolume = -1;
116    // End volume handling fields
117
118    private boolean mIsActive = false;
119    private boolean mDestroyed = false;
120
121    public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
122            ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
123        mOwnerPid = ownerPid;
124        mOwnerUid = ownerUid;
125        mUserId = userId;
126        mSessionInfo = new MediaSessionInfo(UUID.randomUUID().toString(), ownerPackageName,
127                ownerPid);
128        mTag = tag;
129        mController = new ControllerStub();
130        mSession = new SessionStub();
131        mSessionCb = new SessionCb(cb);
132        mService = service;
133        mHandler = new MessageHandler(handler.getLooper());
134        mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
135        mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
136    }
137
138    /**
139     * Get the binder for the {@link MediaSession}.
140     *
141     * @return The session binder apps talk to.
142     */
143    public ISession getSessionBinder() {
144        return mSession;
145    }
146
147    /**
148     * Get the binder for the {@link MediaController}.
149     *
150     * @return The controller binder apps talk to.
151     */
152    public ISessionController getControllerBinder() {
153        return mController;
154    }
155
156    /**
157     * Get the info for this session.
158     *
159     * @return Info that identifies this session.
160     */
161    public MediaSessionInfo getSessionInfo() {
162        return mSessionInfo;
163    }
164
165    public ComponentName getMediaButtonReceiver() {
166        return mMediaButtonReceiver;
167    }
168
169    /**
170     * Get this session's flags.
171     *
172     * @return The flags for this session.
173     */
174    public long getFlags() {
175        return mFlags;
176    }
177
178    /**
179     * Check if this session has the specified flag.
180     *
181     * @param flag The flag to check.
182     * @return True if this session has that flag set, false otherwise.
183     */
184    public boolean hasFlag(int flag) {
185        return (mFlags & flag) != 0;
186    }
187
188    /**
189     * Get the user id this session was created for.
190     *
191     * @return The user id for this session.
192     */
193    public int getUserId() {
194        return mUserId;
195    }
196
197    /**
198     * Check if this session has system priorty and should receive media buttons
199     * before any other sessions.
200     *
201     * @return True if this is a system priority session, false otherwise
202     */
203    public boolean isSystemPriority() {
204        return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
205    }
206
207    /**
208     * Send a volume adjustment to the session owner. Direction must be one of
209     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
210     * {@link AudioManager#ADJUST_SAME}.
211     *
212     * @param direction The direction to adjust volume in.
213     */
214    public void adjustVolume(int direction, int flags) {
215        if (isPlaybackActive(false)) {
216            flags &= ~AudioManager.FLAG_PLAY_SOUND;
217        }
218        if (direction > 1) {
219            direction = 1;
220        } else if (direction < -1) {
221            direction = -1;
222        }
223        if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
224            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
225            mAudioManager.adjustStreamVolume(stream, direction, flags);
226        } else {
227            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
228                // Nothing to do, the volume cannot be changed
229                return;
230            }
231            mSessionCb.adjustVolume(direction);
232
233            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
234            mOptimisticVolume = volumeBefore + direction;
235            mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
236            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
237            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
238            if (volumeBefore != mOptimisticVolume) {
239                pushVolumeUpdate();
240            }
241
242            if (DEBUG) {
243                Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
244                        + mMaxVolume);
245            }
246        }
247    }
248
249    public void setVolumeTo(int value, int flags) {
250        if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
251            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
252            mAudioManager.setStreamVolume(stream, value, flags);
253        } else {
254            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
255                // Nothing to do. The volume can't be set directly.
256                return;
257            }
258            value = Math.max(0, Math.min(value, mMaxVolume));
259            mSessionCb.setVolumeTo(value);
260
261            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
262            mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
263            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
264            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
265            if (volumeBefore != mOptimisticVolume) {
266                pushVolumeUpdate();
267            }
268
269            if (DEBUG) {
270                Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
271                        + mMaxVolume);
272            }
273        }
274    }
275
276    /**
277     * Check if this session has been set to active by the app.
278     *
279     * @return True if the session is active, false otherwise.
280     */
281    public boolean isActive() {
282        return mIsActive && !mDestroyed;
283    }
284
285    /**
286     * Check if the session is currently performing playback. This will also
287     * return true if the session was recently paused.
288     *
289     * @param includeRecentlyActive True if playback that was recently paused
290     *            should count, false if it shouldn't.
291     * @return True if the session is performing playback, false otherwise.
292     */
293    public boolean isPlaybackActive(boolean includeRecentlyActive) {
294        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
295        if (MediaSession.isActiveState(state)) {
296            return true;
297        }
298        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
299            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
300            if (inactiveTime < ACTIVE_BUFFER) {
301                return true;
302            }
303        }
304        return false;
305    }
306
307    /**
308     * Get the type of playback, either local or remote.
309     *
310     * @return The current type of playback.
311     */
312    public int getPlaybackType() {
313        return mVolumeType;
314    }
315
316    /**
317     * Get the local audio stream being used. Only valid if playback type is
318     * local.
319     *
320     * @return The audio stream the session is using.
321     */
322    public AudioAttributes getAudioAttributes() {
323        return mAudioAttrs;
324    }
325
326    /**
327     * Get the type of volume control. Only valid if playback type is remote.
328     *
329     * @return The volume control type being used.
330     */
331    public int getVolumeControl() {
332        return mVolumeControlType;
333    }
334
335    /**
336     * Get the max volume that can be set. Only valid if playback type is
337     * remote.
338     *
339     * @return The max volume that can be set.
340     */
341    public int getMaxVolume() {
342        return mMaxVolume;
343    }
344
345    /**
346     * Get the current volume for this session. Only valid if playback type is
347     * remote.
348     *
349     * @return The current volume of the remote playback.
350     */
351    public int getCurrentVolume() {
352        return mCurrentVolume;
353    }
354
355    /**
356     * Get the volume we'd like it to be set to. This is only valid for a short
357     * while after a call to adjust or set volume.
358     *
359     * @return The current optimistic volume or -1.
360     */
361    public int getOptimisticVolume() {
362        return mOptimisticVolume;
363    }
364
365    public boolean isTransportControlEnabled() {
366        return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
367    }
368
369    @Override
370    public void binderDied() {
371        mService.sessionDied(this);
372    }
373
374    /**
375     * Finish cleaning up this session, including disconnecting if connected and
376     * removing the death observer from the callback binder.
377     */
378    public void onDestroy() {
379        synchronized (mLock) {
380            if (mDestroyed) {
381                return;
382            }
383            mDestroyed = true;
384        }
385    }
386
387    public ISessionCallback getCallback() {
388        return mSessionCb.mCb;
389    }
390
391    public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
392        mSessionCb.sendMediaButton(ke, sequenceId, cb);
393    }
394
395    public void dump(PrintWriter pw, String prefix) {
396        pw.println(prefix + mTag + " " + this);
397
398        final String indent = prefix + "  ";
399        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
400                + ", userId=" + mUserId);
401        pw.println(indent + "info=" + mSessionInfo.toString());
402        pw.println(indent + "active=" + mIsActive);
403        pw.println(indent + "flags=" + mFlags);
404        pw.println(indent + "rating type=" + mRatingType);
405        pw.println(indent + "controllers: " + mControllerCallbacks.size());
406        pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
407        pw.println(indent + "metadata:" + getShortMetadataString());
408    }
409
410    private String getShortMetadataString() {
411        int fields = mMetadata == null ? 0 : mMetadata.size();
412        String title = mMetadata == null ? null : mMetadata
413                .getString(MediaMetadata.METADATA_KEY_TITLE);
414        return "size=" + fields + ", title=" + title;
415    }
416
417    private void pushPlaybackStateUpdate() {
418        synchronized (mLock) {
419            if (mDestroyed) {
420                return;
421            }
422            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
423                ISessionControllerCallback cb = mControllerCallbacks.get(i);
424                try {
425                    cb.onPlaybackStateChanged(mPlaybackState);
426                } catch (DeadObjectException e) {
427                    mControllerCallbacks.remove(i);
428                    Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
429                } catch (RemoteException e) {
430                    Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
431                }
432            }
433        }
434    }
435
436    private void pushMetadataUpdate() {
437        synchronized (mLock) {
438            if (mDestroyed) {
439                return;
440            }
441            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
442                ISessionControllerCallback cb = mControllerCallbacks.get(i);
443                try {
444                    cb.onMetadataChanged(mMetadata);
445                } catch (DeadObjectException e) {
446                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
447                    mControllerCallbacks.remove(i);
448                } catch (RemoteException e) {
449                    Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
450                }
451            }
452        }
453    }
454
455    private void pushVolumeUpdate() {
456        synchronized (mLock) {
457            if (mDestroyed) {
458                return;
459            }
460            ParcelableVolumeInfo info = mController.getVolumeAttributes();
461            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
462                ISessionControllerCallback cb = mControllerCallbacks.get(i);
463                try {
464                    cb.onVolumeInfoChanged(info);
465                } catch (DeadObjectException e) {
466                    Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
467                } catch (RemoteException e) {
468                    Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
469                }
470            }
471        }
472    }
473
474    private void pushEvent(String event, Bundle data) {
475        synchronized (mLock) {
476            if (mDestroyed) {
477                return;
478            }
479            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
480                ISessionControllerCallback cb = mControllerCallbacks.get(i);
481                try {
482                    cb.onEvent(event, data);
483                } catch (DeadObjectException e) {
484                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
485                    mControllerCallbacks.remove(i);
486                } catch (RemoteException e) {
487                    Log.w(TAG, "unexpected exception in pushEvent.", e);
488                }
489            }
490        }
491    }
492
493    private PlaybackState getStateWithUpdatedPosition() {
494        PlaybackState state = mPlaybackState;
495        long duration = -1;
496        if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
497            duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
498        }
499        PlaybackState result = null;
500        if (state != null) {
501            if (state.getState() == PlaybackState.STATE_PLAYING
502                    || state.getState() == PlaybackState.STATE_FAST_FORWARDING
503                    || state.getState() == PlaybackState.STATE_REWINDING) {
504                long updateTime = state.getLastPositionUpdateTime();
505                long currentTime = SystemClock.elapsedRealtime();
506                if (updateTime > 0) {
507                    long position = (long) (state.getPlaybackSpeed()
508                            * (currentTime - updateTime)) + state.getPosition();
509                    if (duration >= 0 && position > duration) {
510                        position = duration;
511                    } else if (position < 0) {
512                        position = 0;
513                    }
514                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
515                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
516                            currentTime);
517                    result = builder.build();
518                }
519            }
520        }
521        return result == null ? state : result;
522    }
523
524    private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
525        IBinder binder = cb.asBinder();
526        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
527            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
528                return i;
529            }
530        }
531        return -1;
532    }
533
534    private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
535        @Override
536        public void run() {
537            boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
538            mOptimisticVolume = -1;
539            if (needUpdate) {
540                pushVolumeUpdate();
541            }
542        }
543    };
544
545    private final class SessionStub extends ISession.Stub {
546        @Override
547        public void destroy() {
548            mService.destroySession(MediaSessionRecord.this);
549        }
550
551        @Override
552        public void sendEvent(String event, Bundle data) {
553            mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
554        }
555
556        @Override
557        public ISessionController getController() {
558            return mController;
559        }
560
561        @Override
562        public void setActive(boolean active) {
563            mIsActive = active;
564            mService.updateSession(MediaSessionRecord.this);
565            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
566        }
567
568        @Override
569        public void setFlags(int flags) {
570            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
571                int pid = getCallingPid();
572                int uid = getCallingUid();
573                mService.enforcePhoneStatePermission(pid, uid);
574            }
575            mFlags = flags;
576            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
577        }
578
579        @Override
580        public void setMediaRouter(IMediaRouter router) {
581            mMediaRouter = router;
582            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
583        }
584
585        @Override
586        public void setMediaButtonReceiver(ComponentName mbr) {
587            mMediaButtonReceiver = mbr;
588        }
589
590        @Override
591        public void setMetadata(MediaMetadata metadata) {
592            mMetadata = metadata;
593            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
594        }
595
596        @Override
597        public void setPlaybackState(PlaybackState state) {
598            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
599            int newState = state == null ? 0 : state.getState();
600            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
601                mLastActiveTime = SystemClock.elapsedRealtime();
602            }
603            mPlaybackState = state;
604            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
605            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
606        }
607
608        @Override
609        public void setRatingType(int type) {
610            mRatingType = type;
611        }
612
613        @Override
614        public void setCurrentVolume(int volume) {
615            mCurrentVolume = volume;
616            mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
617        }
618
619        @Override
620        public void setPlaybackToLocal(AudioAttributes attributes) {
621            boolean typeChanged;
622            synchronized (mLock) {
623                typeChanged = mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE;
624                mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL;
625                if (attributes != null) {
626                    mAudioAttrs = attributes;
627                } else {
628                    Log.e(TAG, "Received null audio attributes, using existing attributes");
629                }
630            }
631            if (typeChanged) {
632                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
633            }
634        }
635
636        @Override
637        public void setPlaybackToRemote(int control, int max) {
638            boolean typeChanged;
639            synchronized (mLock) {
640                typeChanged = mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL;
641                mVolumeType = MediaSession.PLAYBACK_TYPE_REMOTE;
642                mVolumeControlType = control;
643                mMaxVolume = max;
644            }
645            if (typeChanged) {
646                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
647            }
648        }
649    }
650
651    class SessionCb {
652        private final ISessionCallback mCb;
653
654        public SessionCb(ISessionCallback cb) {
655            mCb = cb;
656        }
657
658        public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
659            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
660            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
661            try {
662                mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
663                return true;
664            } catch (RemoteException e) {
665                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
666            }
667            return false;
668        }
669
670        public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
671            try {
672                mCb.onCommand(command, extras, cb);
673            } catch (RemoteException e) {
674                Slog.e(TAG, "Remote failure in sendCommand.", e);
675            }
676        }
677
678        public void play() {
679            try {
680                mCb.onPlay();
681            } catch (RemoteException e) {
682                Slog.e(TAG, "Remote failure in play.", e);
683            }
684        }
685
686        public void pause() {
687            try {
688                mCb.onPause();
689            } catch (RemoteException e) {
690                Slog.e(TAG, "Remote failure in pause.", e);
691            }
692        }
693
694        public void stop() {
695            try {
696                mCb.onStop();
697            } catch (RemoteException e) {
698                Slog.e(TAG, "Remote failure in stop.", e);
699            }
700        }
701
702        public void next() {
703            try {
704                mCb.onNext();
705            } catch (RemoteException e) {
706                Slog.e(TAG, "Remote failure in next.", e);
707            }
708        }
709
710        public void previous() {
711            try {
712                mCb.onPrevious();
713            } catch (RemoteException e) {
714                Slog.e(TAG, "Remote failure in previous.", e);
715            }
716        }
717
718        public void fastForward() {
719            try {
720                mCb.onFastForward();
721            } catch (RemoteException e) {
722                Slog.e(TAG, "Remote failure in fastForward.", e);
723            }
724        }
725
726        public void rewind() {
727            try {
728                mCb.onRewind();
729            } catch (RemoteException e) {
730                Slog.e(TAG, "Remote failure in rewind.", e);
731            }
732        }
733
734        public void seekTo(long pos) {
735            try {
736                mCb.onSeekTo(pos);
737            } catch (RemoteException e) {
738                Slog.e(TAG, "Remote failure in seekTo.", e);
739            }
740        }
741
742        public void rate(Rating rating) {
743            try {
744                mCb.onRate(rating);
745            } catch (RemoteException e) {
746                Slog.e(TAG, "Remote failure in rate.", e);
747            }
748        }
749
750        public void adjustVolume(int direction) {
751            try {
752                mCb.onAdjustVolume(direction);
753            } catch (RemoteException e) {
754                Slog.e(TAG, "Remote failure in adjustVolume.", e);
755            }
756        }
757
758        public void setVolumeTo(int value) {
759            try {
760                mCb.onSetVolumeTo(value);
761            } catch (RemoteException e) {
762                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
763            }
764        }
765    }
766
767    class ControllerStub extends ISessionController.Stub {
768        @Override
769        public void sendCommand(String command, Bundle extras, ResultReceiver cb)
770                throws RemoteException {
771            mSessionCb.sendCommand(command, extras, cb);
772        }
773
774        @Override
775        public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
776            return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
777        }
778
779        @Override
780        public void registerCallbackListener(ISessionControllerCallback cb) {
781            synchronized (mLock) {
782                if (getControllerCbIndexForCb(cb) < 0) {
783                    mControllerCallbacks.add(cb);
784                    if (DEBUG) {
785                        Log.d(TAG, "registering controller callback " + cb);
786                    }
787                }
788            }
789        }
790
791        @Override
792        public void unregisterCallbackListener(ISessionControllerCallback cb)
793                throws RemoteException {
794            synchronized (mLock) {
795                int index = getControllerCbIndexForCb(cb);
796                if (index != -1) {
797                    mControllerCallbacks.remove(index);
798                }
799                if (DEBUG) {
800                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
801                }
802            }
803        }
804
805        @Override
806        public MediaSessionInfo getSessionInfo() {
807            return mSessionInfo;
808        }
809
810        @Override
811        public long getFlags() {
812            return mFlags;
813        }
814
815        @Override
816        public ParcelableVolumeInfo getVolumeAttributes() {
817            synchronized (mLock) {
818                int type;
819                int max;
820                int current;
821                if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) {
822                    type = mVolumeControlType;
823                    max = mMaxVolume;
824                    current = mOptimisticVolume != -1 ? mOptimisticVolume
825                            : mCurrentVolume;
826                } else {
827                    int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
828                    type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
829                    max = mAudioManager.getStreamMaxVolume(stream);
830                    current = mAudioManager.getStreamVolume(stream);
831                }
832                return new ParcelableVolumeInfo(mVolumeType, mAudioAttrs, type, max, current);
833            }
834        }
835
836        @Override
837        public void adjustVolume(int direction, int flags) {
838            final long token = Binder.clearCallingIdentity();
839            try {
840                MediaSessionRecord.this.adjustVolume(direction, flags);
841            } finally {
842                Binder.restoreCallingIdentity(token);
843            }
844        }
845
846        @Override
847        public void setVolumeTo(int value, int flags) {
848            final long token = Binder.clearCallingIdentity();
849            try {
850                MediaSessionRecord.this.setVolumeTo(value, flags);
851            } finally {
852                Binder.restoreCallingIdentity(token);
853            }
854        }
855
856        @Override
857        public void play() throws RemoteException {
858            mSessionCb.play();
859        }
860
861        @Override
862        public void pause() throws RemoteException {
863            mSessionCb.pause();
864        }
865
866        @Override
867        public void stop() throws RemoteException {
868            mSessionCb.stop();
869        }
870
871        @Override
872        public void next() throws RemoteException {
873            mSessionCb.next();
874        }
875
876        @Override
877        public void previous() throws RemoteException {
878            mSessionCb.previous();
879        }
880
881        @Override
882        public void fastForward() throws RemoteException {
883            mSessionCb.fastForward();
884        }
885
886        @Override
887        public void rewind() throws RemoteException {
888            mSessionCb.rewind();
889        }
890
891        @Override
892        public void seekTo(long pos) throws RemoteException {
893            mSessionCb.seekTo(pos);
894        }
895
896        @Override
897        public void rate(Rating rating) throws RemoteException {
898            mSessionCb.rate(rating);
899        }
900
901
902        @Override
903        public MediaMetadata getMetadata() {
904            return mMetadata;
905        }
906
907        @Override
908        public PlaybackState getPlaybackState() {
909            return getStateWithUpdatedPosition();
910        }
911
912        @Override
913        public int getRatingType() {
914            return mRatingType;
915        }
916
917        @Override
918        public boolean isTransportControlEnabled() {
919            return MediaSessionRecord.this.isTransportControlEnabled();
920        }
921
922        @Override
923        public IMediaRouterDelegate createMediaRouterDelegate(
924                IMediaRouterStateCallback callback) {
925            // todo
926            return null;
927        }
928    }
929
930    private class MessageHandler extends Handler {
931        private static final int MSG_UPDATE_METADATA = 1;
932        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
933        private static final int MSG_SEND_EVENT = 3;
934        private static final int MSG_UPDATE_SESSION_STATE = 4;
935        private static final int MSG_UPDATE_VOLUME = 5;
936
937        public MessageHandler(Looper looper) {
938            super(looper);
939        }
940        @Override
941        public void handleMessage(Message msg) {
942            switch (msg.what) {
943                case MSG_UPDATE_METADATA:
944                    pushMetadataUpdate();
945                    break;
946                case MSG_UPDATE_PLAYBACK_STATE:
947                    pushPlaybackStateUpdate();
948                    break;
949                case MSG_SEND_EVENT:
950                    pushEvent((String) msg.obj, msg.getData());
951                    break;
952                case MSG_UPDATE_SESSION_STATE:
953                    // TODO add session state
954                    break;
955                case MSG_UPDATE_VOLUME:
956                    pushVolumeUpdate();
957                    break;
958            }
959        }
960
961        public void post(int what) {
962            post(what, null);
963        }
964
965        public void post(int what, Object obj) {
966            obtainMessage(what, obj).sendToTarget();
967        }
968
969        public void post(int what, Object obj, Bundle data) {
970            Message msg = obtainMessage(what, obj);
971            msg.setData(data);
972            msg.sendToTarget();
973        }
974    }
975
976}
977