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