MediaSessionRecord.java revision c785a78fb483fe54012175c53d3758b2412de7b9
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                long currentTime = SystemClock.elapsedRealtime();
509                if (updateTime > 0) {
510                    long position = (long) (state.getPlaybackSpeed()
511                            * (currentTime - updateTime)) + state.getPosition();
512                    if (duration >= 0 && position > duration) {
513                        position = duration;
514                    } else if (position < 0) {
515                        position = 0;
516                    }
517                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
518                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
519                            currentTime);
520                    result = builder.build();
521                }
522            }
523        }
524        return result == null ? state : result;
525    }
526
527    private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
528        IBinder binder = cb.asBinder();
529        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
530            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
531                return i;
532            }
533        }
534        return -1;
535    }
536
537    private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
538        @Override
539        public void run() {
540            boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
541            mOptimisticVolume = -1;
542            if (needUpdate) {
543                pushVolumeUpdate();
544            }
545        }
546    };
547
548    private final class SessionStub extends ISession.Stub {
549        @Override
550        public void destroy() {
551            mService.destroySession(MediaSessionRecord.this);
552        }
553
554        @Override
555        public void sendEvent(String event, Bundle data) {
556            mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
557        }
558
559        @Override
560        public ISessionController getController() {
561            return mController;
562        }
563
564        @Override
565        public void setActive(boolean active) {
566            mIsActive = active;
567            mService.updateSession(MediaSessionRecord.this);
568            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
569        }
570
571        @Override
572        public void setFlags(int flags) {
573            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
574                int pid = getCallingPid();
575                int uid = getCallingUid();
576                mService.enforcePhoneStatePermission(pid, uid);
577            }
578            mFlags = flags;
579            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
580        }
581
582        @Override
583        public void setMediaRouter(IMediaRouter router) {
584            mMediaRouter = router;
585            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
586        }
587
588        @Override
589        public void setMediaButtonReceiver(ComponentName mbr) {
590            mMediaButtonReceiver = mbr;
591        }
592
593        @Override
594        public void setMetadata(MediaMetadata metadata) {
595            mMetadata = metadata;
596            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
597        }
598
599        @Override
600        public void setPlaybackState(PlaybackState state) {
601            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
602            int newState = state == null ? 0 : state.getState();
603            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
604                mLastActiveTime = SystemClock.elapsedRealtime();
605            }
606            mPlaybackState = state;
607            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
608            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
609        }
610
611        @Override
612        public void setRatingType(int type) {
613            mRatingType = type;
614        }
615
616        @Override
617        public void setCurrentVolume(int volume) {
618            mCurrentVolume = volume;
619            mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
620        }
621
622        @Override
623        public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
624            boolean typeChanged = type != mVolumeType;
625            switch(type) {
626                case MediaSession.PLAYBACK_TYPE_LOCAL:
627                    mVolumeType = type;
628                    int audioStream = arg1;
629                    if (isValidStream(audioStream)) {
630                        mAudioStream = audioStream;
631                    } else {
632                        Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream");
633                        mAudioStream = AudioManager.STREAM_MUSIC;
634                    }
635                    break;
636                case MediaSession.PLAYBACK_TYPE_REMOTE:
637                    mVolumeType = type;
638                    mVolumeControlType = arg1;
639                    mMaxVolume = arg2;
640                    break;
641                default:
642                    throw new IllegalArgumentException("Volume handling type " + type
643                            + " not recognized.");
644            }
645            if (typeChanged) {
646                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
647            }
648        }
649
650        private boolean isValidStream(int stream) {
651            return stream >= AudioManager.STREAM_VOICE_CALL
652                    && stream <= AudioManager.STREAM_NOTIFICATION;
653        }
654    }
655
656    class SessionCb {
657        private final ISessionCallback mCb;
658
659        public SessionCb(ISessionCallback cb) {
660            mCb = cb;
661        }
662
663        public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
664            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
665            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
666            try {
667                mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
668                return true;
669            } catch (RemoteException e) {
670                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
671            }
672            return false;
673        }
674
675        public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
676            try {
677                mCb.onCommand(command, extras, cb);
678            } catch (RemoteException e) {
679                Slog.e(TAG, "Remote failure in sendCommand.", e);
680            }
681        }
682
683        public void play() {
684            try {
685                mCb.onPlay();
686            } catch (RemoteException e) {
687                Slog.e(TAG, "Remote failure in play.", e);
688            }
689        }
690
691        public void pause() {
692            try {
693                mCb.onPause();
694            } catch (RemoteException e) {
695                Slog.e(TAG, "Remote failure in pause.", e);
696            }
697        }
698
699        public void stop() {
700            try {
701                mCb.onStop();
702            } catch (RemoteException e) {
703                Slog.e(TAG, "Remote failure in stop.", e);
704            }
705        }
706
707        public void next() {
708            try {
709                mCb.onNext();
710            } catch (RemoteException e) {
711                Slog.e(TAG, "Remote failure in next.", e);
712            }
713        }
714
715        public void previous() {
716            try {
717                mCb.onPrevious();
718            } catch (RemoteException e) {
719                Slog.e(TAG, "Remote failure in previous.", e);
720            }
721        }
722
723        public void fastForward() {
724            try {
725                mCb.onFastForward();
726            } catch (RemoteException e) {
727                Slog.e(TAG, "Remote failure in fastForward.", e);
728            }
729        }
730
731        public void rewind() {
732            try {
733                mCb.onRewind();
734            } catch (RemoteException e) {
735                Slog.e(TAG, "Remote failure in rewind.", e);
736            }
737        }
738
739        public void seekTo(long pos) {
740            try {
741                mCb.onSeekTo(pos);
742            } catch (RemoteException e) {
743                Slog.e(TAG, "Remote failure in seekTo.", e);
744            }
745        }
746
747        public void rate(Rating rating) {
748            try {
749                mCb.onRate(rating);
750            } catch (RemoteException e) {
751                Slog.e(TAG, "Remote failure in rate.", e);
752            }
753        }
754
755        public void adjustVolumeBy(int delta) {
756            try {
757                mCb.onAdjustVolumeBy(delta);
758            } catch (RemoteException e) {
759                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
760            }
761        }
762
763        public void setVolumeTo(int value) {
764            try {
765                mCb.onSetVolumeTo(value);
766            } catch (RemoteException e) {
767                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
768            }
769        }
770    }
771
772    class ControllerStub extends ISessionController.Stub {
773        @Override
774        public void sendCommand(String command, Bundle extras, ResultReceiver cb)
775                throws RemoteException {
776            mSessionCb.sendCommand(command, extras, cb);
777        }
778
779        @Override
780        public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
781            return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
782        }
783
784        @Override
785        public void registerCallbackListener(ISessionControllerCallback cb) {
786            synchronized (mLock) {
787                if (getControllerCbIndexForCb(cb) < 0) {
788                    mControllerCallbacks.add(cb);
789                    if (DEBUG) {
790                        Log.d(TAG, "registering controller callback " + cb);
791                    }
792                }
793            }
794        }
795
796        @Override
797        public void unregisterCallbackListener(ISessionControllerCallback cb)
798                throws RemoteException {
799            synchronized (mLock) {
800                int index = getControllerCbIndexForCb(cb);
801                if (index != -1) {
802                    mControllerCallbacks.remove(index);
803                }
804                if (DEBUG) {
805                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
806                }
807            }
808        }
809
810        @Override
811        public MediaSessionInfo getSessionInfo() {
812            return mSessionInfo;
813        }
814
815        @Override
816        public long getFlags() {
817            return mFlags;
818        }
819
820        @Override
821        public ParcelableVolumeInfo getVolumeAttributes() {
822            synchronized (mLock) {
823                int type;
824                int max;
825                int current;
826                if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) {
827                    type = mVolumeControlType;
828                    max = mMaxVolume;
829                    current = mOptimisticVolume != -1 ? mOptimisticVolume
830                            : mCurrentVolume;
831                } else {
832                    type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
833                    max = mAudioManager.getStreamMaxVolume(mAudioStream);
834                    current = mAudioManager.getStreamVolume(mAudioStream);
835                }
836                return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current);
837            }
838        }
839
840        @Override
841        public void adjustVolumeBy(int delta, int flags) {
842            final long token = Binder.clearCallingIdentity();
843            try {
844                MediaSessionRecord.this.adjustVolumeBy(delta, flags);
845            } finally {
846                Binder.restoreCallingIdentity(token);
847            }
848        }
849
850        @Override
851        public void setVolumeTo(int value, int flags) {
852            final long token = Binder.clearCallingIdentity();
853            try {
854                MediaSessionRecord.this.setVolumeTo(value, flags);
855            } finally {
856                Binder.restoreCallingIdentity(token);
857            }
858        }
859
860        @Override
861        public void play() throws RemoteException {
862            mSessionCb.play();
863        }
864
865        @Override
866        public void pause() throws RemoteException {
867            mSessionCb.pause();
868        }
869
870        @Override
871        public void stop() throws RemoteException {
872            mSessionCb.stop();
873        }
874
875        @Override
876        public void next() throws RemoteException {
877            mSessionCb.next();
878        }
879
880        @Override
881        public void previous() throws RemoteException {
882            mSessionCb.previous();
883        }
884
885        @Override
886        public void fastForward() throws RemoteException {
887            mSessionCb.fastForward();
888        }
889
890        @Override
891        public void rewind() throws RemoteException {
892            mSessionCb.rewind();
893        }
894
895        @Override
896        public void seekTo(long pos) throws RemoteException {
897            mSessionCb.seekTo(pos);
898        }
899
900        @Override
901        public void rate(Rating rating) throws RemoteException {
902            mSessionCb.rate(rating);
903        }
904
905
906        @Override
907        public MediaMetadata getMetadata() {
908            return mMetadata;
909        }
910
911        @Override
912        public PlaybackState getPlaybackState() {
913            return getStateWithUpdatedPosition();
914        }
915
916        @Override
917        public int getRatingType() {
918            return mRatingType;
919        }
920
921        @Override
922        public boolean isTransportControlEnabled() {
923            return MediaSessionRecord.this.isTransportControlEnabled();
924        }
925
926        @Override
927        public IMediaRouterDelegate createMediaRouterDelegate(
928                IMediaRouterStateCallback callback) {
929            // todo
930            return null;
931        }
932    }
933
934    private class MessageHandler extends Handler {
935        private static final int MSG_UPDATE_METADATA = 1;
936        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
937        private static final int MSG_SEND_EVENT = 3;
938        private static final int MSG_UPDATE_SESSION_STATE = 4;
939        private static final int MSG_UPDATE_VOLUME = 5;
940
941        public MessageHandler(Looper looper) {
942            super(looper);
943        }
944        @Override
945        public void handleMessage(Message msg) {
946            switch (msg.what) {
947                case MSG_UPDATE_METADATA:
948                    pushMetadataUpdate();
949                    break;
950                case MSG_UPDATE_PLAYBACK_STATE:
951                    pushPlaybackStateUpdate();
952                    break;
953                case MSG_SEND_EVENT:
954                    pushEvent((String) msg.obj, msg.getData());
955                    break;
956                case MSG_UPDATE_SESSION_STATE:
957                    // TODO add session state
958                    break;
959                case MSG_UPDATE_VOLUME:
960                    pushVolumeUpdate();
961                    break;
962            }
963        }
964
965        public void post(int what) {
966            post(what, null);
967        }
968
969        public void post(int what, Object obj) {
970            obtainMessage(what, obj).sendToTarget();
971        }
972
973        public void post(int what, Object obj, Bundle data) {
974            Message msg = obtainMessage(what, obj);
975            msg.setData(data);
976            msg.sendToTarget();
977        }
978    }
979
980}
981