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 android.media.session;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.annotation.SystemService;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.pm.ParceledListSlice;
29 import android.media.AudioManager;
30 import android.media.IRemoteVolumeController;
31 import android.media.MediaSession2;
32 import android.media.Session2Token;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.os.ResultReceiver;
38 import android.os.ServiceManager;
39 import android.os.UserHandle;
40 import android.service.media.MediaBrowserService;
41 import android.service.notification.NotificationListenerService;
42 import android.text.TextUtils;
43 import android.util.ArrayMap;
44 import android.util.Log;
45 import android.view.KeyEvent;
46 
47 import com.android.internal.annotations.GuardedBy;
48 
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Objects;
54 import java.util.concurrent.Executor;
55 
56 /**
57  * Provides support for interacting with {@link MediaSession media sessions}
58  * that applications have published to express their ongoing media playback
59  * state.
60  *
61  * @see MediaSession
62  * @see MediaController
63  */
64 @SystemService(Context.MEDIA_SESSION_SERVICE)
65 public final class MediaSessionManager {
66     private static final String TAG = "SessionManager";
67 
68     /**
69      * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
70      * @hide
71      */
72     public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
73 
74     /**
75      * Used by IOnMediaKeyListener to indicate that the media key event is handled.
76      * @hide
77      */
78     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
79     private final ISessionManager mService;
80     private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub =
81             new OnMediaKeyEventDispatchedListenerStub();
82     private final OnMediaKeyEventSessionChangedListenerStub
83             mOnMediaKeyEventSessionChangedListenerStub =
84             new OnMediaKeyEventSessionChangedListenerStub();
85 
86     private final Object mLock = new Object();
87     @GuardedBy("mLock")
88     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners =
89             new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
90     @GuardedBy("mLock")
91     private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
92             mSession2TokensListeners = new ArrayMap<>();
93     @GuardedBy("mLock")
94     private final Map<OnMediaKeyEventDispatchedListener, Executor>
95             mOnMediaKeyEventDispatchedListeners = new HashMap<>();
96     @GuardedBy("mLock")
97     private final Map<OnMediaKeyEventSessionChangedListener, Executor>
98             mMediaKeyEventSessionChangedCallbacks = new HashMap<>();
99     @GuardedBy("mLock")
100     private String mCurMediaKeyEventSessionPackage;
101     @GuardedBy("mLock")
102     private MediaSession.Token mCurMediaKeyEventSession;
103 
104     private Context mContext;
105     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
106     private OnMediaKeyListenerImpl mOnMediaKeyListener;
107 
108     /**
109      * @hide
110      */
MediaSessionManager(Context context)111     public MediaSessionManager(Context context) {
112         // Consider rewriting like DisplayManagerGlobal
113         // Decide if we need context
114         mContext = context;
115         IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
116         mService = ISessionManager.Stub.asInterface(b);
117     }
118 
119     /**
120      * Create a new session in the system and get the binder for it.
121      *
122      * @param tag A short name for debugging purposes.
123      * @param sessionInfo A bundle for additional information about this session.
124      * @return The binder object from the system
125      * @hide
126      */
127     @NonNull
createSession(@onNull MediaSession.CallbackStub cbStub, @NonNull String tag, @Nullable Bundle sessionInfo)128     public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag,
129             @Nullable Bundle sessionInfo) {
130         try {
131             return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
132                     UserHandle.myUserId());
133         } catch (RemoteException e) {
134             throw new RuntimeException(e);
135         }
136     }
137 
138     /**
139      * This API is not generally intended for third party application developers.
140      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
141      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
142      * Library</a> for consistent behavior across all devices.
143      * <p>
144      * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
145      * created.
146      * <p>
147      * Do not use this API directly, but create a new instance through the
148      * {@link MediaSession2.Builder} instead.
149      *
150      * @param token newly created session2 token
151      */
notifySession2Created(@onNull Session2Token token)152     public void notifySession2Created(@NonNull Session2Token token) {
153         if (token == null) {
154             throw new IllegalArgumentException("token shouldn't be null");
155         }
156         if (token.getType() != Session2Token.TYPE_SESSION) {
157             throw new IllegalArgumentException("token's type should be TYPE_SESSION");
158         }
159         try {
160             mService.notifySession2Created(token);
161         } catch (RemoteException e) {
162             e.rethrowFromSystemServer();
163         }
164     }
165 
166     /**
167      * Get a list of controllers for all ongoing sessions. The controllers will
168      * be provided in priority order with the most important controller at index
169      * 0.
170      * <p>
171      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
172      * permission be held by the calling app. You may also retrieve this list if
173      * your app is an enabled notification listener using the
174      * {@link NotificationListenerService} APIs, in which case you must pass the
175      * {@link ComponentName} of your enabled listener.
176      *
177      * @param notificationListener The enabled notification listener component.
178      *            May be null.
179      * @return A list of controllers for ongoing sessions.
180      */
getActiveSessions( @ullable ComponentName notificationListener)181     public @NonNull List<MediaController> getActiveSessions(
182             @Nullable ComponentName notificationListener) {
183         return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
184     }
185 
186     /**
187      * Get active sessions for a specific user. To retrieve actions for a user
188      * other than your own you must hold the
189      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
190      * in addition to any other requirements. If you are an enabled notification
191      * listener you may only get sessions for the users you are enabled for.
192      *
193      * @param notificationListener The enabled notification listener component.
194      *            May be null.
195      * @param userId The user id to fetch sessions for.
196      * @return A list of controllers for ongoing sessions.
197      * @hide
198      */
199     @UnsupportedAppUsage
getActiveSessionsForUser( @ullable ComponentName notificationListener, int userId)200     public @NonNull List<MediaController> getActiveSessionsForUser(
201             @Nullable ComponentName notificationListener, int userId) {
202         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
203         try {
204             List<MediaSession.Token> tokens = mService.getSessions(notificationListener, userId);
205             int size = tokens.size();
206             for (int i = 0; i < size; i++) {
207                 MediaController controller = new MediaController(mContext, tokens.get(i));
208                 controllers.add(controller);
209             }
210         } catch (RemoteException e) {
211             Log.e(TAG, "Failed to get active sessions: ", e);
212         }
213         return controllers;
214     }
215 
216     /**
217      * This API is not generally intended for third party application developers.
218      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
219      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
220      * Library</a> for consistent behavior across all devices.
221      * <p>
222      * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
223      * current user.
224      * <p>
225      * Although this API can be used without any restriction, each session owners can accept or
226      * reject your uses of {@link MediaSession2}.
227      *
228      * @return A list of {@link Session2Token}.
229      */
230     @NonNull
getSession2Tokens()231     public List<Session2Token> getSession2Tokens() {
232         return getSession2Tokens(UserHandle.myUserId());
233     }
234 
235     /**
236      * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
237      * given user.
238      * <p>
239      * If you want to get tokens for another user, you must hold the
240      * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL permission.
241      *
242      * @param userId The user id to fetch sessions for.
243      * @return A list of {@link Session2Token}
244      * @hide
245      */
246     @NonNull
getSession2Tokens(int userId)247     public List<Session2Token> getSession2Tokens(int userId) {
248         try {
249             ParceledListSlice slice = mService.getSession2Tokens(userId);
250             return slice == null ? new ArrayList<>() : slice.getList();
251         } catch (RemoteException e) {
252             Log.e(TAG, "Failed to get session tokens", e);
253         }
254         return new ArrayList<>();
255     }
256 
257     /**
258      * Add a listener to be notified when the list of active sessions
259      * changes.This requires the
260      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
261      * the calling app. You may also retrieve this list if your app is an
262      * enabled notification listener using the
263      * {@link NotificationListenerService} APIs, in which case you must pass the
264      * {@link ComponentName} of your enabled listener. Updates will be posted to
265      * the thread that registered the listener.
266      *
267      * @param sessionListener The listener to add.
268      * @param notificationListener The enabled notification listener component.
269      *            May be null.
270      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener)271     public void addOnActiveSessionsChangedListener(
272             @NonNull OnActiveSessionsChangedListener sessionListener,
273             @Nullable ComponentName notificationListener) {
274         addOnActiveSessionsChangedListener(sessionListener, notificationListener, null);
275     }
276 
277     /**
278      * Add a listener to be notified when the list of active sessions
279      * changes.This requires the
280      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
281      * the calling app. You may also retrieve this list if your app is an
282      * enabled notification listener using the
283      * {@link NotificationListenerService} APIs, in which case you must pass the
284      * {@link ComponentName} of your enabled listener. Updates will be posted to
285      * the handler specified or to the caller's thread if the handler is null.
286      *
287      * @param sessionListener The listener to add.
288      * @param notificationListener The enabled notification listener component.
289      *            May be null.
290      * @param handler The handler to post events to.
291      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler)292     public void addOnActiveSessionsChangedListener(
293             @NonNull OnActiveSessionsChangedListener sessionListener,
294             @Nullable ComponentName notificationListener, @Nullable Handler handler) {
295         addOnActiveSessionsChangedListener(sessionListener, notificationListener,
296                 UserHandle.myUserId(), handler);
297     }
298 
299     /**
300      * Add a listener to be notified when the list of active sessions
301      * changes.This requires the
302      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
303      * the calling app. You may also retrieve this list if your app is an
304      * enabled notification listener using the
305      * {@link NotificationListenerService} APIs, in which case you must pass the
306      * {@link ComponentName} of your enabled listener.
307      *
308      * @param sessionListener The listener to add.
309      * @param notificationListener The enabled notification listener component.
310      *            May be null.
311      * @param userId The userId to listen for changes on.
312      * @param handler The handler to post updates on.
313      * @hide
314      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler)315     public void addOnActiveSessionsChangedListener(
316             @NonNull OnActiveSessionsChangedListener sessionListener,
317             @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
318         if (sessionListener == null) {
319             throw new IllegalArgumentException("listener may not be null");
320         }
321         if (handler == null) {
322             handler = new Handler();
323         }
324         synchronized (mLock) {
325             if (mListeners.get(sessionListener) != null) {
326                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
327                 return;
328             }
329             SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
330                     handler);
331             try {
332                 mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
333                 mListeners.put(sessionListener, wrapper);
334             } catch (RemoteException e) {
335                 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
336             }
337         }
338     }
339 
340     /**
341      * Stop receiving active sessions updates on the specified listener.
342      *
343      * @param listener The listener to remove.
344      */
removeOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener listener)345     public void removeOnActiveSessionsChangedListener(
346             @NonNull OnActiveSessionsChangedListener listener) {
347         if (listener == null) {
348             throw new IllegalArgumentException("listener may not be null");
349         }
350         synchronized (mLock) {
351             SessionsChangedWrapper wrapper = mListeners.remove(listener);
352             if (wrapper != null) {
353                 try {
354                     mService.removeSessionsListener(wrapper.mStub);
355                 } catch (RemoteException e) {
356                     Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e);
357                 } finally {
358                     wrapper.release();
359                 }
360             }
361         }
362     }
363 
364     /**
365      * This API is not generally intended for third party application developers.
366      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
367      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
368      * Library</a> for consistent behavior across all devices.
369      * <p>
370      * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
371      *
372      * @param listener The listener to add
373      */
addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)374     public void addOnSession2TokensChangedListener(
375             @NonNull OnSession2TokensChangedListener listener) {
376         addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler());
377     }
378 
379     /**
380      * This API is not generally intended for third party application developers.
381      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
382      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
383      * Library</a> for consistent behavior across all devices.
384      * <p>
385      * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
386      *
387      * @param listener The listener to add
388      * @param handler The handler to call listener on.
389      */
addOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener, @NonNull Handler handler)390     public void addOnSession2TokensChangedListener(
391             @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) {
392         addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler);
393     }
394 
395     /**
396      * This API is not generally intended for third party application developers.
397      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
398      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
399      * Library</a> for consistent behavior across all devices.
400      * <p>
401      * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
402      *
403      * @param userId The userId to listen for changes on
404      * @param listener The listener to add
405      * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
406      *                be used.
407      * @hide
408      */
addOnSession2TokensChangedListener(int userId, @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler)409     public void addOnSession2TokensChangedListener(int userId,
410             @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
411         if (listener == null) {
412             throw new IllegalArgumentException("listener shouldn't be null");
413         }
414         synchronized (mLock) {
415             if (mSession2TokensListeners.get(listener) != null) {
416                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
417                 return;
418             }
419             Session2TokensChangedWrapper wrapper =
420                     new Session2TokensChangedWrapper(listener, handler);
421             try {
422                 mService.addSession2TokensListener(wrapper.getStub(), userId);
423                 mSession2TokensListeners.put(listener, wrapper);
424             } catch (RemoteException e) {
425                 Log.e(TAG, "Error in addSessionTokensListener.", e);
426                 e.rethrowFromSystemServer();
427             }
428         }
429     }
430 
431     /**
432      * This API is not generally intended for third party application developers.
433      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
434      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
435      * Library</a> for consistent behavior across all devices.
436      * <p>
437      * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
438      *
439      * @param listener The listener to remove.
440      */
removeOnSession2TokensChangedListener( @onNull OnSession2TokensChangedListener listener)441     public void removeOnSession2TokensChangedListener(
442             @NonNull OnSession2TokensChangedListener listener) {
443         if (listener == null) {
444             throw new IllegalArgumentException("listener may not be null");
445         }
446         final Session2TokensChangedWrapper wrapper;
447         synchronized (mLock) {
448             wrapper = mSession2TokensListeners.remove(listener);
449         }
450         if (wrapper != null) {
451             try {
452                 mService.removeSession2TokensListener(wrapper.getStub());
453             } catch (RemoteException e) {
454                 Log.e(TAG, "Error in removeSessionTokensListener.", e);
455                 e.rethrowFromSystemServer();
456             }
457         }
458     }
459 
460     /**
461      * Set the remote volume controller to receive volume updates on.
462      * Only for use by System UI and Settings application.
463      *
464      * @param rvc The volume controller to receive updates on.
465      * @hide
466      */
registerRemoteVolumeController(IRemoteVolumeController rvc)467     public void registerRemoteVolumeController(IRemoteVolumeController rvc) {
468         try {
469             mService.registerRemoteVolumeController(rvc);
470         } catch (RemoteException e) {
471             Log.e(TAG, "Error in registerRemoteVolumeController.", e);
472         }
473     }
474 
475     /**
476      * Unregisters the remote volume controller which was previously registered with
477      * {@link #registerRemoteVolumeController(IRemoteVolumeController)}.
478      * Only for use by System UI and Settings application.
479      *
480      * @param rvc The volume controller which was registered.
481      * @hide
482      */
unregisterRemoteVolumeController(IRemoteVolumeController rvc)483     public void unregisterRemoteVolumeController(IRemoteVolumeController rvc) {
484         try {
485             mService.unregisterRemoteVolumeController(rvc);
486         } catch (RemoteException e) {
487             Log.e(TAG, "Error in unregisterRemoteVolumeController.", e);
488         }
489     }
490 
491     /**
492      * Send a media key event. The receiver will be selected automatically.
493      *
494      * @param keyEvent The KeyEvent to send.
495      * @hide
496      */
dispatchMediaKeyEvent(@onNull KeyEvent keyEvent)497     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
498         dispatchMediaKeyEvent(keyEvent, false);
499     }
500 
501     /**
502      * Send a media key event. The receiver will be selected automatically.
503      *
504      * @param keyEvent The KeyEvent to send.
505      * @param needWakeLock True if a wake lock should be held while sending the key.
506      * @hide
507      */
dispatchMediaKeyEvent(@onNull KeyEvent keyEvent, boolean needWakeLock)508     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
509         dispatchMediaKeyEventInternal(false, keyEvent, needWakeLock);
510     }
511 
512     /**
513      * Send a media key event as system component. The receiver will be selected automatically.
514      * <p>
515      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
516      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
517      * from the hardware devices.
518      *
519      * @param keyEvent The KeyEvent to send.
520      * @hide
521      */
dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent)522     public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) {
523         dispatchMediaKeyEventInternal(true, keyEvent, false);
524     }
525 
dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, boolean needWakeLock)526     private void dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
527             boolean needWakeLock) {
528         try {
529             mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,
530                     needWakeLock);
531         } catch (RemoteException e) {
532             Log.e(TAG, "Failed to send key event.", e);
533         }
534     }
535 
536     /**
537      * Dispatches the media button event as system service to the session.
538      * <p>
539      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the
540      * foreground activity didn't consume the key from the hardware devices.
541      *
542      * @param sessionToken session token
543      * @param keyEvent media key event
544      * @return {@code true} if the event was sent to the session, {@code false} otherwise
545      * @hide
546      */
dispatchMediaKeyEventAsSystemService(@onNull MediaSession.Token sessionToken, @NonNull KeyEvent keyEvent)547     public boolean dispatchMediaKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
548             @NonNull KeyEvent keyEvent) {
549         if (sessionToken == null) {
550             throw new IllegalArgumentException("sessionToken shouldn't be null");
551         }
552         if (keyEvent == null) {
553             throw new IllegalArgumentException("keyEvent shouldn't be null");
554         }
555         if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
556             return false;
557         }
558         try {
559             return mService.dispatchMediaKeyEventToSessionAsSystemService(mContext.getPackageName(),
560                     sessionToken, keyEvent);
561         } catch (RemoteException e) {
562             Log.e(TAG, "Failed to send key event.", e);
563         }
564         return false;
565     }
566 
567     /**
568      * Send a volume key event. The receiver will be selected automatically.
569      *
570      * @param keyEvent The volume KeyEvent to send.
571      * @hide
572      */
dispatchVolumeKeyEvent(@onNull KeyEvent keyEvent, int stream, boolean musicOnly)573     public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
574         dispatchVolumeKeyEventInternal(false, keyEvent, stream, musicOnly);
575     }
576 
577     /**
578      * Dispatches the volume button event as system service to the session. This only effects the
579      * {@link MediaSession.Callback#getCurrentControllerInfo()} and doesn't bypass any permission
580      * check done by the system service.
581      * <p>
582      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
583      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
584      * from the hardware devices.
585      *
586      * @param keyEvent The KeyEvent to send.
587      * @hide
588      */
dispatchVolumeKeyEventAsSystemService(@onNull KeyEvent keyEvent, int streamType)589     public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
590         dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
591     }
592 
dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, int stream, boolean musicOnly)593     private void dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
594             int stream, boolean musicOnly) {
595         try {
596             mService.dispatchVolumeKeyEvent(mContext.getPackageName(), mContext.getOpPackageName(),
597                     asSystemService, keyEvent, stream, musicOnly);
598         } catch (RemoteException e) {
599             Log.e(TAG, "Failed to send volume key event.", e);
600         }
601     }
602 
603     /**
604      * Dispatches the volume key event as system service to the session.
605      * <p>
606      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the
607      * foreground activity didn't consume the key from the hardware devices.
608      *
609      * @param sessionToken sessionToken
610      * @param keyEvent volume key event
611      * @hide
612      */
dispatchVolumeKeyEventAsSystemService(@onNull MediaSession.Token sessionToken, @NonNull KeyEvent keyEvent)613     public void dispatchVolumeKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
614             @NonNull KeyEvent keyEvent) {
615         if (sessionToken == null) {
616             throw new IllegalArgumentException("sessionToken shouldn't be null");
617         }
618         if (keyEvent == null) {
619             throw new IllegalArgumentException("keyEvent shouldn't be null");
620         }
621         try {
622             mService.dispatchVolumeKeyEventToSessionAsSystemService(mContext.getPackageName(),
623                     mContext.getOpPackageName(), sessionToken, keyEvent);
624         } catch (RemoteException e) {
625             Log.wtf(TAG, "Error calling dispatchVolumeKeyEventAsSystemService", e);
626         }
627     }
628 
629     /**
630      * Dispatch an adjust volume request to the system. It will be sent to the
631      * most relevant audio stream or media session. The direction must be one of
632      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
633      * {@link AudioManager#ADJUST_SAME}.
634      *
635      * @param suggestedStream The stream to fall back to if there isn't a
636      *            relevant stream
637      * @param direction The direction to adjust volume in.
638      * @param flags Any flags to include with the volume change.
639      * @hide
640      */
dispatchAdjustVolume(int suggestedStream, int direction, int flags)641     public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
642         try {
643             mService.dispatchAdjustVolume(mContext.getPackageName(), mContext.getOpPackageName(),
644                     suggestedStream, direction, flags);
645         } catch (RemoteException e) {
646             Log.e(TAG, "Failed to send adjust volume.", e);
647         }
648     }
649 
650     /**
651      * Checks whether the remote user is a trusted app.
652      * <p>
653      * An app is trusted if the app holds the android.Manifest.permission.MEDIA_CONTENT_CONTROL
654      * permission or has an enabled notification listener.
655      *
656      * @param userInfo The remote user info from either
657      *            {@link MediaSession#getCurrentControllerInfo()} or
658      *            {@link MediaBrowserService#getCurrentBrowserInfo()}.
659      * @return {@code true} if the remote user is trusted and its package name matches with the UID.
660      *            {@code false} otherwise.
661      */
isTrustedForMediaControl(@onNull RemoteUserInfo userInfo)662     public boolean isTrustedForMediaControl(@NonNull RemoteUserInfo userInfo) {
663         if (userInfo == null) {
664             throw new IllegalArgumentException("userInfo may not be null");
665         }
666         if (userInfo.getPackageName() == null) {
667             return false;
668         }
669         try {
670             return mService.isTrusted(
671                     userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid());
672         } catch (RemoteException e) {
673             Log.wtf(TAG, "Cannot communicate with the service.", e);
674         }
675         return false;
676     }
677 
678     /**
679      * Check if the global priority session is currently active. This can be
680      * used to decide if media keys should be sent to the session or to the app.
681      *
682      * @hide
683      */
isGlobalPriorityActive()684     public boolean isGlobalPriorityActive() {
685         try {
686             return mService.isGlobalPriorityActive();
687         } catch (RemoteException e) {
688             Log.e(TAG, "Failed to check if the global priority is active.", e);
689         }
690         return false;
691     }
692 
693     /**
694      * Set the volume key long-press listener. While the listener is set, the listener
695      * gets the volume key long-presses instead of changing volume.
696      *
697      * <p>System can only have a single volume key long-press listener.
698      *
699      * @param listener The volume key long-press listener. {@code null} to reset.
700      * @param handler The handler on which the listener should be invoked, or {@code null}
701      *            if the listener should be invoked on the calling thread's looper.
702      * @hide
703      */
704     @SystemApi
705     @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER)
setOnVolumeKeyLongPressListener( OnVolumeKeyLongPressListener listener, @Nullable Handler handler)706     public void setOnVolumeKeyLongPressListener(
707             OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
708         synchronized (mLock) {
709             try {
710                 if (listener == null) {
711                     mOnVolumeKeyLongPressListener = null;
712                     mService.setOnVolumeKeyLongPressListener(null);
713                 } else {
714                     if (handler == null) {
715                         handler = new Handler();
716                     }
717                     mOnVolumeKeyLongPressListener =
718                             new OnVolumeKeyLongPressListenerImpl(listener, handler);
719                     mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
720                 }
721             } catch (RemoteException e) {
722                 Log.e(TAG, "Failed to set volume key long press listener", e);
723             }
724         }
725     }
726 
727     /**
728      * Set the media key listener. While the listener is set, the listener
729      * gets the media key before any other media sessions but after the global priority session.
730      * If the listener handles the key (i.e. returns {@code true}),
731      * other sessions will not get the event.
732      *
733      * <p>System can only have a single media key listener.
734      *
735      * @param listener The media key listener. {@code null} to reset.
736      * @param handler The handler on which the listener should be invoked, or {@code null}
737      *            if the listener should be invoked on the calling thread's looper.
738      * @hide
739      */
740     @SystemApi
741     @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER)
setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler)742     public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
743         synchronized (mLock) {
744             try {
745                 if (listener == null) {
746                     mOnMediaKeyListener = null;
747                     mService.setOnMediaKeyListener(null);
748                 } else {
749                     if (handler == null) {
750                         handler = new Handler();
751                     }
752                     mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
753                     mService.setOnMediaKeyListener(mOnMediaKeyListener);
754                 }
755             } catch (RemoteException e) {
756                 Log.e(TAG, "Failed to set media key listener", e);
757             }
758         }
759     }
760 
761     /**
762      * Add a {@link OnMediaKeyEventDispatchedListener}.
763      *
764      * @param executor The executor on which the callback should be invoked
765      * @param listener A {@link OnMediaKeyEventDispatchedListener}.
766      * @hide
767      */
768     @SystemApi
769     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
addOnMediaKeyEventDispatchedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventDispatchedListener listener)770     public void addOnMediaKeyEventDispatchedListener(
771             @NonNull @CallbackExecutor Executor executor,
772             @NonNull OnMediaKeyEventDispatchedListener listener) {
773         if (executor == null) {
774             throw new NullPointerException("executor shouldn't be null");
775         }
776         if (listener == null) {
777             throw new NullPointerException("listener shouldn't be null");
778         }
779         synchronized (mLock) {
780             try {
781                 mOnMediaKeyEventDispatchedListeners.put(listener, executor);
782                 if (mOnMediaKeyEventDispatchedListeners.size() == 1) {
783                     mService.addOnMediaKeyEventDispatchedListener(
784                             mOnMediaKeyEventDispatchedListenerStub);
785                 }
786             } catch (RemoteException e) {
787                 Log.e(TAG, "Failed to set media key listener", e);
788             }
789         }
790     }
791 
792     /**
793      * Remove a {@link OnMediaKeyEventDispatchedListener}.
794      *
795      * @param listener A {@link OnMediaKeyEventDispatchedListener}.
796      * @hide
797      */
798     @SystemApi
799     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
removeOnMediaKeyEventDispatchedListener( @onNull OnMediaKeyEventDispatchedListener listener)800     public void removeOnMediaKeyEventDispatchedListener(
801             @NonNull OnMediaKeyEventDispatchedListener listener) {
802         if (listener == null) {
803             throw new NullPointerException("listener shouldn't be null");
804         }
805         synchronized (mLock) {
806             try {
807                 mOnMediaKeyEventDispatchedListeners.remove(listener);
808                 if (mOnMediaKeyEventDispatchedListeners.size() == 0) {
809                     mService.removeOnMediaKeyEventDispatchedListener(
810                             mOnMediaKeyEventDispatchedListenerStub);
811                 }
812             } catch (RemoteException e) {
813                 Log.e(TAG, "Failed to set media key event dispatched listener", e);
814             }
815         }
816     }
817 
818     /**
819      * Add a {@link OnMediaKeyEventDispatchedListener}.
820      *
821      * @param executor The executor on which the callback should be invoked
822      * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
823      * @hide
824      */
825     @SystemApi
826     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
addOnMediaKeyEventSessionChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnMediaKeyEventSessionChangedListener listener)827     public void addOnMediaKeyEventSessionChangedListener(
828             @NonNull @CallbackExecutor Executor executor,
829             @NonNull OnMediaKeyEventSessionChangedListener listener) {
830         if (executor == null) {
831             throw new NullPointerException("executor shouldn't be null");
832         }
833         if (listener == null) {
834             throw new NullPointerException("listener shouldn't be null");
835         }
836         synchronized (mLock) {
837             try {
838                 mMediaKeyEventSessionChangedCallbacks.put(listener, executor);
839                 executor.execute(
840                         () -> listener.onMediaKeyEventSessionChanged(
841                                 mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession));
842                 if (mMediaKeyEventSessionChangedCallbacks.size() == 1) {
843                     mService.addOnMediaKeyEventSessionChangedListener(
844                             mOnMediaKeyEventSessionChangedListenerStub);
845                 }
846             } catch (RemoteException e) {
847                 Log.e(TAG, "Failed to set media key listener", e);
848             }
849         }
850     }
851 
852     /**
853      * Remove a {@link OnMediaKeyEventSessionChangedListener}.
854      *
855      * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
856      * @hide
857      */
858     @SystemApi
859     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
removeOnMediaKeyEventSessionChangedListener( @onNull OnMediaKeyEventSessionChangedListener listener)860     public void removeOnMediaKeyEventSessionChangedListener(
861             @NonNull OnMediaKeyEventSessionChangedListener listener) {
862         if (listener == null) {
863             throw new NullPointerException("listener shouldn't be null");
864         }
865         synchronized (mLock) {
866             try {
867                 mMediaKeyEventSessionChangedCallbacks.remove(listener);
868                 if (mMediaKeyEventSessionChangedCallbacks.size() == 0) {
869                     mService.removeOnMediaKeyEventSessionChangedListener(
870                             mOnMediaKeyEventSessionChangedListenerStub);
871                 }
872             } catch (RemoteException e) {
873                 Log.e(TAG, "Failed to set media key listener", e);
874             }
875         }
876     }
877 
878     /**
879      * Listens for changes to the list of active sessions. This can be added
880      * using {@link #addOnActiveSessionsChangedListener}.
881      */
882     public interface OnActiveSessionsChangedListener {
onActiveSessionsChanged(@ullable List<MediaController> controllers)883         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
884     }
885 
886     /**
887      * This API is not generally intended for third party application developers.
888      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
889      * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
890      * Library</a> for consistent behavior across all devices.
891      * <p>
892      * Listens for changes to the {@link #getSession2Tokens()}. This can be added
893      * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}.
894      */
895     public interface OnSession2TokensChangedListener {
896         /**
897          * Called when the {@link #getSession2Tokens()} is changed.
898          *
899          * @param tokens list of {@link Session2Token}
900          */
onSession2TokensChanged(@onNull List<Session2Token> tokens)901         void onSession2TokensChanged(@NonNull List<Session2Token> tokens);
902     }
903 
904     /**
905      * Listens the volume key long-presses.
906      * @hide
907      */
908     @SystemApi
909     public interface OnVolumeKeyLongPressListener {
910         /**
911          * Called when the volume key is long-pressed.
912          * <p>This will be called for both down and up events.
913          */
onVolumeKeyLongPress(KeyEvent event)914         void onVolumeKeyLongPress(KeyEvent event);
915     }
916 
917     /**
918      * Listens the media key.
919      * @hide
920      */
921     @SystemApi
922     public interface OnMediaKeyListener {
923         /**
924          * Called when the media key is pressed.
925          * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with
926          * repeat count zero), it must also comsume all following key events.
927          * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP).
928          * <p>If it takes more than 1s to return, the key event will be sent to
929          * other media sessions.
930          */
onMediaKey(KeyEvent event)931         boolean onMediaKey(KeyEvent event);
932     }
933 
934     /**
935      * Listener to receive when the media session service
936      * @hide
937      */
938     @SystemApi
939     public interface OnMediaKeyEventDispatchedListener {
940         /**
941          * Called when a media key event is dispatched through the media session service. The
942          * session token can be {@link null} if the framework has sent the media key event to the
943          * media button receiver to revive the media app's playback.
944          *
945          * the session is dead when , but the framework sent
946          *
947          * @param event Dispatched media key event.
948          * @param packageName Package
949          * @param sessionToken The media session's token. Can be {@code null}.
950          */
onMediaKeyEventDispatched(@onNull KeyEvent event, @NonNull String packageName, @NonNull MediaSession.Token sessionToken)951         default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
952                 @NonNull MediaSession.Token sessionToken) { }
953     }
954 
955     /**
956      * Listener to receive changes in the media key event session, which would receive the media key
957      * event unless specified.
958      * @hide
959      */
960     @SystemApi
961     public interface OnMediaKeyEventSessionChangedListener {
962         /**
963          * Called when the media key session is changed to the given media session. The key event
964          * session is the media session which would receive key event by default, unless the caller
965          * has specified the target.
966          * <p>
967          * The session token can be {@link null} if the media button session is unset. In that case,
968          * framework would dispatch to the last sessions's media button receiver.
969          *
970          * @param packageName The package name who would receive the media key event. Can be empty.
971          * @param sessionToken The media session's token. Can be {@code null.}
972          */
onMediaKeyEventSessionChanged(@onNull String packageName, @Nullable MediaSession.Token sessionToken)973         default void onMediaKeyEventSessionChanged(@NonNull String packageName,
974                 @Nullable MediaSession.Token sessionToken) { }
975     }
976 
977     /**
978      * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}.
979      * This can be used to decide whether the remote user is trusted app, and also differentiate
980      * caller of {@link MediaSession} and {@link MediaBrowserService} callbacks.
981      * <p>
982      * See {@link #equals(Object)} to take a look at how it differentiate media controller.
983      *
984      * @see #isTrustedForMediaControl(RemoteUserInfo)
985      */
986     public static final class RemoteUserInfo {
987         private final String mPackageName;
988         private final int mPid;
989         private final int mUid;
990 
991         /**
992          * Create a new remote user information.
993          *
994          * @param packageName The package name of the remote user
995          * @param pid The pid of the remote user
996          * @param uid The uid of the remote user
997          */
RemoteUserInfo(@onNull String packageName, int pid, int uid)998         public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
999             mPackageName = packageName;
1000             mPid = pid;
1001             mUid = uid;
1002         }
1003 
1004         /**
1005          * @return package name of the controller
1006          */
getPackageName()1007         public String getPackageName() {
1008             return mPackageName;
1009         }
1010 
1011         /**
1012          * @return pid of the controller
1013          */
getPid()1014         public int getPid() {
1015             return mPid;
1016         }
1017 
1018         /**
1019          * @return uid of the controller
1020          */
getUid()1021         public int getUid() {
1022             return mUid;
1023         }
1024 
1025         /**
1026          * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal
1027          * if and only if they have the same package name, same pid, and same uid.
1028          *
1029          * @param obj the reference object with which to compare.
1030          * @return {@code true} if equals, {@code false} otherwise
1031          */
1032         @Override
equals(@ullable Object obj)1033         public boolean equals(@Nullable Object obj) {
1034             if (!(obj instanceof RemoteUserInfo)) {
1035                 return false;
1036             }
1037             if (this == obj) {
1038                 return true;
1039             }
1040             RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
1041             return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
1042                     && mPid == otherUserInfo.mPid
1043                     && mUid == otherUserInfo.mUid;
1044         }
1045 
1046         @Override
hashCode()1047         public int hashCode() {
1048             return Objects.hash(mPackageName, mPid, mUid);
1049         }
1050     }
1051 
1052     private static final class SessionsChangedWrapper {
1053         private Context mContext;
1054         private OnActiveSessionsChangedListener mListener;
1055         private Handler mHandler;
1056 
SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, Handler handler)1057         public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
1058                 Handler handler) {
1059             mContext = context;
1060             mListener = listener;
1061             mHandler = handler;
1062         }
1063 
1064         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
1065             @Override
1066             public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
1067                 final Handler handler = mHandler;
1068                 if (handler != null) {
1069                     handler.post(new Runnable() {
1070                         @Override
1071                         public void run() {
1072                             final Context context = mContext;
1073                             if (context != null) {
1074                                 ArrayList<MediaController> controllers = new ArrayList<>();
1075                                 int size = tokens.size();
1076                                 for (int i = 0; i < size; i++) {
1077                                     controllers.add(new MediaController(context, tokens.get(i)));
1078                                 }
1079                                 final OnActiveSessionsChangedListener listener = mListener;
1080                                 if (listener != null) {
1081                                     listener.onActiveSessionsChanged(controllers);
1082                                 }
1083                             }
1084                         }
1085                     });
1086                 }
1087             }
1088         };
1089 
release()1090         private void release() {
1091             mListener = null;
1092             mContext = null;
1093             mHandler = null;
1094         }
1095     }
1096 
1097     private static final class Session2TokensChangedWrapper {
1098         private final OnSession2TokensChangedListener mListener;
1099         private final Handler mHandler;
1100         private final ISession2TokensListener.Stub mStub =
1101                 new ISession2TokensListener.Stub() {
1102                     @Override
1103                     public void onSession2TokensChanged(final List<Session2Token> tokens) {
1104                         mHandler.post(() -> mListener.onSession2TokensChanged(tokens));
1105                     }
1106                 };
1107 
Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler)1108         Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler) {
1109             mListener = listener;
1110             mHandler = (handler == null) ? new Handler() : new Handler(handler.getLooper());
1111         }
1112 
getStub()1113         public ISession2TokensListener.Stub getStub() {
1114             return mStub;
1115         }
1116     }
1117 
1118     private static final class OnVolumeKeyLongPressListenerImpl
1119             extends IOnVolumeKeyLongPressListener.Stub {
1120         private OnVolumeKeyLongPressListener mListener;
1121         private Handler mHandler;
1122 
OnVolumeKeyLongPressListenerImpl( OnVolumeKeyLongPressListener listener, Handler handler)1123         public OnVolumeKeyLongPressListenerImpl(
1124                 OnVolumeKeyLongPressListener listener, Handler handler) {
1125             mListener = listener;
1126             mHandler = handler;
1127         }
1128 
1129         @Override
onVolumeKeyLongPress(KeyEvent event)1130         public void onVolumeKeyLongPress(KeyEvent event) {
1131             if (mListener == null || mHandler == null) {
1132                 Log.w(TAG, "Failed to call volume key long-press listener." +
1133                         " Either mListener or mHandler is null");
1134                 return;
1135             }
1136             mHandler.post(new Runnable() {
1137                 @Override
1138                 public void run() {
1139                     mListener.onVolumeKeyLongPress(event);
1140                 }
1141             });
1142         }
1143     }
1144 
1145     private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
1146         private OnMediaKeyListener mListener;
1147         private Handler mHandler;
1148 
OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler)1149         public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
1150             mListener = listener;
1151             mHandler = handler;
1152         }
1153 
1154         @Override
onMediaKey(KeyEvent event, ResultReceiver result)1155         public void onMediaKey(KeyEvent event, ResultReceiver result) {
1156             if (mListener == null || mHandler == null) {
1157                 Log.w(TAG, "Failed to call media key listener." +
1158                         " Either mListener or mHandler is null");
1159                 return;
1160             }
1161             mHandler.post(new Runnable() {
1162                 @Override
1163                 public void run() {
1164                     boolean handled = mListener.onMediaKey(event);
1165                     Log.d(TAG, "The media key listener is returned " + handled);
1166                     if (result != null) {
1167                         result.send(
1168                                 handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
1169                                 null);
1170                     }
1171                 }
1172             });
1173         }
1174     }
1175 
1176     private final class OnMediaKeyEventDispatchedListenerStub
1177             extends IOnMediaKeyEventDispatchedListener.Stub {
1178 
1179         @Override
onMediaKeyEventDispatched(KeyEvent event, String packageName, MediaSession.Token sessionToken)1180         public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
1181                 MediaSession.Token sessionToken) {
1182             synchronized (mLock) {
1183                 for (Map.Entry<OnMediaKeyEventDispatchedListener, Executor> e
1184                         : mOnMediaKeyEventDispatchedListeners.entrySet()) {
1185                     e.getValue().execute(
1186                             () -> e.getKey().onMediaKeyEventDispatched(event, packageName,
1187                                     sessionToken));
1188                 }
1189             }
1190         }
1191     }
1192 
1193     private final class OnMediaKeyEventSessionChangedListenerStub
1194             extends IOnMediaKeyEventSessionChangedListener.Stub {
1195         @Override
onMediaKeyEventSessionChanged(String packageName, MediaSession.Token sessionToken)1196         public void onMediaKeyEventSessionChanged(String packageName,
1197                 MediaSession.Token sessionToken) {
1198             synchronized (mLock) {
1199                 mCurMediaKeyEventSessionPackage = packageName;
1200                 mCurMediaKeyEventSession = sessionToken;
1201                 for (Map.Entry<OnMediaKeyEventSessionChangedListener, Executor> e
1202                         : mMediaKeyEventSessionChangedCallbacks.entrySet()) {
1203                     e.getValue().execute(() -> e.getKey().onMediaKeyEventSessionChanged(packageName,
1204                             sessionToken));
1205                 }
1206             }
1207         }
1208     }
1209 }
1210