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