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