MediaSessionRecord.java revision 2610d71251e3e376e2514f20986c81e5d55f1b55
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
334            if (DEBUG) {
335                Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
336                        + mMaxVolume);
337            }
338        }
339    }
340
341    public void setVolumeTo(int value, int flags, String packageName, int uid) {
342        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
343            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
344            mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid);
345        } else {
346            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
347                // Nothing to do. The volume can't be set directly.
348                return;
349            }
350            value = Math.max(0, Math.min(value, mMaxVolume));
351            mSessionCb.setVolumeTo(value);
352
353            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
354            mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
355            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
356            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
357            if (volumeBefore != mOptimisticVolume) {
358                pushVolumeUpdate();
359            }
360
361            if (DEBUG) {
362                Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
363                        + mMaxVolume);
364            }
365        }
366    }
367
368    /**
369     * Check if this session has been set to active by the app.
370     *
371     * @return True if the session is active, false otherwise.
372     */
373    public boolean isActive() {
374        return mIsActive && !mDestroyed;
375    }
376
377    /**
378     * Check if the session is currently performing playback. This will also
379     * return true if the session was recently paused.
380     *
381     * @param includeRecentlyActive True if playback that was recently paused
382     *            should count, false if it shouldn't.
383     * @return True if the session is performing playback, false otherwise.
384     */
385    public boolean isPlaybackActive(boolean includeRecentlyActive) {
386        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
387        if (MediaSession.isActiveState(state)) {
388            return true;
389        }
390        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
391            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
392            if (inactiveTime < ACTIVE_BUFFER) {
393                return true;
394            }
395        }
396        return false;
397    }
398
399    /**
400     * Get the type of playback, either local or remote.
401     *
402     * @return The current type of playback.
403     */
404    public int getPlaybackType() {
405        return mVolumeType;
406    }
407
408    /**
409     * Get the local audio stream being used. Only valid if playback type is
410     * local.
411     *
412     * @return The audio stream the session is using.
413     */
414    public AudioAttributes getAudioAttributes() {
415        return mAudioAttrs;
416    }
417
418    /**
419     * Get the type of volume control. Only valid if playback type is remote.
420     *
421     * @return The volume control type being used.
422     */
423    public int getVolumeControl() {
424        return mVolumeControlType;
425    }
426
427    /**
428     * Get the max volume that can be set. Only valid if playback type is
429     * remote.
430     *
431     * @return The max volume that can be set.
432     */
433    public int getMaxVolume() {
434        return mMaxVolume;
435    }
436
437    /**
438     * Get the current volume for this session. Only valid if playback type is
439     * remote.
440     *
441     * @return The current volume of the remote playback.
442     */
443    public int getCurrentVolume() {
444        return mCurrentVolume;
445    }
446
447    /**
448     * Get the volume we'd like it to be set to. This is only valid for a short
449     * while after a call to adjust or set volume.
450     *
451     * @return The current optimistic volume or -1.
452     */
453    public int getOptimisticVolume() {
454        return mOptimisticVolume;
455    }
456
457    public boolean isTransportControlEnabled() {
458        return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
459    }
460
461    @Override
462    public void binderDied() {
463        mService.sessionDied(this);
464    }
465
466    /**
467     * Finish cleaning up this session, including disconnecting if connected and
468     * removing the death observer from the callback binder.
469     */
470    public void onDestroy() {
471        synchronized (mLock) {
472            if (mDestroyed) {
473                return;
474            }
475            mDestroyed = true;
476            mHandler.post(MessageHandler.MSG_DESTROYED);
477        }
478    }
479
480    public ISessionCallback getCallback() {
481        return mSessionCb.mCb;
482    }
483
484    public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
485        mSessionCb.sendMediaButton(ke, sequenceId, cb);
486    }
487
488    public void dump(PrintWriter pw, String prefix) {
489        pw.println(prefix + mTag + " " + this);
490
491        final String indent = prefix + "  ";
492        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
493                + ", userId=" + mUserId);
494        pw.println(indent + "package=" + mPackageName);
495        pw.println(indent + "launchIntent=" + mLaunchIntent);
496        pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
497        pw.println(indent + "active=" + mIsActive);
498        pw.println(indent + "flags=" + mFlags);
499        pw.println(indent + "rating type=" + mRatingType);
500        pw.println(indent + "controllers: " + mControllerCallbacks.size());
501        pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
502        pw.println(indent + "audioAttrs=" + mAudioAttrs);
503        pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
504                + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
505        pw.println(indent + "metadata:" + getShortMetadataString());
506        pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
507                + (mQueue == null ? 0 : mQueue.getList().size()));
508    }
509
510    @Override
511    public String toString() {
512        return mPackageName + "/" + mTag;
513    }
514
515    private String getShortMetadataString() {
516        int fields = mMetadata == null ? 0 : mMetadata.size();
517        MediaDescription description = mMetadata == null ? null : mMetadata
518                .getDescription();
519        return "size=" + fields + ", description=" + description;
520    }
521
522    private void pushPlaybackStateUpdate() {
523        synchronized (mLock) {
524            if (mDestroyed) {
525                return;
526            }
527            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
528                ISessionControllerCallback cb = mControllerCallbacks.get(i);
529                try {
530                    cb.onPlaybackStateChanged(mPlaybackState);
531                } catch (DeadObjectException e) {
532                    mControllerCallbacks.remove(i);
533                    Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
534                } catch (RemoteException e) {
535                    Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
536                }
537            }
538        }
539    }
540
541    private void pushMetadataUpdate() {
542        synchronized (mLock) {
543            if (mDestroyed) {
544                return;
545            }
546            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
547                ISessionControllerCallback cb = mControllerCallbacks.get(i);
548                try {
549                    cb.onMetadataChanged(mMetadata);
550                } catch (DeadObjectException e) {
551                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
552                    mControllerCallbacks.remove(i);
553                } catch (RemoteException e) {
554                    Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
555                }
556            }
557        }
558    }
559
560    private void pushQueueUpdate() {
561        synchronized (mLock) {
562            if (mDestroyed) {
563                return;
564            }
565            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
566                ISessionControllerCallback cb = mControllerCallbacks.get(i);
567                try {
568                    cb.onQueueChanged(mQueue);
569                } catch (DeadObjectException e) {
570                    mControllerCallbacks.remove(i);
571                    Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e);
572                } catch (RemoteException e) {
573                    Log.w(TAG, "unexpected exception in pushQueueUpdate.", e);
574                }
575            }
576        }
577    }
578
579    private void pushQueueTitleUpdate() {
580        synchronized (mLock) {
581            if (mDestroyed) {
582                return;
583            }
584            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
585                ISessionControllerCallback cb = mControllerCallbacks.get(i);
586                try {
587                    cb.onQueueTitleChanged(mQueueTitle);
588                } catch (DeadObjectException e) {
589                    mControllerCallbacks.remove(i);
590                    Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e);
591                } catch (RemoteException e) {
592                    Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e);
593                }
594            }
595        }
596    }
597
598    private void pushExtrasUpdate() {
599        synchronized (mLock) {
600            if (mDestroyed) {
601                return;
602            }
603            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
604                ISessionControllerCallback cb = mControllerCallbacks.get(i);
605                try {
606                    cb.onExtrasChanged(mExtras);
607                } catch (DeadObjectException e) {
608                    mControllerCallbacks.remove(i);
609                    Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e);
610                } catch (RemoteException e) {
611                    Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e);
612                }
613            }
614        }
615    }
616
617    private void pushVolumeUpdate() {
618        synchronized (mLock) {
619            if (mDestroyed) {
620                return;
621            }
622            ParcelableVolumeInfo info = mController.getVolumeAttributes();
623            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
624                ISessionControllerCallback cb = mControllerCallbacks.get(i);
625                try {
626                    cb.onVolumeInfoChanged(info);
627                } catch (DeadObjectException e) {
628                    Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
629                } catch (RemoteException e) {
630                    Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
631                }
632            }
633        }
634    }
635
636    private void pushEvent(String event, Bundle data) {
637        synchronized (mLock) {
638            if (mDestroyed) {
639                return;
640            }
641            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
642                ISessionControllerCallback cb = mControllerCallbacks.get(i);
643                try {
644                    cb.onEvent(event, data);
645                } catch (DeadObjectException e) {
646                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
647                    mControllerCallbacks.remove(i);
648                } catch (RemoteException e) {
649                    Log.w(TAG, "unexpected exception in pushEvent.", e);
650                }
651            }
652        }
653    }
654
655    private void pushSessionDestroyed() {
656        synchronized (mLock) {
657            // This is the only method that may be (and can only be) called
658            // after the session is destroyed.
659            if (!mDestroyed) {
660                return;
661            }
662            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
663                ISessionControllerCallback cb = mControllerCallbacks.get(i);
664                try {
665                    cb.onSessionDestroyed();
666                } catch (DeadObjectException e) {
667                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
668                    mControllerCallbacks.remove(i);
669                } catch (RemoteException e) {
670                    Log.w(TAG, "unexpected exception in pushEvent.", e);
671                }
672            }
673            // After notifying clear all listeners
674            mControllerCallbacks.clear();
675        }
676    }
677
678    private PlaybackState getStateWithUpdatedPosition() {
679        PlaybackState state;
680        long duration = -1;
681        synchronized (mLock) {
682            state = mPlaybackState;
683            if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
684                duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
685            }
686        }
687        PlaybackState result = null;
688        if (state != null) {
689            if (state.getState() == PlaybackState.STATE_PLAYING
690                    || state.getState() == PlaybackState.STATE_FAST_FORWARDING
691                    || state.getState() == PlaybackState.STATE_REWINDING) {
692                long updateTime = state.getLastPositionUpdateTime();
693                long currentTime = SystemClock.elapsedRealtime();
694                if (updateTime > 0) {
695                    long position = (long) (state.getPlaybackSpeed()
696                            * (currentTime - updateTime)) + state.getPosition();
697                    if (duration >= 0 && position > duration) {
698                        position = duration;
699                    } else if (position < 0) {
700                        position = 0;
701                    }
702                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
703                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
704                            currentTime);
705                    result = builder.build();
706                }
707            }
708        }
709        return result == null ? state : result;
710    }
711
712    private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
713        IBinder binder = cb.asBinder();
714        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
715            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
716                return i;
717            }
718        }
719        return -1;
720    }
721
722    private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
723        @Override
724        public void run() {
725            boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
726            mOptimisticVolume = -1;
727            if (needUpdate) {
728                pushVolumeUpdate();
729            }
730        }
731    };
732
733    private final class SessionStub extends ISession.Stub {
734        @Override
735        public void destroy() {
736            mService.destroySession(MediaSessionRecord.this);
737        }
738
739        @Override
740        public void sendEvent(String event, Bundle data) {
741            mHandler.post(MessageHandler.MSG_SEND_EVENT, event,
742                    data == null ? null : new Bundle(data));
743        }
744
745        @Override
746        public ISessionController getController() {
747            return mController;
748        }
749
750        @Override
751        public void setActive(boolean active) {
752            mIsActive = active;
753            mService.updateSession(MediaSessionRecord.this);
754            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
755        }
756
757        @Override
758        public void setFlags(int flags) {
759            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
760                int pid = getCallingPid();
761                int uid = getCallingUid();
762                mService.enforcePhoneStatePermission(pid, uid);
763            }
764            mFlags = flags;
765            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
766        }
767
768        @Override
769        public void setMediaButtonReceiver(PendingIntent pi) {
770            mMediaButtonReceiver = pi;
771        }
772
773        @Override
774        public void setLaunchPendingIntent(PendingIntent pi) {
775            mLaunchIntent = pi;
776        }
777
778        @Override
779        public void setMetadata(MediaMetadata metadata) {
780            synchronized (mLock) {
781                MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
782                        .build();
783                // This is to guarantee that the underlying bundle is unparceled
784                // before we set it to prevent concurrent reads from throwing an
785                // exception
786                if (temp != null) {
787                    temp.size();
788                }
789                mMetadata = temp;
790            }
791            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
792        }
793
794        @Override
795        public void setPlaybackState(PlaybackState state) {
796            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
797            int newState = state == null ? 0 : state.getState();
798            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
799                mLastActiveTime = SystemClock.elapsedRealtime();
800            }
801            synchronized (mLock) {
802                mPlaybackState = state;
803            }
804            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
805            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
806        }
807
808        @Override
809        public void setQueue(ParceledListSlice queue) {
810            synchronized (mLock) {
811                mQueue = queue;
812            }
813            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
814        }
815
816        @Override
817        public void setQueueTitle(CharSequence title) {
818            mQueueTitle = title;
819            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
820        }
821
822        @Override
823        public void setExtras(Bundle extras) {
824            synchronized (mLock) {
825                mExtras = extras == null ? null : new Bundle(extras);
826            }
827            mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS);
828        }
829
830        @Override
831        public void setRatingType(int type) {
832            mRatingType = type;
833        }
834
835        @Override
836        public void setCurrentVolume(int volume) {
837            mCurrentVolume = volume;
838            mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
839        }
840
841        @Override
842        public void setPlaybackToLocal(AudioAttributes attributes) {
843            boolean typeChanged;
844            synchronized (mLock) {
845                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
846                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
847                if (attributes != null) {
848                    mAudioAttrs = attributes;
849                } else {
850                    Log.e(TAG, "Received null audio attributes, using existing attributes");
851                }
852            }
853            if (typeChanged) {
854                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
855            }
856        }
857
858        @Override
859        public void setPlaybackToRemote(int control, int max) {
860            boolean typeChanged;
861            synchronized (mLock) {
862                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
863                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
864                mVolumeControlType = control;
865                mMaxVolume = max;
866            }
867            if (typeChanged) {
868                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
869            }
870        }
871    }
872
873    class SessionCb {
874        private final ISessionCallback mCb;
875
876        public SessionCb(ISessionCallback cb) {
877            mCb = cb;
878        }
879
880        public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
881            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
882            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
883            try {
884                mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
885                return true;
886            } catch (RemoteException e) {
887                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
888            }
889            return false;
890        }
891
892        public void sendCommand(String command, Bundle args, ResultReceiver cb) {
893            try {
894                mCb.onCommand(command, args, cb);
895            } catch (RemoteException e) {
896                Slog.e(TAG, "Remote failure in sendCommand.", e);
897            }
898        }
899
900        public void sendCustomAction(String action, Bundle args) {
901            try {
902                mCb.onCustomAction(action, args);
903            } catch (RemoteException e) {
904                Slog.e(TAG, "Remote failure in sendCustomAction.", e);
905            }
906        }
907
908        public void play() {
909            try {
910                mCb.onPlay();
911            } catch (RemoteException e) {
912                Slog.e(TAG, "Remote failure in play.", e);
913            }
914        }
915
916        public void playFromMediaId(String mediaId, Bundle extras) {
917            try {
918                mCb.onPlayFromMediaId(mediaId, extras);
919            } catch (RemoteException e) {
920                Slog.e(TAG, "Remote failure in playUri.", e);
921            }
922        }
923
924        public void playFromSearch(String query, Bundle extras) {
925            try {
926                mCb.onPlayFromSearch(query, extras);
927            } catch (RemoteException e) {
928                Slog.e(TAG, "Remote failure in playFromSearch.", e);
929            }
930        }
931
932        public void skipToTrack(long id) {
933            try {
934                mCb.onSkipToTrack(id);
935            } catch (RemoteException e) {
936                Slog.e(TAG, "Remote failure in skipToTrack", e);
937            }
938        }
939
940        public void pause() {
941            try {
942                mCb.onPause();
943            } catch (RemoteException e) {
944                Slog.e(TAG, "Remote failure in pause.", e);
945            }
946        }
947
948        public void stop() {
949            try {
950                mCb.onStop();
951            } catch (RemoteException e) {
952                Slog.e(TAG, "Remote failure in stop.", e);
953            }
954        }
955
956        public void next() {
957            try {
958                mCb.onNext();
959            } catch (RemoteException e) {
960                Slog.e(TAG, "Remote failure in next.", e);
961            }
962        }
963
964        public void previous() {
965            try {
966                mCb.onPrevious();
967            } catch (RemoteException e) {
968                Slog.e(TAG, "Remote failure in previous.", e);
969            }
970        }
971
972        public void fastForward() {
973            try {
974                mCb.onFastForward();
975            } catch (RemoteException e) {
976                Slog.e(TAG, "Remote failure in fastForward.", e);
977            }
978        }
979
980        public void rewind() {
981            try {
982                mCb.onRewind();
983            } catch (RemoteException e) {
984                Slog.e(TAG, "Remote failure in rewind.", e);
985            }
986        }
987
988        public void seekTo(long pos) {
989            try {
990                mCb.onSeekTo(pos);
991            } catch (RemoteException e) {
992                Slog.e(TAG, "Remote failure in seekTo.", e);
993            }
994        }
995
996        public void rate(Rating rating) {
997            try {
998                mCb.onRate(rating);
999            } catch (RemoteException e) {
1000                Slog.e(TAG, "Remote failure in rate.", e);
1001            }
1002        }
1003
1004        public void adjustVolume(int direction) {
1005            try {
1006                mCb.onAdjustVolume(direction);
1007            } catch (RemoteException e) {
1008                Slog.e(TAG, "Remote failure in adjustVolume.", e);
1009            }
1010        }
1011
1012        public void setVolumeTo(int value) {
1013            try {
1014                mCb.onSetVolumeTo(value);
1015            } catch (RemoteException e) {
1016                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
1017            }
1018        }
1019    }
1020
1021    class ControllerStub extends ISessionController.Stub {
1022        @Override
1023        public void sendCommand(String command, Bundle args, ResultReceiver cb)
1024                throws RemoteException {
1025            mSessionCb.sendCommand(command, args, cb);
1026        }
1027
1028        @Override
1029        public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
1030            return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
1031        }
1032
1033        @Override
1034        public void registerCallbackListener(ISessionControllerCallback cb) {
1035            synchronized (mLock) {
1036                // If this session is already destroyed tell the caller and
1037                // don't add them.
1038                if (mDestroyed) {
1039                    try {
1040                        cb.onSessionDestroyed();
1041                    } catch (Exception e) {
1042                        // ignored
1043                    }
1044                    return;
1045                }
1046                if (getControllerCbIndexForCb(cb) < 0) {
1047                    mControllerCallbacks.add(cb);
1048                    if (DEBUG) {
1049                        Log.d(TAG, "registering controller callback " + cb);
1050                    }
1051                }
1052            }
1053        }
1054
1055        @Override
1056        public void unregisterCallbackListener(ISessionControllerCallback cb)
1057                throws RemoteException {
1058            synchronized (mLock) {
1059                int index = getControllerCbIndexForCb(cb);
1060                if (index != -1) {
1061                    mControllerCallbacks.remove(index);
1062                }
1063                if (DEBUG) {
1064                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
1065                }
1066            }
1067        }
1068
1069        @Override
1070        public String getPackageName() {
1071            return mPackageName;
1072        }
1073
1074        @Override
1075        public String getTag() {
1076            return mTag;
1077        }
1078
1079        @Override
1080        public PendingIntent getLaunchPendingIntent() {
1081            return mLaunchIntent;
1082        }
1083
1084        @Override
1085        public long getFlags() {
1086            return mFlags;
1087        }
1088
1089        @Override
1090        public ParcelableVolumeInfo getVolumeAttributes() {
1091            synchronized (mLock) {
1092                int type;
1093                int max;
1094                int current;
1095                if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1096                    type = mVolumeControlType;
1097                    max = mMaxVolume;
1098                    current = mOptimisticVolume != -1 ? mOptimisticVolume
1099                            : mCurrentVolume;
1100                } else {
1101                    int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
1102                    type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
1103                    max = mAudioManager.getStreamMaxVolume(stream);
1104                    current = mAudioManager.getStreamVolume(stream);
1105                }
1106                return new ParcelableVolumeInfo(mVolumeType, mAudioAttrs, type, max, current);
1107            }
1108        }
1109
1110        @Override
1111        public void adjustVolume(int direction, int flags, String packageName) {
1112            int uid = Binder.getCallingUid();
1113            final long token = Binder.clearCallingIdentity();
1114            try {
1115                MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false);
1116            } finally {
1117                Binder.restoreCallingIdentity(token);
1118            }
1119        }
1120
1121        @Override
1122        public void setVolumeTo(int value, int flags, String packageName) {
1123            int uid = Binder.getCallingUid();
1124            final long token = Binder.clearCallingIdentity();
1125            try {
1126                MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid);
1127            } finally {
1128                Binder.restoreCallingIdentity(token);
1129            }
1130        }
1131
1132        @Override
1133        public void play() throws RemoteException {
1134            mSessionCb.play();
1135        }
1136
1137        @Override
1138        public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1139            mSessionCb.playFromMediaId(mediaId, extras);
1140        }
1141
1142        @Override
1143        public void playFromSearch(String query, Bundle extras) throws RemoteException {
1144            mSessionCb.playFromSearch(query, extras);
1145        }
1146
1147        @Override
1148        public void skipToQueueItem(long id) {
1149            mSessionCb.skipToTrack(id);
1150        }
1151
1152
1153        @Override
1154        public void pause() throws RemoteException {
1155            mSessionCb.pause();
1156        }
1157
1158        @Override
1159        public void stop() throws RemoteException {
1160            mSessionCb.stop();
1161        }
1162
1163        @Override
1164        public void next() throws RemoteException {
1165            mSessionCb.next();
1166        }
1167
1168        @Override
1169        public void previous() throws RemoteException {
1170            mSessionCb.previous();
1171        }
1172
1173        @Override
1174        public void fastForward() throws RemoteException {
1175            mSessionCb.fastForward();
1176        }
1177
1178        @Override
1179        public void rewind() throws RemoteException {
1180            mSessionCb.rewind();
1181        }
1182
1183        @Override
1184        public void seekTo(long pos) throws RemoteException {
1185            mSessionCb.seekTo(pos);
1186        }
1187
1188        @Override
1189        public void rate(Rating rating) throws RemoteException {
1190            mSessionCb.rate(rating);
1191        }
1192
1193        @Override
1194        public void sendCustomAction(String action, Bundle args)
1195                throws RemoteException {
1196            mSessionCb.sendCustomAction(action, args);
1197        }
1198
1199
1200        @Override
1201        public MediaMetadata getMetadata() {
1202            synchronized (mLock) {
1203                return mMetadata;
1204            }
1205        }
1206
1207        @Override
1208        public PlaybackState getPlaybackState() {
1209            return getStateWithUpdatedPosition();
1210        }
1211
1212        @Override
1213        public ParceledListSlice getQueue() {
1214            synchronized (mLock) {
1215                return mQueue;
1216            }
1217        }
1218
1219        @Override
1220        public CharSequence getQueueTitle() {
1221            return mQueueTitle;
1222        }
1223
1224        @Override
1225        public Bundle getExtras() {
1226            synchronized (mLock) {
1227                return mExtras;
1228            }
1229        }
1230
1231        @Override
1232        public int getRatingType() {
1233            return mRatingType;
1234        }
1235
1236        @Override
1237        public boolean isTransportControlEnabled() {
1238            return MediaSessionRecord.this.isTransportControlEnabled();
1239        }
1240    }
1241
1242    private class MessageHandler extends Handler {
1243        private static final int MSG_UPDATE_METADATA = 1;
1244        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1245        private static final int MSG_UPDATE_QUEUE = 3;
1246        private static final int MSG_UPDATE_QUEUE_TITLE = 4;
1247        private static final int MSG_UPDATE_EXTRAS = 5;
1248        private static final int MSG_SEND_EVENT = 6;
1249        private static final int MSG_UPDATE_SESSION_STATE = 7;
1250        private static final int MSG_UPDATE_VOLUME = 8;
1251        private static final int MSG_DESTROYED = 9;
1252
1253        public MessageHandler(Looper looper) {
1254            super(looper);
1255        }
1256        @Override
1257        public void handleMessage(Message msg) {
1258            switch (msg.what) {
1259                case MSG_UPDATE_METADATA:
1260                    pushMetadataUpdate();
1261                    break;
1262                case MSG_UPDATE_PLAYBACK_STATE:
1263                    pushPlaybackStateUpdate();
1264                    break;
1265                case MSG_UPDATE_QUEUE:
1266                    pushQueueUpdate();
1267                    break;
1268                case MSG_UPDATE_QUEUE_TITLE:
1269                    pushQueueTitleUpdate();
1270                    break;
1271                case MSG_UPDATE_EXTRAS:
1272                    pushExtrasUpdate();
1273                    break;
1274                case MSG_SEND_EVENT:
1275                    pushEvent((String) msg.obj, msg.getData());
1276                    break;
1277                case MSG_UPDATE_SESSION_STATE:
1278                    // TODO add session state
1279                    break;
1280                case MSG_UPDATE_VOLUME:
1281                    pushVolumeUpdate();
1282                    break;
1283                case MSG_DESTROYED:
1284                    pushSessionDestroyed();
1285            }
1286        }
1287
1288        public void post(int what) {
1289            post(what, null);
1290        }
1291
1292        public void post(int what, Object obj) {
1293            obtainMessage(what, obj).sendToTarget();
1294        }
1295
1296        public void post(int what, Object obj, Bundle data) {
1297            Message msg = obtainMessage(what, obj);
1298            msg.setData(data);
1299            msg.sendToTarget();
1300        }
1301    }
1302
1303}
1304