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