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