• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.media;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.app.PendingIntent;
24 import android.content.ComponentName;
25 import android.content.ContentProvider;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ParceledListSlice;
31 import android.content.pm.ResolveInfo;
32 import android.media.AudioAttributes;
33 import android.media.AudioManager;
34 import android.media.AudioSystem;
35 import android.media.MediaMetadata;
36 import android.media.MediaRouter2Manager;
37 import android.media.Rating;
38 import android.media.RoutingSessionInfo;
39 import android.media.VolumeProvider;
40 import android.media.session.ISession;
41 import android.media.session.ISessionCallback;
42 import android.media.session.ISessionController;
43 import android.media.session.ISessionControllerCallback;
44 import android.media.session.MediaController;
45 import android.media.session.MediaController.PlaybackInfo;
46 import android.media.session.MediaSession;
47 import android.media.session.MediaSession.QueueItem;
48 import android.media.session.ParcelableListBinder;
49 import android.media.session.PlaybackState;
50 import android.net.Uri;
51 import android.os.Binder;
52 import android.os.Bundle;
53 import android.os.DeadObjectException;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.Looper;
57 import android.os.Message;
58 import android.os.Process;
59 import android.os.RemoteException;
60 import android.os.ResultReceiver;
61 import android.os.SystemClock;
62 import android.os.UserHandle;
63 import android.text.TextUtils;
64 import android.util.EventLog;
65 import android.util.Log;
66 import android.view.KeyEvent;
67 
68 import com.android.server.LocalServices;
69 import com.android.server.uri.UriGrantsManagerInternal;
70 
71 import java.io.PrintWriter;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collection;
75 import java.util.List;
76 import java.util.NoSuchElementException;
77 import java.util.concurrent.CopyOnWriteArrayList;
78 
79 /**
80  * This is the system implementation of a Session. Apps will interact with the
81  * MediaSession wrapper class instead.
82  */
83 // TODO(jaewan): Do not call service method directly -- introduce listener instead.
84 public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
85     private static final String TAG = "MediaSessionRecord";
86     private static final String[] ART_URIS = new String[] {
87             MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
88             MediaMetadata.METADATA_KEY_ART_URI,
89             MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI};
90     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
91 
92     /**
93      * The amount of time we'll send an assumed volume after the last volume
94      * command before reverting to the last reported volume.
95      */
96     private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
97 
98     /**
99      * These are states that usually indicate the user took an action and should
100      * bump priority regardless of the old state.
101      */
102     private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList(
103             PlaybackState.STATE_FAST_FORWARDING,
104             PlaybackState.STATE_REWINDING,
105             PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
106             PlaybackState.STATE_SKIPPING_TO_NEXT);
107     /**
108      * These are states that usually indicate the user took an action if they
109      * were entered from a non-priority state.
110      */
111     private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList(
112             PlaybackState.STATE_BUFFERING,
113             PlaybackState.STATE_CONNECTING,
114             PlaybackState.STATE_PLAYING);
115 
116     private static final AudioAttributes DEFAULT_ATTRIBUTES =
117             new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
118 
getVolumeStream(@ullable AudioAttributes attr)119     private static int getVolumeStream(@Nullable AudioAttributes attr) {
120         if (attr == null) {
121             return DEFAULT_ATTRIBUTES.getVolumeControlStream();
122         }
123         final int stream = attr.getVolumeControlStream();
124         if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
125             return DEFAULT_ATTRIBUTES.getVolumeControlStream();
126         }
127         return stream;
128     }
129 
130     private final MessageHandler mHandler;
131 
132     private final int mOwnerPid;
133     private final int mOwnerUid;
134     private final int mUserId;
135     private final String mPackageName;
136     private final String mTag;
137     private final Bundle mSessionInfo;
138     private final ControllerStub mController;
139     private final MediaSession.Token mSessionToken;
140     private final SessionStub mSession;
141     private final SessionCb mSessionCb;
142     private final MediaSessionService mService;
143     private final UriGrantsManagerInternal mUgmInternal;
144     private final Context mContext;
145     private final boolean mVolumeAdjustmentForRemoteGroupSessions;
146 
147     private final Object mLock = new Object();
148     private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
149             mControllerCallbackHolders = new CopyOnWriteArrayList<>();
150 
151     private long mFlags;
152     private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
153     private PendingIntent mLaunchIntent;
154 
155     // TransportPerformer fields
156     private Bundle mExtras;
157     // Note: Avoid unparceling the bundle inside MediaMetadata since unparceling in system process
158     // may result in throwing an exception.
159     private MediaMetadata mMetadata;
160     private PlaybackState mPlaybackState;
161     private List<QueueItem> mQueue;
162     private CharSequence mQueueTitle;
163     private int mRatingType;
164     // End TransportPerformer fields
165 
166     // Volume handling fields
167     private AudioAttributes mAudioAttrs;
168     private AudioManager mAudioManager;
169     private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
170     private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
171     private int mMaxVolume = 0;
172     private int mCurrentVolume = 0;
173     private int mOptimisticVolume = -1;
174     private String mVolumeControlId;
175     // End volume handling fields
176 
177     private boolean mIsActive = false;
178     private boolean mDestroyed = false;
179 
180     private long mDuration = -1;
181     private String mMetadataDescription;
182 
183     private int mPolicies;
184 
MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo, MediaSessionService service, Looper handlerLooper, int policies)185     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
186             ISessionCallback cb, String tag, Bundle sessionInfo,
187             MediaSessionService service, Looper handlerLooper, int policies)
188             throws RemoteException {
189         mOwnerPid = ownerPid;
190         mOwnerUid = ownerUid;
191         mUserId = userId;
192         mPackageName = ownerPackageName;
193         mTag = tag;
194         mSessionInfo = sessionInfo;
195         mController = new ControllerStub();
196         mSessionToken = new MediaSession.Token(ownerUid, mController);
197         mSession = new SessionStub();
198         mSessionCb = new SessionCb(cb);
199         mService = service;
200         mContext = mService.getContext();
201         mHandler = new MessageHandler(handlerLooper);
202         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
203         mAudioAttrs = DEFAULT_ATTRIBUTES;
204         mPolicies = policies;
205         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
206         mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
207                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
208 
209         // May throw RemoteException if the session app is killed.
210         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
211     }
212 
213     /**
214      * Get the session binder for the {@link MediaSession}.
215      *
216      * @return The session binder apps talk to.
217      */
getSessionBinder()218     public ISession getSessionBinder() {
219         return mSession;
220     }
221 
222     /**
223      * Get the session token for creating {@link MediaController}.
224      *
225      * @return The session token.
226      */
getSessionToken()227     public MediaSession.Token getSessionToken() {
228         return mSessionToken;
229     }
230 
231     /**
232      * Get the info for this session.
233      *
234      * @return Info that identifies this session.
235      */
236     @Override
getPackageName()237     public String getPackageName() {
238         return mPackageName;
239     }
240 
241     /**
242      * Get the intent the app set for their media button receiver.
243      *
244      * @return The pending intent set by the app or null.
245      */
getMediaButtonReceiver()246     public MediaButtonReceiverHolder getMediaButtonReceiver() {
247         return mMediaButtonReceiverHolder;
248     }
249 
250     /**
251      * Get the UID this session was created for.
252      *
253      * @return The UID for this session.
254      */
255     @Override
getUid()256     public int getUid() {
257         return mOwnerUid;
258     }
259 
260     /**
261      * Get the user id this session was created for.
262      *
263      * @return The user id for this session.
264      */
265     @Override
getUserId()266     public int getUserId() {
267         return mUserId;
268     }
269 
270     /**
271      * Check if this session has system priorty and should receive media buttons
272      * before any other sessions.
273      *
274      * @return True if this is a system priority session, false otherwise
275      */
276     @Override
isSystemPriority()277     public boolean isSystemPriority() {
278         return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
279     }
280 
281     /**
282      * Send a volume adjustment to the session owner. Direction must be one of
283      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
284      * {@link AudioManager#ADJUST_SAME}.
285      *
286      * @param packageName The package that made the original volume request.
287      * @param opPackageName The op package that made the original volume request.
288      * @param pid The pid that made the original volume request.
289      * @param uid The uid that made the original volume request.
290      * @param asSystemService {@code true} if the event sent to the session as if it was come from
291      *          the system service instead of the app process. This helps sessions to distinguish
292      *          between the key injection by the app and key events from the hardware devices.
293      *          Should be used only when the volume key events aren't handled by foreground
294      *          activity. {@code false} otherwise to tell session about the real caller.
295      * @param direction The direction to adjust volume in.
296      * @param flags Any of the flags from {@link AudioManager}.
297      * @param useSuggested True to use adjustSuggestedStreamVolumeForUid instead of
298      *          adjustStreamVolumeForUid
299      */
adjustVolume(String packageName, String opPackageName, int pid, int uid, boolean asSystemService, int direction, int flags, boolean useSuggested)300     public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
301             boolean asSystemService, int direction, int flags, boolean useSuggested) {
302         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
303         if (checkPlaybackActiveState(true) || isSystemPriority()) {
304             flags &= ~AudioManager.FLAG_PLAY_SOUND;
305         }
306         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
307             // Adjust the volume with a handler not to be blocked by other system service.
308             int stream = getVolumeStream(mAudioAttrs);
309             postAdjustLocalVolume(stream, direction, flags, opPackageName, pid, uid,
310                     asSystemService, useSuggested, previousFlagPlaySound);
311         } else {
312             if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
313                 if (DEBUG) {
314                     Log.d(TAG, "Session does not support volume adjustment");
315                 }
316             } else if (direction == AudioManager.ADJUST_TOGGLE_MUTE
317                     || direction == AudioManager.ADJUST_MUTE
318                     || direction == AudioManager.ADJUST_UNMUTE) {
319                 Log.w(TAG, "Muting remote playback is not supported");
320             } else {
321                 if (DEBUG) {
322                     Log.w(TAG, "adjusting volume, pkg=" + packageName + ", asSystemService="
323                             + asSystemService + ", dir=" + direction);
324                 }
325                 mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction);
326 
327                 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
328                 mOptimisticVolume = volumeBefore + direction;
329                 mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
330                 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
331                 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
332                 if (volumeBefore != mOptimisticVolume) {
333                     pushVolumeUpdate();
334                 }
335 
336                 if (DEBUG) {
337                     Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
338                             + mMaxVolume);
339                 }
340             }
341             // Always notify, even if the volume hasn't changed. This is important to ensure that
342             // System UI receives an event if a hardware volume key is pressed but the session that
343             // handles it does not allow volume adjustment. Without such an event, System UI would
344             // not show volume controls to the user.
345             mService.notifyRemoteVolumeChanged(flags, this);
346         }
347     }
348 
setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value, int flags)349     private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value,
350             int flags) {
351         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
352             int stream = getVolumeStream(mAudioAttrs);
353             final int volumeValue = value;
354             mHandler.post(new Runnable() {
355                 @Override
356                 public void run() {
357                     try {
358                         mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags,
359                                 opPackageName, uid, pid,
360                                 mContext.getApplicationInfo().targetSdkVersion);
361                     } catch (IllegalArgumentException | SecurityException e) {
362                         Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue
363                                 + ", flags=" + flags, e);
364                     }
365                 }
366             });
367         } else {
368             if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
369                 if (DEBUG) {
370                     Log.d(TAG, "Session does not support setting volume");
371                 }
372             } else {
373                 value = Math.max(0, Math.min(value, mMaxVolume));
374                 mSessionCb.setVolumeTo(packageName, pid, uid, value);
375 
376                 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
377                 mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
378                 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
379                 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
380                 if (volumeBefore != mOptimisticVolume) {
381                     pushVolumeUpdate();
382                 }
383 
384                 if (DEBUG) {
385                     Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
386                             + mMaxVolume);
387                 }
388             }
389             // Always notify, even if the volume hasn't changed.
390             mService.notifyRemoteVolumeChanged(flags, this);
391         }
392     }
393 
394     /**
395      * Check if this session has been set to active by the app.
396      * <p>
397      * It's not used to prioritize sessions for dispatching media keys since API 26, but still used
398      * to filter session list in MediaSessionManager#getActiveSessions().
399      *
400      * @return True if the session is active, false otherwise.
401      */
402     @Override
isActive()403     public boolean isActive() {
404         return mIsActive && !mDestroyed;
405     }
406 
407     /**
408      * Check if the session's playback active state matches with the expectation. This always return
409      * {@code false} if the playback state is {@code null}, where we cannot know the actual playback
410      * state associated with the session.
411      *
412      * @param expected True if playback is expected to be active. false otherwise.
413      * @return True if the session's playback matches with the expectation. false otherwise.
414      */
415     @Override
checkPlaybackActiveState(boolean expected)416     public boolean checkPlaybackActiveState(boolean expected) {
417         if (mPlaybackState == null) {
418             return false;
419         }
420         return mPlaybackState.isActive() == expected;
421     }
422 
423     /**
424      * Get whether the playback is local.
425      *
426      * @return {@code true} if the playback is local.
427      */
428     @Override
isPlaybackTypeLocal()429     public boolean isPlaybackTypeLocal() {
430         return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
431     }
432 
433     @Override
binderDied()434     public void binderDied() {
435         mService.onSessionDied(this);
436     }
437 
438     /**
439      * Finish cleaning up this session, including disconnecting if connected and
440      * removing the death observer from the callback binder.
441      */
442     @Override
close()443     public void close() {
444         synchronized (mLock) {
445             if (mDestroyed) {
446                 return;
447             }
448             mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
449             mDestroyed = true;
450             mPlaybackState = null;
451             mHandler.post(MessageHandler.MSG_DESTROYED);
452         }
453     }
454 
455     @Override
isClosed()456     public boolean isClosed() {
457         synchronized (mLock) {
458             return mDestroyed;
459         }
460     }
461 
462     /**
463      * Sends media button.
464      *
465      * @param packageName caller package name
466      * @param pid caller pid
467      * @param uid caller uid
468      * @param asSystemService {@code true} if the event sent to the session as if it was come from
469      *          the system service instead of the app process.
470      * @param ke key events
471      * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
472      * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
473      *           needed.
474      * @return {@code true} if the attempt to send media button was successfully.
475      *         {@code false} otherwise.
476      */
sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb)477     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
478             KeyEvent ke, int sequenceId, ResultReceiver cb) {
479         return mSessionCb.sendMediaButton(packageName, pid, uid, asSystemService, ke, sequenceId,
480                 cb);
481     }
482 
483     @Override
canHandleVolumeKey()484     public boolean canHandleVolumeKey() {
485         if (isPlaybackTypeLocal() || mVolumeAdjustmentForRemoteGroupSessions) {
486             return true;
487         }
488         MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
489         List<RoutingSessionInfo> sessions =
490                 mRouter2Manager.getRoutingSessions(mPackageName);
491         boolean foundNonSystemSession = false;
492         boolean isGroup = false;
493         for (RoutingSessionInfo session : sessions) {
494             if (!session.isSystemSession()) {
495                 foundNonSystemSession = true;
496                 int selectedRouteCount = session.getSelectedRoutes().size();
497                 if (selectedRouteCount > 1) {
498                     isGroup = true;
499                     break;
500                 }
501             }
502         }
503         if (!foundNonSystemSession) {
504             Log.d(TAG, "No routing session for " + mPackageName);
505             return false;
506         }
507         return !isGroup;
508     }
509 
510     @Override
getSessionPolicies()511     public int getSessionPolicies() {
512         synchronized (mLock) {
513             return mPolicies;
514         }
515     }
516 
517     @Override
setSessionPolicies(int policies)518     public void setSessionPolicies(int policies) {
519         synchronized (mLock) {
520             mPolicies = policies;
521         }
522     }
523 
524     @Override
dump(PrintWriter pw, String prefix)525     public void dump(PrintWriter pw, String prefix) {
526         pw.println(prefix + mTag + " " + this);
527 
528         final String indent = prefix + "  ";
529         pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
530                 + ", userId=" + mUserId);
531         pw.println(indent + "package=" + mPackageName);
532         pw.println(indent + "launchIntent=" + mLaunchIntent);
533         pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder);
534         pw.println(indent + "active=" + mIsActive);
535         pw.println(indent + "flags=" + mFlags);
536         pw.println(indent + "rating type=" + mRatingType);
537         pw.println(indent + "controllers: " + mControllerCallbackHolders.size());
538         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
539         pw.println(indent + "audioAttrs=" + mAudioAttrs);
540         pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
541                 + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
542         pw.println(indent + "metadata: " + mMetadataDescription);
543         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
544                 + (mQueue == null ? 0 : mQueue.size()));
545     }
546 
547     @Override
toString()548     public String toString() {
549         return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
550     }
551 
postAdjustLocalVolume(final int stream, final int direction, final int flags, final String callingOpPackageName, final int callingPid, final int callingUid, final boolean asSystemService, final boolean useSuggested, final int previousFlagPlaySound)552     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
553             final String callingOpPackageName, final int callingPid, final int callingUid,
554             final boolean asSystemService, final boolean useSuggested,
555             final int previousFlagPlaySound) {
556         if (DEBUG) {
557             Log.w(TAG, "adjusting local volume, stream=" + stream + ", dir=" + direction
558                     + ", asSystemService=" + asSystemService + ", useSuggested=" + useSuggested);
559         }
560         // Must use opPackageName for adjusting volumes with UID.
561         final String opPackageName;
562         final int uid;
563         final int pid;
564         if (asSystemService) {
565             opPackageName = mContext.getOpPackageName();
566             uid = Process.SYSTEM_UID;
567             pid = Process.myPid();
568         } else {
569             opPackageName = callingOpPackageName;
570             uid = callingUid;
571             pid = callingPid;
572         }
573         mHandler.post(new Runnable() {
574             @Override
575             public void run() {
576                 try {
577                     if (useSuggested) {
578                         if (AudioSystem.isStreamActive(stream, 0)) {
579                             mAudioManager.adjustSuggestedStreamVolumeForUid(stream,
580                                     direction, flags, opPackageName, uid, pid,
581                                     mContext.getApplicationInfo().targetSdkVersion);
582                         } else {
583                             mAudioManager.adjustSuggestedStreamVolumeForUid(
584                                     AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
585                                     flags | previousFlagPlaySound, opPackageName, uid, pid,
586                                     mContext.getApplicationInfo().targetSdkVersion);
587                         }
588                     } else {
589                         mAudioManager.adjustStreamVolumeForUid(stream, direction, flags,
590                                 opPackageName, uid, pid,
591                                 mContext.getApplicationInfo().targetSdkVersion);
592                     }
593                 } catch (IllegalArgumentException | SecurityException e) {
594                     Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
595                             + stream + ", flags=" + flags + ", opPackageName=" + opPackageName
596                             + ", uid=" + uid + ", useSuggested=" + useSuggested
597                             + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
598                 }
599             }
600         });
601     }
602 
logCallbackException( String msg, ISessionControllerCallbackHolder holder, Exception e)603     private void logCallbackException(
604             String msg, ISessionControllerCallbackHolder holder, Exception e) {
605         Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
606                 + ", exception=" + e);
607     }
608 
pushPlaybackStateUpdate()609     private void pushPlaybackStateUpdate() {
610         PlaybackState playbackState;
611         synchronized (mLock) {
612             if (mDestroyed) {
613                 return;
614             }
615             playbackState = mPlaybackState;
616         }
617         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
618         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
619             try {
620                 holder.mCallback.onPlaybackStateChanged(playbackState);
621             } catch (DeadObjectException e) {
622                 if (deadCallbackHolders == null) {
623                     deadCallbackHolders = new ArrayList<>();
624                 }
625                 deadCallbackHolders.add(holder);
626                 logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder,
627                         e);
628             } catch (RemoteException e) {
629                 logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e);
630             }
631         }
632         if (deadCallbackHolders != null) {
633             mControllerCallbackHolders.removeAll(deadCallbackHolders);
634         }
635     }
636 
pushMetadataUpdate()637     private void pushMetadataUpdate() {
638         MediaMetadata metadata;
639         synchronized (mLock) {
640             if (mDestroyed) {
641                 return;
642             }
643             metadata = mMetadata;
644         }
645         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
646         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
647             try {
648                 holder.mCallback.onMetadataChanged(metadata);
649             } catch (DeadObjectException e) {
650                 if (deadCallbackHolders == null) {
651                     deadCallbackHolders = new ArrayList<>();
652                 }
653                 deadCallbackHolders.add(holder);
654                 logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
655             } catch (RemoteException e) {
656                 logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
657             }
658         }
659         if (deadCallbackHolders != null) {
660             mControllerCallbackHolders.removeAll(deadCallbackHolders);
661         }
662     }
663 
pushQueueUpdate()664     private void pushQueueUpdate() {
665         ArrayList<QueueItem> toSend;
666         synchronized (mLock) {
667             if (mDestroyed) {
668                 return;
669             }
670             toSend = mQueue == null ? null : new ArrayList<>(mQueue);
671         }
672         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
673         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
674             ParceledListSlice<QueueItem> parcelableQueue = null;
675             if (toSend != null) {
676                 parcelableQueue = new ParceledListSlice<>(toSend);
677                 // Limit the size of initial Parcel to prevent binder buffer overflow
678                 // as onQueueChanged is an async binder call.
679                 parcelableQueue.setInlineCountLimit(1);
680             }
681 
682             try {
683                 holder.mCallback.onQueueChanged(parcelableQueue);
684             } catch (DeadObjectException e) {
685                 if (deadCallbackHolders == null) {
686                     deadCallbackHolders = new ArrayList<>();
687                 }
688                 deadCallbackHolders.add(holder);
689                 logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
690             } catch (RemoteException e) {
691                 logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
692             }
693         }
694         if (deadCallbackHolders != null) {
695             mControllerCallbackHolders.removeAll(deadCallbackHolders);
696         }
697     }
698 
pushQueueTitleUpdate()699     private void pushQueueTitleUpdate() {
700         CharSequence queueTitle;
701         synchronized (mLock) {
702             if (mDestroyed) {
703                 return;
704             }
705             queueTitle = mQueueTitle;
706         }
707         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
708         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
709             try {
710                 holder.mCallback.onQueueTitleChanged(queueTitle);
711             } catch (DeadObjectException e) {
712                 if (deadCallbackHolders == null) {
713                     deadCallbackHolders = new ArrayList<>();
714                 }
715                 deadCallbackHolders.add(holder);
716                 logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e);
717             } catch (RemoteException e) {
718                 logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
719             }
720         }
721         if (deadCallbackHolders != null) {
722             mControllerCallbackHolders.removeAll(deadCallbackHolders);
723         }
724     }
725 
pushExtrasUpdate()726     private void pushExtrasUpdate() {
727         Bundle extras;
728         synchronized (mLock) {
729             if (mDestroyed) {
730                 return;
731             }
732             extras = mExtras;
733         }
734         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
735         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
736             try {
737                 holder.mCallback.onExtrasChanged(extras);
738             } catch (DeadObjectException e) {
739                 if (deadCallbackHolders == null) {
740                     deadCallbackHolders = new ArrayList<>();
741                 }
742                 deadCallbackHolders.add(holder);
743                 logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
744             } catch (RemoteException e) {
745                 logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
746             }
747         }
748         if (deadCallbackHolders != null) {
749             mControllerCallbackHolders.removeAll(deadCallbackHolders);
750         }
751     }
752 
pushVolumeUpdate()753     private void pushVolumeUpdate() {
754         PlaybackInfo info;
755         synchronized (mLock) {
756             if (mDestroyed) {
757                 return;
758             }
759             info = getVolumeAttributes();
760         }
761         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
762         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
763             try {
764                 holder.mCallback.onVolumeInfoChanged(info);
765             } catch (DeadObjectException e) {
766                 if (deadCallbackHolders == null) {
767                     deadCallbackHolders = new ArrayList<>();
768                 }
769                 deadCallbackHolders.add(holder);
770                 logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
771             } catch (RemoteException e) {
772                 logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
773             }
774         }
775         if (deadCallbackHolders != null) {
776             mControllerCallbackHolders.removeAll(deadCallbackHolders);
777         }
778     }
779 
pushEvent(String event, Bundle data)780     private void pushEvent(String event, Bundle data) {
781         synchronized (mLock) {
782             if (mDestroyed) {
783                 return;
784             }
785         }
786         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
787         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
788             try {
789                 holder.mCallback.onEvent(event, data);
790             } catch (DeadObjectException e) {
791                 if (deadCallbackHolders == null) {
792                     deadCallbackHolders = new ArrayList<>();
793                 }
794                 deadCallbackHolders.add(holder);
795                 logCallbackException("Removing dead callback in pushEvent", holder, e);
796             } catch (RemoteException e) {
797                 logCallbackException("unexpected exception in pushEvent", holder, e);
798             }
799         }
800         if (deadCallbackHolders != null) {
801             mControllerCallbackHolders.removeAll(deadCallbackHolders);
802         }
803     }
804 
pushSessionDestroyed()805     private void pushSessionDestroyed() {
806         synchronized (mLock) {
807             // This is the only method that may be (and can only be) called
808             // after the session is destroyed.
809             if (!mDestroyed) {
810                 return;
811             }
812         }
813         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
814             try {
815                 holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0);
816                 holder.mCallback.onSessionDestroyed();
817             } catch (NoSuchElementException e) {
818                 logCallbackException("error unlinking to binder death", holder, e);
819             } catch (DeadObjectException e) {
820                 logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
821             } catch (RemoteException e) {
822                 logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
823             }
824         }
825         // After notifying clear all listeners
826         mControllerCallbackHolders.clear();
827     }
828 
getStateWithUpdatedPosition()829     private PlaybackState getStateWithUpdatedPosition() {
830         PlaybackState state;
831         long duration;
832         synchronized (mLock) {
833             if (mDestroyed) {
834                 return null;
835             }
836             state = mPlaybackState;
837             duration = mDuration;
838         }
839         PlaybackState result = null;
840         if (state != null) {
841             if (state.getState() == PlaybackState.STATE_PLAYING
842                     || state.getState() == PlaybackState.STATE_FAST_FORWARDING
843                     || state.getState() == PlaybackState.STATE_REWINDING) {
844                 long updateTime = state.getLastPositionUpdateTime();
845                 long currentTime = SystemClock.elapsedRealtime();
846                 if (updateTime > 0) {
847                     long position = (long) (state.getPlaybackSpeed()
848                             * (currentTime - updateTime)) + state.getPosition();
849                     if (duration >= 0 && position > duration) {
850                         position = duration;
851                     } else if (position < 0) {
852                         position = 0;
853                     }
854                     PlaybackState.Builder builder = new PlaybackState.Builder(state);
855                     builder.setState(state.getState(), position, state.getPlaybackSpeed(),
856                             currentTime);
857                     result = builder.build();
858                 }
859             }
860         }
861         return result == null ? state : result;
862     }
863 
getControllerHolderIndexForCb(ISessionControllerCallback cb)864     private int getControllerHolderIndexForCb(ISessionControllerCallback cb) {
865         IBinder binder = cb.asBinder();
866         for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
867             if (binder.equals(mControllerCallbackHolders.get(i).mCallback.asBinder())) {
868                 return i;
869             }
870         }
871         return -1;
872     }
873 
getVolumeAttributes()874     private PlaybackInfo getVolumeAttributes() {
875         int volumeType;
876         AudioAttributes attributes;
877         synchronized (mLock) {
878             if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
879                 int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
880                 return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
881                         mAudioAttrs, mVolumeControlId);
882             }
883             volumeType = mVolumeType;
884             attributes = mAudioAttrs;
885         }
886         int stream = getVolumeStream(attributes);
887         int max = mAudioManager.getStreamMaxVolume(stream);
888         int current = mAudioManager.getStreamVolume(stream);
889         return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
890                 current, attributes, null);
891     }
892 
893     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
894         @Override
895         public void run() {
896             boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
897             mOptimisticVolume = -1;
898             if (needUpdate) {
899                 pushVolumeUpdate();
900             }
901         }
902     };
903 
904     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
componentNameExists( @onNull ComponentName componentName, @NonNull Context context, int userId)905     private static boolean componentNameExists(
906             @NonNull ComponentName componentName, @NonNull Context context, int userId) {
907         Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
908         mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
909         mediaButtonIntent.setComponent(componentName);
910 
911         UserHandle userHandle = UserHandle.of(userId);
912         PackageManager pm = context.getPackageManager();
913 
914         List<ResolveInfo> resolveInfos =
915                 pm.queryBroadcastReceiversAsUser(
916                         mediaButtonIntent, /* flags */ 0, userHandle);
917         return !resolveInfos.isEmpty();
918     }
919 
920     private final class SessionStub extends ISession.Stub {
921         @Override
destroySession()922         public void destroySession() throws RemoteException {
923             final long token = Binder.clearCallingIdentity();
924             try {
925                 mService.onSessionDied(MediaSessionRecord.this);
926             } finally {
927                 Binder.restoreCallingIdentity(token);
928             }
929         }
930 
931         @Override
sendEvent(String event, Bundle data)932         public void sendEvent(String event, Bundle data) throws RemoteException {
933             mHandler.post(MessageHandler.MSG_SEND_EVENT, event,
934                     data == null ? null : new Bundle(data));
935         }
936 
937         @Override
getController()938         public ISessionController getController() throws RemoteException {
939             return mController;
940         }
941 
942         @Override
setActive(boolean active)943         public void setActive(boolean active) throws RemoteException {
944             mIsActive = active;
945             final long token = Binder.clearCallingIdentity();
946             try {
947                 mService.onSessionActiveStateChanged(MediaSessionRecord.this);
948             } finally {
949                 Binder.restoreCallingIdentity(token);
950             }
951             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
952         }
953 
954         @Override
setFlags(int flags)955         public void setFlags(int flags) throws RemoteException {
956             if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
957                 int pid = Binder.getCallingPid();
958                 int uid = Binder.getCallingUid();
959                 mService.enforcePhoneStatePermission(pid, uid);
960             }
961             mFlags = flags;
962             if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
963                 final long token = Binder.clearCallingIdentity();
964                 try {
965                     mService.setGlobalPrioritySession(MediaSessionRecord.this);
966                 } finally {
967                     Binder.restoreCallingIdentity(token);
968                 }
969             }
970             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
971         }
972 
973         @Override
setMediaButtonReceiver(PendingIntent pi)974         public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
975             final long token = Binder.clearCallingIdentity();
976             try {
977                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
978                         != 0) {
979                     return;
980                 }
981                 mMediaButtonReceiverHolder =
982                         MediaButtonReceiverHolder.create(mUserId, pi, mPackageName);
983                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
984             } finally {
985                 Binder.restoreCallingIdentity(token);
986             }
987         }
988 
989         @Override
990         @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
setMediaButtonBroadcastReceiver(ComponentName receiver)991         public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
992             final long token = Binder.clearCallingIdentity();
993             try {
994                 //mPackageName has been verified in MediaSessionService.enforcePackageName().
995                 if (receiver != null && !TextUtils.equals(
996                         mPackageName, receiver.getPackageName())) {
997                     EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging.
998                     throw new IllegalArgumentException("receiver does not belong to "
999                             + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
1000                             + ", Receiver Pkg = " + receiver.getPackageName());
1001                 }
1002                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
1003                         != 0) {
1004                     return;
1005                 }
1006 
1007                 if (!componentNameExists(receiver, mContext, mUserId)) {
1008                     Log.w(
1009                             TAG,
1010                             "setMediaButtonBroadcastReceiver(): "
1011                                     + "Ignoring invalid component name="
1012                                     + receiver);
1013                     return;
1014                 }
1015 
1016                 mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver);
1017                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
1018             } finally {
1019                 Binder.restoreCallingIdentity(token);
1020             }
1021         }
1022 
1023         @Override
setLaunchPendingIntent(PendingIntent pi)1024         public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException {
1025             mLaunchIntent = pi;
1026         }
1027 
1028         @Override
setMetadata(MediaMetadata metadata, long duration, String metadataDescription)1029         public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription)
1030                 throws RemoteException {
1031             synchronized (mLock) {
1032                 mDuration = duration;
1033                 mMetadataDescription = metadataDescription;
1034                 mMetadata = sanitizeMediaMetadata(metadata);
1035             }
1036             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
1037         }
1038 
sanitizeMediaMetadata(MediaMetadata metadata)1039         private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) {
1040             if (metadata == null) {
1041                 return null;
1042             }
1043             MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata);
1044             for (String key: ART_URIS) {
1045                 String uriString = metadata.getString(key);
1046                 if (TextUtils.isEmpty(uriString)) {
1047                     continue;
1048                 }
1049                 Uri uri = Uri.parse(uriString);
1050                 if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
1051                     continue;
1052                 }
1053                 try {
1054                     mUgmInternal.checkGrantUriPermission(getUid(),
1055                             getPackageName(),
1056                             ContentProvider.getUriWithoutUserId(uri),
1057                             Intent.FLAG_GRANT_READ_URI_PERMISSION,
1058                             ContentProvider.getUserIdFromUri(uri, getUserId()));
1059                 } catch (SecurityException e) {
1060                     metadataBuilder.putString(key, null);
1061                 }
1062             }
1063             MediaMetadata sanitizedMetadata = metadataBuilder.build();
1064             // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled
1065             // before we set it to prevent concurrent reads from throwing an
1066             // exception
1067             sanitizedMetadata.size();
1068             return sanitizedMetadata;
1069         }
1070 
1071         @Override
setPlaybackState(PlaybackState state)1072         public void setPlaybackState(PlaybackState state) throws RemoteException {
1073             int oldState = mPlaybackState == null
1074                     ? PlaybackState.STATE_NONE : mPlaybackState.getState();
1075             int newState = state == null
1076                     ? PlaybackState.STATE_NONE : state.getState();
1077             boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
1078                     || (!TRANSITION_PRIORITY_STATES.contains(oldState)
1079                     && TRANSITION_PRIORITY_STATES.contains(newState));
1080             synchronized (mLock) {
1081                 mPlaybackState = state;
1082             }
1083             final long token = Binder.clearCallingIdentity();
1084             try {
1085                 mService.onSessionPlaybackStateChanged(
1086                         MediaSessionRecord.this, shouldUpdatePriority);
1087             } finally {
1088                 Binder.restoreCallingIdentity(token);
1089             }
1090             mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
1091         }
1092 
1093         @Override
resetQueue()1094         public void resetQueue() throws RemoteException {
1095             synchronized (mLock) {
1096                 mQueue = null;
1097             }
1098             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
1099         }
1100 
1101         @Override
getBinderForSetQueue()1102         public IBinder getBinderForSetQueue() throws RemoteException {
1103             return new ParcelableListBinder<QueueItem>((list) -> {
1104                 synchronized (mLock) {
1105                     mQueue = list;
1106                 }
1107                 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
1108             });
1109         }
1110 
1111         @Override
setQueueTitle(CharSequence title)1112         public void setQueueTitle(CharSequence title) throws RemoteException {
1113             mQueueTitle = title;
1114             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
1115         }
1116 
1117         @Override
setExtras(Bundle extras)1118         public void setExtras(Bundle extras) throws RemoteException {
1119             synchronized (mLock) {
1120                 mExtras = extras == null ? null : new Bundle(extras);
1121             }
1122             mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS);
1123         }
1124 
1125         @Override
setRatingType(int type)1126         public void setRatingType(int type) throws RemoteException {
1127             mRatingType = type;
1128         }
1129 
1130         @Override
setCurrentVolume(int volume)1131         public void setCurrentVolume(int volume) throws RemoteException {
1132             mCurrentVolume = volume;
1133             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
1134         }
1135 
1136         @Override
setPlaybackToLocal(AudioAttributes attributes)1137         public void setPlaybackToLocal(AudioAttributes attributes) throws RemoteException {
1138             boolean typeChanged;
1139             synchronized (mLock) {
1140                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1141                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1142                 mVolumeControlId = null;
1143                 if (attributes != null) {
1144                     mAudioAttrs = attributes;
1145                 } else {
1146                     Log.e(TAG, "Received null audio attributes, using existing attributes");
1147                 }
1148             }
1149             if (typeChanged) {
1150                 final long token = Binder.clearCallingIdentity();
1151                 try {
1152                     mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
1153                 } finally {
1154                     Binder.restoreCallingIdentity(token);
1155                 }
1156                 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
1157             }
1158         }
1159 
1160         @Override
setPlaybackToRemote(int control, int max, String controlId)1161         public void setPlaybackToRemote(int control, int max, String controlId)
1162                 throws RemoteException {
1163             boolean typeChanged;
1164             synchronized (mLock) {
1165                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
1166                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
1167                 mVolumeControlType = control;
1168                 mMaxVolume = max;
1169                 mVolumeControlId = controlId;
1170             }
1171             if (typeChanged) {
1172                 final long token = Binder.clearCallingIdentity();
1173                 try {
1174                     mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
1175                 } finally {
1176                     Binder.restoreCallingIdentity(token);
1177                 }
1178                 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
1179             }
1180         }
1181     }
1182 
1183     class SessionCb {
1184         private final ISessionCallback mCb;
1185 
1186         SessionCb(ISessionCallback cb) {
1187             mCb = cb;
1188         }
1189 
1190         public boolean sendMediaButton(String packageName, int pid, int uid,
1191                 boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
1192             try {
1193                 if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
1194                     final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
1195                             + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
1196                     mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1197                             pid, uid, packageName, reason);
1198                 }
1199                 if (asSystemService) {
1200                     mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
1201                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
1202                 } else {
1203                     mCb.onMediaButton(packageName, pid, uid,
1204                             createMediaButtonIntent(keyEvent), sequenceId, cb);
1205                 }
1206                 return true;
1207             } catch (RemoteException e) {
1208                 Log.e(TAG, "Remote failure in sendMediaRequest.", e);
1209             }
1210             return false;
1211         }
1212 
1213         public boolean sendMediaButton(String packageName, int pid, int uid,
1214                 boolean asSystemService, KeyEvent keyEvent) {
1215             try {
1216                 if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
1217                     final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
1218                             + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
1219                     mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1220                             pid, uid, packageName, reason);
1221                 }
1222                 if (asSystemService) {
1223                     mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
1224                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
1225                 } else {
1226                     mCb.onMediaButtonFromController(packageName, pid, uid,
1227                             createMediaButtonIntent(keyEvent));
1228                 }
1229                 return true;
1230             } catch (RemoteException e) {
1231                 Log.e(TAG, "Remote failure in sendMediaRequest.", e);
1232             }
1233             return false;
1234         }
1235 
1236         public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
1237                 ResultReceiver cb) {
1238             try {
1239                 final String reason = TAG + ":" + command;
1240                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1241                         pid, uid, packageName, reason);
1242                 mCb.onCommand(packageName, pid, uid, command, args, cb);
1243             } catch (RemoteException e) {
1244                 Log.e(TAG, "Remote failure in sendCommand.", e);
1245             }
1246         }
1247 
1248         public void sendCustomAction(String packageName, int pid, int uid, String action,
1249                 Bundle args) {
1250             try {
1251                 final String reason = TAG + ":custom-" + action;
1252                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1253                         pid, uid, packageName, reason);
1254                 mCb.onCustomAction(packageName, pid, uid, action, args);
1255             } catch (RemoteException e) {
1256                 Log.e(TAG, "Remote failure in sendCustomAction.", e);
1257             }
1258         }
1259 
1260         public void prepare(String packageName, int pid, int uid) {
1261             try {
1262                 final String reason = TAG + ":prepare";
1263                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1264                         pid, uid, packageName, reason);
1265                 mCb.onPrepare(packageName, pid, uid);
1266             } catch (RemoteException e) {
1267                 Log.e(TAG, "Remote failure in prepare.", e);
1268             }
1269         }
1270 
1271         public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId,
1272                 Bundle extras) {
1273             try {
1274                 final String reason = TAG + ":prepareFromMediaId";
1275                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1276                         pid, uid, packageName, reason);
1277                 mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
1278             } catch (RemoteException e) {
1279                 Log.e(TAG, "Remote failure in prepareFromMediaId.", e);
1280             }
1281         }
1282 
1283         public void prepareFromSearch(String packageName, int pid, int uid, String query,
1284                 Bundle extras) {
1285             try {
1286                 final String reason = TAG + ":prepareFromSearch";
1287                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1288                         pid, uid, packageName, reason);
1289                 mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
1290             } catch (RemoteException e) {
1291                 Log.e(TAG, "Remote failure in prepareFromSearch.", e);
1292             }
1293         }
1294 
1295         public void prepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
1296             try {
1297                 final String reason = TAG + ":prepareFromUri";
1298                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1299                         pid, uid, packageName, reason);
1300                 mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
1301             } catch (RemoteException e) {
1302                 Log.e(TAG, "Remote failure in prepareFromUri.", e);
1303             }
1304         }
1305 
1306         public void play(String packageName, int pid, int uid) {
1307             try {
1308                 final String reason = TAG + ":play";
1309                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1310                         pid, uid, packageName, reason);
1311                 mCb.onPlay(packageName, pid, uid);
1312             } catch (RemoteException e) {
1313                 Log.e(TAG, "Remote failure in play.", e);
1314             }
1315         }
1316 
1317         public void playFromMediaId(String packageName, int pid, int uid, String mediaId,
1318                 Bundle extras) {
1319             try {
1320                 final String reason = TAG + ":playFromMediaId";
1321                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1322                         pid, uid, packageName, reason);
1323                 mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
1324             } catch (RemoteException e) {
1325                 Log.e(TAG, "Remote failure in playFromMediaId.", e);
1326             }
1327         }
1328 
1329         public void playFromSearch(String packageName, int pid, int uid, String query,
1330                 Bundle extras) {
1331             try {
1332                 final String reason = TAG + ":playFromSearch";
1333                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1334                         pid, uid, packageName, reason);
1335                 mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
1336             } catch (RemoteException e) {
1337                 Log.e(TAG, "Remote failure in playFromSearch.", e);
1338             }
1339         }
1340 
1341         public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
1342             try {
1343                 final String reason = TAG + ":playFromUri";
1344                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1345                         pid, uid, packageName, reason);
1346                 mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
1347             } catch (RemoteException e) {
1348                 Log.e(TAG, "Remote failure in playFromUri.", e);
1349             }
1350         }
1351 
1352         public void skipToTrack(String packageName, int pid, int uid, long id) {
1353             try {
1354                 final String reason = TAG + ":skipToTrack";
1355                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1356                         pid, uid, packageName, reason);
1357                 mCb.onSkipToTrack(packageName, pid, uid, id);
1358             } catch (RemoteException e) {
1359                 Log.e(TAG, "Remote failure in skipToTrack", e);
1360             }
1361         }
1362 
1363         public void pause(String packageName, int pid, int uid) {
1364             try {
1365                 final String reason = TAG + ":pause";
1366                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1367                         pid, uid, packageName, reason);
1368                 mCb.onPause(packageName, pid, uid);
1369             } catch (RemoteException e) {
1370                 Log.e(TAG, "Remote failure in pause.", e);
1371             }
1372         }
1373 
1374         public void stop(String packageName, int pid, int uid) {
1375             try {
1376                 final String reason = TAG + ":stop";
1377                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1378                         pid, uid, packageName, reason);
1379                 mCb.onStop(packageName, pid, uid);
1380             } catch (RemoteException e) {
1381                 Log.e(TAG, "Remote failure in stop.", e);
1382             }
1383         }
1384 
1385         public void next(String packageName, int pid, int uid) {
1386             try {
1387                 final String reason = TAG + ":next";
1388                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1389                         pid, uid, packageName, reason);
1390                 mCb.onNext(packageName, pid, uid);
1391             } catch (RemoteException e) {
1392                 Log.e(TAG, "Remote failure in next.", e);
1393             }
1394         }
1395 
1396         public void previous(String packageName, int pid, int uid) {
1397             try {
1398                 final String reason = TAG + ":previous";
1399                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1400                         pid, uid, packageName, reason);
1401                 mCb.onPrevious(packageName, pid, uid);
1402             } catch (RemoteException e) {
1403                 Log.e(TAG, "Remote failure in previous.", e);
1404             }
1405         }
1406 
1407         public void fastForward(String packageName, int pid, int uid) {
1408             try {
1409                 final String reason = TAG + ":fastForward";
1410                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1411                         pid, uid, packageName, reason);
1412                 mCb.onFastForward(packageName, pid, uid);
1413             } catch (RemoteException e) {
1414                 Log.e(TAG, "Remote failure in fastForward.", e);
1415             }
1416         }
1417 
1418         public void rewind(String packageName, int pid, int uid) {
1419             try {
1420                 final String reason = TAG + ":rewind";
1421                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1422                         pid, uid, packageName, reason);
1423                 mCb.onRewind(packageName, pid, uid);
1424             } catch (RemoteException e) {
1425                 Log.e(TAG, "Remote failure in rewind.", e);
1426             }
1427         }
1428 
1429         public void seekTo(String packageName, int pid, int uid, long pos) {
1430             try {
1431                 final String reason = TAG + ":seekTo";
1432                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1433                         pid, uid, packageName, reason);
1434                 mCb.onSeekTo(packageName, pid, uid, pos);
1435             } catch (RemoteException e) {
1436                 Log.e(TAG, "Remote failure in seekTo.", e);
1437             }
1438         }
1439 
1440         public void rate(String packageName, int pid, int uid, Rating rating) {
1441             try {
1442                 final String reason = TAG + ":rate";
1443                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1444                         pid, uid, packageName, reason);
1445                 mCb.onRate(packageName, pid, uid, rating);
1446             } catch (RemoteException e) {
1447                 Log.e(TAG, "Remote failure in rate.", e);
1448             }
1449         }
1450 
1451         public void setPlaybackSpeed(String packageName, int pid, int uid, float speed) {
1452             try {
1453                 final String reason = TAG + ":setPlaybackSpeed";
1454                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1455                         pid, uid, packageName, reason);
1456                 mCb.onSetPlaybackSpeed(packageName, pid, uid, speed);
1457             } catch (RemoteException e) {
1458                 Log.e(TAG, "Remote failure in setPlaybackSpeed.", e);
1459             }
1460         }
1461 
1462         public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService,
1463                 int direction) {
1464             try {
1465                 final String reason = TAG + ":adjustVolume";
1466                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1467                         pid, uid, packageName, reason);
1468                 if (asSystemService) {
1469                     mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
1470                             Process.SYSTEM_UID, direction);
1471                 } else {
1472                     mCb.onAdjustVolume(packageName, pid, uid, direction);
1473                 }
1474             } catch (RemoteException e) {
1475                 Log.e(TAG, "Remote failure in adjustVolume.", e);
1476             }
1477         }
1478 
1479         public void setVolumeTo(String packageName, int pid, int uid, int value) {
1480             try {
1481                 final String reason = TAG + ":setVolumeTo";
1482                 mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
1483                         pid, uid, packageName, reason);
1484                 mCb.onSetVolumeTo(packageName, pid, uid, value);
1485             } catch (RemoteException e) {
1486                 Log.e(TAG, "Remote failure in setVolumeTo.", e);
1487             }
1488         }
1489 
1490         private Intent createMediaButtonIntent(KeyEvent keyEvent) {
1491             Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1492             mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1493             return mediaButtonIntent;
1494         }
1495     }
1496 
1497     class ControllerStub extends ISessionController.Stub {
1498         @Override
1499         public void sendCommand(String packageName, String command, Bundle args,
1500                 ResultReceiver cb) {
1501             mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1502                     command, args, cb);
1503         }
1504 
1505         @Override
1506         public boolean sendMediaButton(String packageName, KeyEvent keyEvent) {
1507             return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(),
1508                     Binder.getCallingUid(), false, keyEvent);
1509         }
1510 
1511         @Override
1512         public void registerCallback(String packageName, ISessionControllerCallback cb) {
1513             synchronized (mLock) {
1514                 // If this session is already destroyed tell the caller and
1515                 // don't add them.
1516                 if (mDestroyed) {
1517                     try {
1518                         cb.onSessionDestroyed();
1519                     } catch (Exception e) {
1520                         // ignored
1521                     }
1522                     return;
1523                 }
1524                 if (getControllerHolderIndexForCb(cb) < 0) {
1525                     ISessionControllerCallbackHolder holder = new ISessionControllerCallbackHolder(
1526                         cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb));
1527                     mControllerCallbackHolders.add(holder);
1528                     if (DEBUG) {
1529                         Log.d(TAG, "registering controller callback " + cb + " from controller"
1530                                 + packageName);
1531                     }
1532                     // Avoid callback leaks
1533                     try {
1534                         // cb is not referenced outside of the MediaSessionRecord, so the death
1535                         // handler won't prevent MediaSessionRecord to be garbage collected.
1536                         cb.asBinder().linkToDeath(holder.mDeathMonitor, 0);
1537                     } catch (RemoteException e) {
1538                         unregisterCallback(cb);
1539                         Log.w(TAG, "registerCallback failed to linkToDeath", e);
1540                     }
1541                 }
1542             }
1543         }
1544 
1545         @Override
1546         public void unregisterCallback(ISessionControllerCallback cb) {
1547             synchronized (mLock) {
1548                 int index = getControllerHolderIndexForCb(cb);
1549                 if (index != -1) {
1550                     try {
1551                         cb.asBinder().unlinkToDeath(
1552                           mControllerCallbackHolders.get(index).mDeathMonitor, 0);
1553                     } catch (NoSuchElementException e) {
1554                         Log.w(TAG, "error unlinking to binder death", e);
1555                     }
1556                     mControllerCallbackHolders.remove(index);
1557                 }
1558                 if (DEBUG) {
1559                     Log.d(TAG, "unregistering callback " + cb.asBinder());
1560                 }
1561             }
1562         }
1563 
1564         @Override
1565         public String getPackageName() {
1566             return mPackageName;
1567         }
1568 
1569         @Override
1570         public String getTag() {
1571             return mTag;
1572         }
1573 
1574         @Override
1575         public Bundle getSessionInfo() {
1576             return mSessionInfo;
1577         }
1578 
1579         @Override
1580         public PendingIntent getLaunchPendingIntent() {
1581             return mLaunchIntent;
1582         }
1583 
1584         @Override
1585         public long getFlags() {
1586             return mFlags;
1587         }
1588 
1589         @Override
1590         public PlaybackInfo getVolumeAttributes() {
1591             return MediaSessionRecord.this.getVolumeAttributes();
1592         }
1593 
1594         @Override
1595         public void adjustVolume(String packageName, String opPackageName, int direction,
1596                 int flags) {
1597             int pid = Binder.getCallingPid();
1598             int uid = Binder.getCallingUid();
1599             final long token = Binder.clearCallingIdentity();
1600             try {
1601                 MediaSessionRecord.this.adjustVolume(packageName, opPackageName, pid, uid,
1602                         false, direction, flags, false /* useSuggested */);
1603             } finally {
1604                 Binder.restoreCallingIdentity(token);
1605             }
1606         }
1607 
1608         @Override
1609         public void setVolumeTo(String packageName, String opPackageName, int value, int flags) {
1610             int pid = Binder.getCallingPid();
1611             int uid = Binder.getCallingUid();
1612             final long token = Binder.clearCallingIdentity();
1613             try {
1614                 MediaSessionRecord.this.setVolumeTo(packageName, opPackageName, pid, uid, value,
1615                         flags);
1616             } finally {
1617                 Binder.restoreCallingIdentity(token);
1618             }
1619         }
1620 
1621         @Override
1622         public void prepare(String packageName) {
1623             mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1624         }
1625 
1626         @Override
1627         public void prepareFromMediaId(String packageName, String mediaId, Bundle extras) {
1628             mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(),
1629                     Binder.getCallingUid(), mediaId, extras);
1630         }
1631 
1632         @Override
1633         public void prepareFromSearch(String packageName, String query, Bundle extras) {
1634             mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(),
1635                     Binder.getCallingUid(), query, extras);
1636         }
1637 
1638         @Override
1639         public void prepareFromUri(String packageName, Uri uri, Bundle extras) {
1640             mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1641                     uri, extras);
1642         }
1643 
1644         @Override
1645         public void play(String packageName) {
1646             mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1647         }
1648 
1649         @Override
1650         public void playFromMediaId(String packageName, String mediaId, Bundle extras) {
1651             mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1652                     mediaId, extras);
1653         }
1654 
1655         @Override
1656         public void playFromSearch(String packageName, String query, Bundle extras) {
1657             mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1658                      query, extras);
1659         }
1660 
1661         @Override
1662         public void playFromUri(String packageName, Uri uri, Bundle extras) {
1663             mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1664                     uri, extras);
1665         }
1666 
1667         @Override
1668         public void skipToQueueItem(String packageName, long id) {
1669             mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(), id);
1670         }
1671 
1672         @Override
1673         public void pause(String packageName) {
1674             mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1675         }
1676 
1677         @Override
1678         public void stop(String packageName) {
1679             mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1680         }
1681 
1682         @Override
1683         public void next(String packageName) {
1684             mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1685         }
1686 
1687         @Override
1688         public void previous(String packageName) {
1689             mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1690         }
1691 
1692         @Override
1693         public void fastForward(String packageName) {
1694             mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1695         }
1696 
1697         @Override
1698         public void rewind(String packageName) {
1699             mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid());
1700         }
1701 
1702         @Override
1703         public void seekTo(String packageName, long pos) {
1704             mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), pos);
1705         }
1706 
1707         @Override
1708         public void rate(String packageName, Rating rating) {
1709             mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), rating);
1710         }
1711 
1712         @Override
1713         public void setPlaybackSpeed(String packageName,
1714                 float speed) {
1715             mSessionCb.setPlaybackSpeed(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1716                     speed);
1717         }
1718 
1719         @Override
1720         public void sendCustomAction(String packageName, String action, Bundle args) {
1721             mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
1722                     action, args);
1723         }
1724 
1725         @Override
1726         public MediaMetadata getMetadata() {
1727             synchronized (mLock) {
1728                 return mMetadata;
1729             }
1730         }
1731 
1732         @Override
1733         public PlaybackState getPlaybackState() {
1734             return getStateWithUpdatedPosition();
1735         }
1736 
1737         @Override
1738         public ParceledListSlice getQueue() {
1739             synchronized (mLock) {
1740                 return mQueue == null ? null : new ParceledListSlice<>(mQueue);
1741             }
1742         }
1743 
1744         @Override
1745         public CharSequence getQueueTitle() {
1746             return mQueueTitle;
1747         }
1748 
1749         @Override
1750         public Bundle getExtras() {
1751             synchronized (mLock) {
1752                 return mExtras;
1753             }
1754         }
1755 
1756         @Override
1757         public int getRatingType() {
1758             return mRatingType;
1759         }
1760     }
1761 
1762     private class ISessionControllerCallbackHolder {
1763         private final ISessionControllerCallback mCallback;
1764         private final String mPackageName;
1765         private final int mUid;
1766         private final IBinder.DeathRecipient mDeathMonitor;
1767 
1768         ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
1769                 int uid, IBinder.DeathRecipient deathMonitor) {
1770             mCallback = callback;
1771             mPackageName = packageName;
1772             mUid = uid;
1773             mDeathMonitor = deathMonitor;
1774         }
1775     }
1776 
1777     private class MessageHandler extends Handler {
1778         private static final int MSG_UPDATE_METADATA = 1;
1779         private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1780         private static final int MSG_UPDATE_QUEUE = 3;
1781         private static final int MSG_UPDATE_QUEUE_TITLE = 4;
1782         private static final int MSG_UPDATE_EXTRAS = 5;
1783         private static final int MSG_SEND_EVENT = 6;
1784         private static final int MSG_UPDATE_SESSION_STATE = 7;
1785         private static final int MSG_UPDATE_VOLUME = 8;
1786         private static final int MSG_DESTROYED = 9;
1787 
1788         public MessageHandler(Looper looper) {
1789             super(looper);
1790         }
1791         @Override
1792         public void handleMessage(Message msg) {
1793             switch (msg.what) {
1794                 case MSG_UPDATE_METADATA:
1795                     pushMetadataUpdate();
1796                     break;
1797                 case MSG_UPDATE_PLAYBACK_STATE:
1798                     pushPlaybackStateUpdate();
1799                     break;
1800                 case MSG_UPDATE_QUEUE:
1801                     pushQueueUpdate();
1802                     break;
1803                 case MSG_UPDATE_QUEUE_TITLE:
1804                     pushQueueTitleUpdate();
1805                     break;
1806                 case MSG_UPDATE_EXTRAS:
1807                     pushExtrasUpdate();
1808                     break;
1809                 case MSG_SEND_EVENT:
1810                     pushEvent((String) msg.obj, msg.getData());
1811                     break;
1812                 case MSG_UPDATE_SESSION_STATE:
1813                     // TODO add session state
1814                     break;
1815                 case MSG_UPDATE_VOLUME:
1816                     pushVolumeUpdate();
1817                     break;
1818                 case MSG_DESTROYED:
1819                     pushSessionDestroyed();
1820             }
1821         }
1822 
1823         public void post(int what) {
1824             post(what, null);
1825         }
1826 
1827         public void post(int what, Object obj) {
1828             obtainMessage(what, obj).sendToTarget();
1829         }
1830 
1831         public void post(int what, Object obj, Bundle data) {
1832             Message msg = obtainMessage(what, obj);
1833             msg.setData(data);
1834             msg.sendToTarget();
1835         }
1836     }
1837 }
1838