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