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