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