MediaSessionRecord.java revision 30be970a8ecd984ace75354e00a8d969577d18e9
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.Context;
21import android.content.Intent;
22import android.content.pm.ParceledListSlice;
23import android.media.AudioManager;
24import android.media.AudioManagerInternal;
25import android.media.AudioSystem;
26import android.media.MediaDescription;
27import android.media.MediaMetadata;
28import android.media.Rating;
29import android.media.VolumeProvider;
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.MediaController.PlaybackInfo;
36import android.media.session.MediaSession;
37import android.media.session.ParcelableVolumeInfo;
38import android.media.session.PlaybackState;
39import android.media.AudioAttributes;
40import android.net.Uri;
41import android.os.Binder;
42import android.os.Bundle;
43import android.os.DeadObjectException;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Looper;
47import android.os.Message;
48import android.os.RemoteException;
49import android.os.ResultReceiver;
50import android.os.SystemClock;
51import android.util.Log;
52import android.util.Slog;
53import android.view.KeyEvent;
54
55import com.android.server.LocalServices;
56
57import java.io.PrintWriter;
58import java.util.ArrayList;
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 = Log.isLoggable(TAG, Log.DEBUG);
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     * @param flags Any of the flags from {@link AudioManager}.
232     * @param packageName The package that made the original volume request.
233     * @param uid The uid that made the original volume request.
234     * @param useSuggested True to use adjustSuggestedStreamVolume instead of
235     *            adjustStreamVolume.
236     */
237    public void adjustVolume(int direction, int flags, String packageName, int uid,
238            boolean useSuggested) {
239        int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
240        if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
241            flags &= ~AudioManager.FLAG_PLAY_SOUND;
242        }
243        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
244            // Adjust the volume with a handler not to be blocked by other system service.
245            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
246            postAdjustLocalVolume(stream, direction, flags, packageName, uid, useSuggested,
247                    previousFlagPlaySound);
248        } else {
249            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
250                // Nothing to do, the volume cannot be changed
251                return;
252            }
253            if (direction == AudioManager.ADJUST_TOGGLE_MUTE
254                    || direction == AudioManager.ADJUST_MUTE
255                    || direction == AudioManager.ADJUST_UNMUTE) {
256                Log.w(TAG, "Muting remote playback is not supported");
257                return;
258            }
259            mSessionCb.adjustVolume(direction);
260
261            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
262            mOptimisticVolume = volumeBefore + direction;
263            mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
264            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
265            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
266            if (volumeBefore != mOptimisticVolume) {
267                pushVolumeUpdate();
268            }
269            mService.notifyRemoteVolumeChanged(flags, this);
270
271            if (DEBUG) {
272                Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
273                        + mMaxVolume);
274            }
275        }
276    }
277
278    public void setVolumeTo(int value, int flags, String packageName, int uid) {
279        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
280            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
281            mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid);
282        } else {
283            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
284                // Nothing to do. The volume can't be set directly.
285                return;
286            }
287            value = Math.max(0, Math.min(value, mMaxVolume));
288            mSessionCb.setVolumeTo(value);
289
290            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
291            mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
292            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
293            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
294            if (volumeBefore != mOptimisticVolume) {
295                pushVolumeUpdate();
296            }
297            mService.notifyRemoteVolumeChanged(flags, this);
298
299            if (DEBUG) {
300                Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
301                        + mMaxVolume);
302            }
303        }
304    }
305
306    /**
307     * Check if this session has been set to active by the app.
308     *
309     * @return True if the session is active, false otherwise.
310     */
311    public boolean isActive() {
312        return mIsActive && !mDestroyed;
313    }
314
315    /**
316     * Check if the session is currently performing playback. This will also
317     * return true if the session was recently paused.
318     *
319     * @param includeRecentlyActive True if playback that was recently paused
320     *            should count, false if it shouldn't.
321     * @return True if the session is performing playback, false otherwise.
322     */
323    public boolean isPlaybackActive(boolean includeRecentlyActive) {
324        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
325        if (MediaSession.isActiveState(state)) {
326            return true;
327        }
328        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
329            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
330            if (inactiveTime < ACTIVE_BUFFER) {
331                return true;
332            }
333        }
334        return false;
335    }
336
337    /**
338     * Get the type of playback, either local or remote.
339     *
340     * @return The current type of playback.
341     */
342    public int getPlaybackType() {
343        return mVolumeType;
344    }
345
346    /**
347     * Get the local audio stream being used. Only valid if playback type is
348     * local.
349     *
350     * @return The audio stream the session is using.
351     */
352    public AudioAttributes getAudioAttributes() {
353        return mAudioAttrs;
354    }
355
356    /**
357     * Get the type of volume control. Only valid if playback type is remote.
358     *
359     * @return The volume control type being used.
360     */
361    public int getVolumeControl() {
362        return mVolumeControlType;
363    }
364
365    /**
366     * Get the max volume that can be set. Only valid if playback type is
367     * remote.
368     *
369     * @return The max volume that can be set.
370     */
371    public int getMaxVolume() {
372        return mMaxVolume;
373    }
374
375    /**
376     * Get the current volume for this session. Only valid if playback type is
377     * remote.
378     *
379     * @return The current volume of the remote playback.
380     */
381    public int getCurrentVolume() {
382        return mCurrentVolume;
383    }
384
385    /**
386     * Get the volume we'd like it to be set to. This is only valid for a short
387     * while after a call to adjust or set volume.
388     *
389     * @return The current optimistic volume or -1.
390     */
391    public int getOptimisticVolume() {
392        return mOptimisticVolume;
393    }
394
395    public boolean isTransportControlEnabled() {
396        return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
397    }
398
399    @Override
400    public void binderDied() {
401        mService.sessionDied(this);
402    }
403
404    /**
405     * Finish cleaning up this session, including disconnecting if connected and
406     * removing the death observer from the callback binder.
407     */
408    public void onDestroy() {
409        synchronized (mLock) {
410            if (mDestroyed) {
411                return;
412            }
413            mDestroyed = true;
414            mHandler.post(MessageHandler.MSG_DESTROYED);
415        }
416    }
417
418    public ISessionCallback getCallback() {
419        return mSessionCb.mCb;
420    }
421
422    public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
423        mSessionCb.sendMediaButton(ke, sequenceId, cb);
424    }
425
426    public void dump(PrintWriter pw, String prefix) {
427        pw.println(prefix + mTag + " " + this);
428
429        final String indent = prefix + "  ";
430        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
431                + ", userId=" + mUserId);
432        pw.println(indent + "package=" + mPackageName);
433        pw.println(indent + "launchIntent=" + mLaunchIntent);
434        pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
435        pw.println(indent + "active=" + mIsActive);
436        pw.println(indent + "flags=" + mFlags);
437        pw.println(indent + "rating type=" + mRatingType);
438        pw.println(indent + "controllers: " + mControllerCallbacks.size());
439        pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
440        pw.println(indent + "audioAttrs=" + mAudioAttrs);
441        pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
442                + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
443        pw.println(indent + "metadata:" + getShortMetadataString());
444        pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
445                + (mQueue == null ? 0 : mQueue.getList().size()));
446    }
447
448    @Override
449    public String toString() {
450        return mPackageName + "/" + mTag;
451    }
452
453    private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
454            final String packageName, final int uid, final boolean useSuggested,
455            final int previousFlagPlaySound) {
456        mHandler.post(new Runnable() {
457            @Override
458            public void run() {
459                if (useSuggested) {
460                    if (AudioSystem.isStreamActive(stream, 0)) {
461                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
462                                flags, packageName, uid);
463                    } else {
464                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
465                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
466                                flags | previousFlagPlaySound, packageName, uid);
467                    }
468                } else {
469                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
470                            packageName, uid);
471                }
472            }
473        });
474    }
475
476    private String getShortMetadataString() {
477        int fields = mMetadata == null ? 0 : mMetadata.size();
478        MediaDescription description = mMetadata == null ? null : mMetadata
479                .getDescription();
480        return "size=" + fields + ", description=" + description;
481    }
482
483    private void pushPlaybackStateUpdate() {
484        synchronized (mLock) {
485            if (mDestroyed) {
486                return;
487            }
488            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
489                ISessionControllerCallback cb = mControllerCallbacks.get(i);
490                try {
491                    cb.onPlaybackStateChanged(mPlaybackState);
492                } catch (DeadObjectException e) {
493                    mControllerCallbacks.remove(i);
494                    Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
495                } catch (RemoteException e) {
496                    Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
497                }
498            }
499        }
500    }
501
502    private void pushMetadataUpdate() {
503        synchronized (mLock) {
504            if (mDestroyed) {
505                return;
506            }
507            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
508                ISessionControllerCallback cb = mControllerCallbacks.get(i);
509                try {
510                    cb.onMetadataChanged(mMetadata);
511                } catch (DeadObjectException e) {
512                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
513                    mControllerCallbacks.remove(i);
514                } catch (RemoteException e) {
515                    Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
516                }
517            }
518        }
519    }
520
521    private void pushQueueUpdate() {
522        synchronized (mLock) {
523            if (mDestroyed) {
524                return;
525            }
526            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
527                ISessionControllerCallback cb = mControllerCallbacks.get(i);
528                try {
529                    cb.onQueueChanged(mQueue);
530                } catch (DeadObjectException e) {
531                    mControllerCallbacks.remove(i);
532                    Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e);
533                } catch (RemoteException e) {
534                    Log.w(TAG, "unexpected exception in pushQueueUpdate.", e);
535                }
536            }
537        }
538    }
539
540    private void pushQueueTitleUpdate() {
541        synchronized (mLock) {
542            if (mDestroyed) {
543                return;
544            }
545            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
546                ISessionControllerCallback cb = mControllerCallbacks.get(i);
547                try {
548                    cb.onQueueTitleChanged(mQueueTitle);
549                } catch (DeadObjectException e) {
550                    mControllerCallbacks.remove(i);
551                    Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e);
552                } catch (RemoteException e) {
553                    Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e);
554                }
555            }
556        }
557    }
558
559    private void pushExtrasUpdate() {
560        synchronized (mLock) {
561            if (mDestroyed) {
562                return;
563            }
564            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
565                ISessionControllerCallback cb = mControllerCallbacks.get(i);
566                try {
567                    cb.onExtrasChanged(mExtras);
568                } catch (DeadObjectException e) {
569                    mControllerCallbacks.remove(i);
570                    Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e);
571                } catch (RemoteException e) {
572                    Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e);
573                }
574            }
575        }
576    }
577
578    private void pushVolumeUpdate() {
579        synchronized (mLock) {
580            if (mDestroyed) {
581                return;
582            }
583            ParcelableVolumeInfo info = mController.getVolumeAttributes();
584            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
585                ISessionControllerCallback cb = mControllerCallbacks.get(i);
586                try {
587                    cb.onVolumeInfoChanged(info);
588                } catch (DeadObjectException e) {
589                    Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
590                } catch (RemoteException e) {
591                    Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
592                }
593            }
594        }
595    }
596
597    private void pushEvent(String event, Bundle data) {
598        synchronized (mLock) {
599            if (mDestroyed) {
600                return;
601            }
602            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
603                ISessionControllerCallback cb = mControllerCallbacks.get(i);
604                try {
605                    cb.onEvent(event, data);
606                } catch (DeadObjectException e) {
607                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
608                    mControllerCallbacks.remove(i);
609                } catch (RemoteException e) {
610                    Log.w(TAG, "unexpected exception in pushEvent.", e);
611                }
612            }
613        }
614    }
615
616    private void pushSessionDestroyed() {
617        synchronized (mLock) {
618            // This is the only method that may be (and can only be) called
619            // after the session is destroyed.
620            if (!mDestroyed) {
621                return;
622            }
623            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
624                ISessionControllerCallback cb = mControllerCallbacks.get(i);
625                try {
626                    cb.onSessionDestroyed();
627                } catch (DeadObjectException e) {
628                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
629                    mControllerCallbacks.remove(i);
630                } catch (RemoteException e) {
631                    Log.w(TAG, "unexpected exception in pushEvent.", e);
632                }
633            }
634            // After notifying clear all listeners
635            mControllerCallbacks.clear();
636        }
637    }
638
639    private PlaybackState getStateWithUpdatedPosition() {
640        PlaybackState state;
641        long duration = -1;
642        synchronized (mLock) {
643            state = mPlaybackState;
644            if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
645                duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
646            }
647        }
648        PlaybackState result = null;
649        if (state != null) {
650            if (state.getState() == PlaybackState.STATE_PLAYING
651                    || state.getState() == PlaybackState.STATE_FAST_FORWARDING
652                    || state.getState() == PlaybackState.STATE_REWINDING) {
653                long updateTime = state.getLastPositionUpdateTime();
654                long currentTime = SystemClock.elapsedRealtime();
655                if (updateTime > 0) {
656                    long position = (long) (state.getPlaybackSpeed()
657                            * (currentTime - updateTime)) + state.getPosition();
658                    if (duration >= 0 && position > duration) {
659                        position = duration;
660                    } else if (position < 0) {
661                        position = 0;
662                    }
663                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
664                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
665                            currentTime);
666                    result = builder.build();
667                }
668            }
669        }
670        return result == null ? state : result;
671    }
672
673    private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
674        IBinder binder = cb.asBinder();
675        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
676            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
677                return i;
678            }
679        }
680        return -1;
681    }
682
683    private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
684        @Override
685        public void run() {
686            boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
687            mOptimisticVolume = -1;
688            if (needUpdate) {
689                pushVolumeUpdate();
690            }
691        }
692    };
693
694    private final class SessionStub extends ISession.Stub {
695        @Override
696        public void destroy() {
697            mService.destroySession(MediaSessionRecord.this);
698        }
699
700        @Override
701        public void sendEvent(String event, Bundle data) {
702            mHandler.post(MessageHandler.MSG_SEND_EVENT, event,
703                    data == null ? null : new Bundle(data));
704        }
705
706        @Override
707        public ISessionController getController() {
708            return mController;
709        }
710
711        @Override
712        public void setActive(boolean active) {
713            mIsActive = active;
714            mService.updateSession(MediaSessionRecord.this);
715            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
716        }
717
718        @Override
719        public void setFlags(int flags) {
720            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
721                int pid = getCallingPid();
722                int uid = getCallingUid();
723                mService.enforcePhoneStatePermission(pid, uid);
724            }
725            mFlags = flags;
726            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
727        }
728
729        @Override
730        public void setMediaButtonReceiver(PendingIntent pi) {
731            mMediaButtonReceiver = pi;
732        }
733
734        @Override
735        public void setLaunchPendingIntent(PendingIntent pi) {
736            mLaunchIntent = pi;
737        }
738
739        @Override
740        public void setMetadata(MediaMetadata metadata) {
741            synchronized (mLock) {
742                MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
743                        .build();
744                // This is to guarantee that the underlying bundle is unparceled
745                // before we set it to prevent concurrent reads from throwing an
746                // exception
747                if (temp != null) {
748                    temp.size();
749                }
750                mMetadata = temp;
751            }
752            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
753        }
754
755        @Override
756        public void setPlaybackState(PlaybackState state) {
757            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
758            int newState = state == null ? 0 : state.getState();
759            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
760                mLastActiveTime = SystemClock.elapsedRealtime();
761            }
762            synchronized (mLock) {
763                mPlaybackState = state;
764            }
765            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
766            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
767        }
768
769        @Override
770        public void setQueue(ParceledListSlice queue) {
771            synchronized (mLock) {
772                mQueue = queue;
773            }
774            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
775        }
776
777        @Override
778        public void setQueueTitle(CharSequence title) {
779            mQueueTitle = title;
780            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
781        }
782
783        @Override
784        public void setExtras(Bundle extras) {
785            synchronized (mLock) {
786                mExtras = extras == null ? null : new Bundle(extras);
787            }
788            mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS);
789        }
790
791        @Override
792        public void setRatingType(int type) {
793            mRatingType = type;
794        }
795
796        @Override
797        public void setCurrentVolume(int volume) {
798            mCurrentVolume = volume;
799            mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
800        }
801
802        @Override
803        public void setPlaybackToLocal(AudioAttributes attributes) {
804            boolean typeChanged;
805            synchronized (mLock) {
806                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
807                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
808                if (attributes != null) {
809                    mAudioAttrs = attributes;
810                } else {
811                    Log.e(TAG, "Received null audio attributes, using existing attributes");
812                }
813            }
814            if (typeChanged) {
815                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
816                mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
817            }
818        }
819
820        @Override
821        public void setPlaybackToRemote(int control, int max) {
822            boolean typeChanged;
823            synchronized (mLock) {
824                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
825                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
826                mVolumeControlType = control;
827                mMaxVolume = max;
828            }
829            if (typeChanged) {
830                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
831                mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
832            }
833        }
834    }
835
836    class SessionCb {
837        private final ISessionCallback mCb;
838
839        public SessionCb(ISessionCallback cb) {
840            mCb = cb;
841        }
842
843        public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
844            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
845            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
846            try {
847                mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
848                return true;
849            } catch (RemoteException e) {
850                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
851            }
852            return false;
853        }
854
855        public void sendCommand(String command, Bundle args, ResultReceiver cb) {
856            try {
857                mCb.onCommand(command, args, cb);
858            } catch (RemoteException e) {
859                Slog.e(TAG, "Remote failure in sendCommand.", e);
860            }
861        }
862
863        public void sendCustomAction(String action, Bundle args) {
864            try {
865                mCb.onCustomAction(action, args);
866            } catch (RemoteException e) {
867                Slog.e(TAG, "Remote failure in sendCustomAction.", e);
868            }
869        }
870
871        public void play() {
872            try {
873                mCb.onPlay();
874            } catch (RemoteException e) {
875                Slog.e(TAG, "Remote failure in play.", e);
876            }
877        }
878
879        public void playFromMediaId(String mediaId, Bundle extras) {
880            try {
881                mCb.onPlayFromMediaId(mediaId, extras);
882            } catch (RemoteException e) {
883                Slog.e(TAG, "Remote failure in playUri.", e);
884            }
885        }
886
887        public void playFromSearch(String query, Bundle extras) {
888            try {
889                mCb.onPlayFromSearch(query, extras);
890            } catch (RemoteException e) {
891                Slog.e(TAG, "Remote failure in playFromSearch.", e);
892            }
893        }
894
895        public void playFromUri(Uri uri, Bundle extras) {
896            try {
897                mCb.onPlayFromUri(uri, extras);
898            } catch (RemoteException e) {
899                Slog.e(TAG, "Remote failure in playFromUri.", e);
900            }
901        }
902
903        public void skipToTrack(long id) {
904            try {
905                mCb.onSkipToTrack(id);
906            } catch (RemoteException e) {
907                Slog.e(TAG, "Remote failure in skipToTrack", e);
908            }
909        }
910
911        public void pause() {
912            try {
913                mCb.onPause();
914            } catch (RemoteException e) {
915                Slog.e(TAG, "Remote failure in pause.", e);
916            }
917        }
918
919        public void stop() {
920            try {
921                mCb.onStop();
922            } catch (RemoteException e) {
923                Slog.e(TAG, "Remote failure in stop.", e);
924            }
925        }
926
927        public void next() {
928            try {
929                mCb.onNext();
930            } catch (RemoteException e) {
931                Slog.e(TAG, "Remote failure in next.", e);
932            }
933        }
934
935        public void previous() {
936            try {
937                mCb.onPrevious();
938            } catch (RemoteException e) {
939                Slog.e(TAG, "Remote failure in previous.", e);
940            }
941        }
942
943        public void fastForward() {
944            try {
945                mCb.onFastForward();
946            } catch (RemoteException e) {
947                Slog.e(TAG, "Remote failure in fastForward.", e);
948            }
949        }
950
951        public void rewind() {
952            try {
953                mCb.onRewind();
954            } catch (RemoteException e) {
955                Slog.e(TAG, "Remote failure in rewind.", e);
956            }
957        }
958
959        public void seekTo(long pos) {
960            try {
961                mCb.onSeekTo(pos);
962            } catch (RemoteException e) {
963                Slog.e(TAG, "Remote failure in seekTo.", e);
964            }
965        }
966
967        public void rate(Rating rating) {
968            try {
969                mCb.onRate(rating);
970            } catch (RemoteException e) {
971                Slog.e(TAG, "Remote failure in rate.", e);
972            }
973        }
974
975        public void adjustVolume(int direction) {
976            try {
977                mCb.onAdjustVolume(direction);
978            } catch (RemoteException e) {
979                Slog.e(TAG, "Remote failure in adjustVolume.", e);
980            }
981        }
982
983        public void setVolumeTo(int value) {
984            try {
985                mCb.onSetVolumeTo(value);
986            } catch (RemoteException e) {
987                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
988            }
989        }
990    }
991
992    class ControllerStub extends ISessionController.Stub {
993        @Override
994        public void sendCommand(String command, Bundle args, ResultReceiver cb)
995                throws RemoteException {
996            mSessionCb.sendCommand(command, args, cb);
997        }
998
999        @Override
1000        public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
1001            return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
1002        }
1003
1004        @Override
1005        public void registerCallbackListener(ISessionControllerCallback cb) {
1006            synchronized (mLock) {
1007                // If this session is already destroyed tell the caller and
1008                // don't add them.
1009                if (mDestroyed) {
1010                    try {
1011                        cb.onSessionDestroyed();
1012                    } catch (Exception e) {
1013                        // ignored
1014                    }
1015                    return;
1016                }
1017                if (getControllerCbIndexForCb(cb) < 0) {
1018                    mControllerCallbacks.add(cb);
1019                    if (DEBUG) {
1020                        Log.d(TAG, "registering controller callback " + cb);
1021                    }
1022                }
1023            }
1024        }
1025
1026        @Override
1027        public void unregisterCallbackListener(ISessionControllerCallback cb)
1028                throws RemoteException {
1029            synchronized (mLock) {
1030                int index = getControllerCbIndexForCb(cb);
1031                if (index != -1) {
1032                    mControllerCallbacks.remove(index);
1033                }
1034                if (DEBUG) {
1035                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
1036                }
1037            }
1038        }
1039
1040        @Override
1041        public String getPackageName() {
1042            return mPackageName;
1043        }
1044
1045        @Override
1046        public String getTag() {
1047            return mTag;
1048        }
1049
1050        @Override
1051        public PendingIntent getLaunchPendingIntent() {
1052            return mLaunchIntent;
1053        }
1054
1055        @Override
1056        public long getFlags() {
1057            return mFlags;
1058        }
1059
1060        @Override
1061        public ParcelableVolumeInfo getVolumeAttributes() {
1062            int volumeType;
1063            AudioAttributes attributes;
1064            synchronized (mLock) {
1065                if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1066                    int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
1067                    return new ParcelableVolumeInfo(
1068                            mVolumeType, mAudioAttrs, mVolumeControlType, mMaxVolume, current);
1069                }
1070                volumeType = mVolumeType;
1071                attributes = mAudioAttrs;
1072            }
1073            int stream = AudioAttributes.toLegacyStreamType(attributes);
1074            int max = mAudioManager.getStreamMaxVolume(stream);
1075            int current = mAudioManager.getStreamVolume(stream);
1076            return new ParcelableVolumeInfo(
1077                    volumeType, attributes, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max, current);
1078        }
1079
1080        @Override
1081        public void adjustVolume(int direction, int flags, String packageName) {
1082            int uid = Binder.getCallingUid();
1083            final long token = Binder.clearCallingIdentity();
1084            try {
1085                MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false);
1086            } finally {
1087                Binder.restoreCallingIdentity(token);
1088            }
1089        }
1090
1091        @Override
1092        public void setVolumeTo(int value, int flags, String packageName) {
1093            int uid = Binder.getCallingUid();
1094            final long token = Binder.clearCallingIdentity();
1095            try {
1096                MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid);
1097            } finally {
1098                Binder.restoreCallingIdentity(token);
1099            }
1100        }
1101
1102        @Override
1103        public void play() throws RemoteException {
1104            mSessionCb.play();
1105        }
1106
1107        @Override
1108        public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1109            mSessionCb.playFromMediaId(mediaId, extras);
1110        }
1111
1112        @Override
1113        public void playFromSearch(String query, Bundle extras) throws RemoteException {
1114            mSessionCb.playFromSearch(query, extras);
1115        }
1116
1117        @Override
1118        public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1119            mSessionCb.playFromUri(uri, extras);
1120        }
1121
1122        @Override
1123        public void skipToQueueItem(long id) {
1124            mSessionCb.skipToTrack(id);
1125        }
1126
1127
1128        @Override
1129        public void pause() throws RemoteException {
1130            mSessionCb.pause();
1131        }
1132
1133        @Override
1134        public void stop() throws RemoteException {
1135            mSessionCb.stop();
1136        }
1137
1138        @Override
1139        public void next() throws RemoteException {
1140            mSessionCb.next();
1141        }
1142
1143        @Override
1144        public void previous() throws RemoteException {
1145            mSessionCb.previous();
1146        }
1147
1148        @Override
1149        public void fastForward() throws RemoteException {
1150            mSessionCb.fastForward();
1151        }
1152
1153        @Override
1154        public void rewind() throws RemoteException {
1155            mSessionCb.rewind();
1156        }
1157
1158        @Override
1159        public void seekTo(long pos) throws RemoteException {
1160            mSessionCb.seekTo(pos);
1161        }
1162
1163        @Override
1164        public void rate(Rating rating) throws RemoteException {
1165            mSessionCb.rate(rating);
1166        }
1167
1168        @Override
1169        public void sendCustomAction(String action, Bundle args)
1170                throws RemoteException {
1171            mSessionCb.sendCustomAction(action, args);
1172        }
1173
1174
1175        @Override
1176        public MediaMetadata getMetadata() {
1177            synchronized (mLock) {
1178                return mMetadata;
1179            }
1180        }
1181
1182        @Override
1183        public PlaybackState getPlaybackState() {
1184            return getStateWithUpdatedPosition();
1185        }
1186
1187        @Override
1188        public ParceledListSlice getQueue() {
1189            synchronized (mLock) {
1190                return mQueue;
1191            }
1192        }
1193
1194        @Override
1195        public CharSequence getQueueTitle() {
1196            return mQueueTitle;
1197        }
1198
1199        @Override
1200        public Bundle getExtras() {
1201            synchronized (mLock) {
1202                return mExtras;
1203            }
1204        }
1205
1206        @Override
1207        public int getRatingType() {
1208            return mRatingType;
1209        }
1210
1211        @Override
1212        public boolean isTransportControlEnabled() {
1213            return MediaSessionRecord.this.isTransportControlEnabled();
1214        }
1215    }
1216
1217    private class MessageHandler extends Handler {
1218        private static final int MSG_UPDATE_METADATA = 1;
1219        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1220        private static final int MSG_UPDATE_QUEUE = 3;
1221        private static final int MSG_UPDATE_QUEUE_TITLE = 4;
1222        private static final int MSG_UPDATE_EXTRAS = 5;
1223        private static final int MSG_SEND_EVENT = 6;
1224        private static final int MSG_UPDATE_SESSION_STATE = 7;
1225        private static final int MSG_UPDATE_VOLUME = 8;
1226        private static final int MSG_DESTROYED = 9;
1227
1228        public MessageHandler(Looper looper) {
1229            super(looper);
1230        }
1231        @Override
1232        public void handleMessage(Message msg) {
1233            switch (msg.what) {
1234                case MSG_UPDATE_METADATA:
1235                    pushMetadataUpdate();
1236                    break;
1237                case MSG_UPDATE_PLAYBACK_STATE:
1238                    pushPlaybackStateUpdate();
1239                    break;
1240                case MSG_UPDATE_QUEUE:
1241                    pushQueueUpdate();
1242                    break;
1243                case MSG_UPDATE_QUEUE_TITLE:
1244                    pushQueueTitleUpdate();
1245                    break;
1246                case MSG_UPDATE_EXTRAS:
1247                    pushExtrasUpdate();
1248                    break;
1249                case MSG_SEND_EVENT:
1250                    pushEvent((String) msg.obj, msg.getData());
1251                    break;
1252                case MSG_UPDATE_SESSION_STATE:
1253                    // TODO add session state
1254                    break;
1255                case MSG_UPDATE_VOLUME:
1256                    pushVolumeUpdate();
1257                    break;
1258                case MSG_DESTROYED:
1259                    pushSessionDestroyed();
1260            }
1261        }
1262
1263        public void post(int what) {
1264            post(what, null);
1265        }
1266
1267        public void post(int what, Object obj) {
1268            obtainMessage(what, obj).sendToTarget();
1269        }
1270
1271        public void post(int what, Object obj, Bundle data) {
1272            Message msg = obtainMessage(what, obj);
1273            msg.setData(data);
1274            msg.sendToTarget();
1275        }
1276    }
1277
1278}
1279