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