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