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