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