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