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