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