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