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