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