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