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