1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.session;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.annotation.SystemService;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.media.AudioManager;
28 import android.media.IRemoteVolumeController;
29 import android.media.ISessionTokensListener;
30 import android.media.MediaSession2;
31 import android.media.MediaSessionService2;
32 import android.media.SessionToken2;
33 import android.media.browse.MediaBrowser;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.ResultReceiver;
39 import android.os.ServiceManager;
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 java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.concurrent.Executor;
53 
54 /**
55  * Provides support for interacting with {@link MediaSession media sessions}
56  * that applications have published to express their ongoing media playback
57  * state.
58  *
59  * @see MediaSession
60  * @see MediaController
61  */
62 @SystemService(Context.MEDIA_SESSION_SERVICE)
63 public final class MediaSessionManager {
64     private static final String TAG = "SessionManager";
65 
66     /**
67      * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
68      * @hide
69      */
70     public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
71 
72     /**
73      * Used by IOnMediaKeyListener to indicate that the media key event is handled.
74      * @hide
75      */
76     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
77 
78     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
79             = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
80     private final ArrayMap<OnSessionTokensChangedListener, SessionTokensChangedWrapper>
81             mSessionTokensListener = new ArrayMap<>();
82     private final Object mLock = new Object();
83     private final ISessionManager mService;
84 
85     private Context mContext;
86 
87     private CallbackImpl mCallback;
88     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
89     private OnMediaKeyListenerImpl mOnMediaKeyListener;
90 
91     /**
92      * @hide
93      */
MediaSessionManager(Context context)94     public MediaSessionManager(Context context) {
95         // Consider rewriting like DisplayManagerGlobal
96         // Decide if we need context
97         mContext = context;
98         IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
99         mService = ISessionManager.Stub.asInterface(b);
100     }
101 
102     /**
103      * Create a new session in the system and get the binder for it.
104      *
105      * @param tag A short name for debugging purposes.
106      * @return The binder object from the system
107      * @hide
108      */
createSession(@onNull MediaSession.CallbackStub cbStub, @NonNull String tag, int userId)109     public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
110             @NonNull String tag, int userId) throws RemoteException {
111         return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
112     }
113 
114     /**
115      * Get a list of controllers for all ongoing sessions. The controllers will
116      * be provided in priority order with the most important controller at index
117      * 0.
118      * <p>
119      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
120      * permission be held by the calling app. You may also retrieve this list if
121      * your app is an enabled notification listener using the
122      * {@link NotificationListenerService} APIs, in which case you must pass the
123      * {@link ComponentName} of your enabled listener.
124      *
125      * @param notificationListener The enabled notification listener component.
126      *            May be null.
127      * @return A list of controllers for ongoing sessions.
128      */
getActiveSessions( @ullable ComponentName notificationListener)129     public @NonNull List<MediaController> getActiveSessions(
130             @Nullable ComponentName notificationListener) {
131         return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
132     }
133 
134     /**
135      * Get active sessions for a specific user. To retrieve actions for a user
136      * other than your own you must hold the
137      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
138      * in addition to any other requirements. If you are an enabled notification
139      * listener you may only get sessions for the users you are enabled for.
140      *
141      * @param notificationListener The enabled notification listener component.
142      *            May be null.
143      * @param userId The user id to fetch sessions for.
144      * @return A list of controllers for ongoing sessions.
145      * @hide
146      */
getActiveSessionsForUser( @ullable ComponentName notificationListener, int userId)147     public @NonNull List<MediaController> getActiveSessionsForUser(
148             @Nullable ComponentName notificationListener, int userId) {
149         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
150         try {
151             List<IBinder> binders = mService.getSessions(notificationListener, userId);
152             int size = binders.size();
153             for (int i = 0; i < size; i++) {
154                 MediaController controller = new MediaController(mContext, ISessionController.Stub
155                         .asInterface(binders.get(i)));
156                 controllers.add(controller);
157             }
158         } catch (RemoteException e) {
159             Log.e(TAG, "Failed to get active sessions: ", e);
160         }
161         return controllers;
162     }
163 
164     /**
165      * Add a listener to be notified when the list of active sessions
166      * changes.This requires the
167      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
168      * the calling app. You may also retrieve this list if your app is an
169      * enabled notification listener using the
170      * {@link NotificationListenerService} APIs, in which case you must pass the
171      * {@link ComponentName} of your enabled listener. Updates will be posted to
172      * the thread that registered the listener.
173      *
174      * @param sessionListener The listener to add.
175      * @param notificationListener The enabled notification listener component.
176      *            May be null.
177      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener)178     public void addOnActiveSessionsChangedListener(
179             @NonNull OnActiveSessionsChangedListener sessionListener,
180             @Nullable ComponentName notificationListener) {
181         addOnActiveSessionsChangedListener(sessionListener, notificationListener, null);
182     }
183 
184     /**
185      * Add a listener to be notified when the list of active sessions
186      * changes.This requires the
187      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
188      * the calling app. You may also retrieve this list if your app is an
189      * enabled notification listener using the
190      * {@link NotificationListenerService} APIs, in which case you must pass the
191      * {@link ComponentName} of your enabled listener. Updates will be posted to
192      * the handler specified or to the caller's thread if the handler is null.
193      *
194      * @param sessionListener The listener to add.
195      * @param notificationListener The enabled notification listener component.
196      *            May be null.
197      * @param handler The handler to post events to.
198      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler)199     public void addOnActiveSessionsChangedListener(
200             @NonNull OnActiveSessionsChangedListener sessionListener,
201             @Nullable ComponentName notificationListener, @Nullable Handler handler) {
202         addOnActiveSessionsChangedListener(sessionListener, notificationListener,
203                 UserHandle.myUserId(), handler);
204     }
205 
206     /**
207      * Add a listener to be notified when the list of active sessions
208      * changes.This requires the
209      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
210      * the calling app. You may also retrieve this list if your app is an
211      * enabled notification listener using the
212      * {@link NotificationListenerService} APIs, in which case you must pass the
213      * {@link ComponentName} of your enabled listener.
214      *
215      * @param sessionListener The listener to add.
216      * @param notificationListener The enabled notification listener component.
217      *            May be null.
218      * @param userId The userId to listen for changes on.
219      * @param handler The handler to post updates on.
220      * @hide
221      */
addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler)222     public void addOnActiveSessionsChangedListener(
223             @NonNull OnActiveSessionsChangedListener sessionListener,
224             @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
225         if (sessionListener == null) {
226             throw new IllegalArgumentException("listener may not be null");
227         }
228         if (handler == null) {
229             handler = new Handler();
230         }
231         synchronized (mLock) {
232             if (mListeners.get(sessionListener) != null) {
233                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
234                 return;
235             }
236             SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
237                     handler);
238             try {
239                 mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
240                 mListeners.put(sessionListener, wrapper);
241             } catch (RemoteException e) {
242                 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
243             }
244         }
245     }
246 
247     /**
248      * Stop receiving active sessions updates on the specified listener.
249      *
250      * @param listener The listener to remove.
251      */
removeOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener listener)252     public void removeOnActiveSessionsChangedListener(
253             @NonNull OnActiveSessionsChangedListener listener) {
254         if (listener == null) {
255             throw new IllegalArgumentException("listener may not be null");
256         }
257         synchronized (mLock) {
258             SessionsChangedWrapper wrapper = mListeners.remove(listener);
259             if (wrapper != null) {
260                 try {
261                     mService.removeSessionsListener(wrapper.mStub);
262                 } catch (RemoteException e) {
263                     Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e);
264                 } finally {
265                     wrapper.release();
266                 }
267             }
268         }
269     }
270 
271     /**
272      * Set the remote volume controller to receive volume updates on. Only for
273      * use by system UI.
274      *
275      * @param rvc The volume controller to receive updates on.
276      * @hide
277      */
setRemoteVolumeController(IRemoteVolumeController rvc)278     public void setRemoteVolumeController(IRemoteVolumeController rvc) {
279         try {
280             mService.setRemoteVolumeController(rvc);
281         } catch (RemoteException e) {
282             Log.e(TAG, "Error in setRemoteVolumeController.", e);
283         }
284     }
285 
286     /**
287      * Send a media key event. The receiver will be selected automatically.
288      *
289      * @param keyEvent The KeyEvent to send.
290      * @hide
291      */
dispatchMediaKeyEvent(@onNull KeyEvent keyEvent)292     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
293         dispatchMediaKeyEvent(keyEvent, false);
294     }
295 
296     /**
297      * Send a media key event. The receiver will be selected automatically.
298      *
299      * @param keyEvent The KeyEvent to send.
300      * @param needWakeLock True if a wake lock should be held while sending the key.
301      * @hide
302      */
dispatchMediaKeyEvent(@onNull KeyEvent keyEvent, boolean needWakeLock)303     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
304         dispatchMediaKeyEventInternal(false, keyEvent, needWakeLock);
305     }
306 
307     /**
308      * Send a media key event as system component. The receiver will be selected automatically.
309      * <p>
310      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
311      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
312      * from the hardware devices.
313      *
314      * @param keyEvent The KeyEvent to send.
315      * @hide
316      */
dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent)317     public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) {
318         dispatchMediaKeyEventInternal(true, keyEvent, false);
319     }
320 
dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, boolean needWakeLock)321     private void dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
322             boolean needWakeLock) {
323         try {
324             mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,
325                     needWakeLock);
326         } catch (RemoteException e) {
327             Log.e(TAG, "Failed to send key event.", e);
328         }
329     }
330 
331     /**
332      * Send a volume key event. The receiver will be selected automatically.
333      *
334      * @param keyEvent The volume KeyEvent to send.
335      * @hide
336      */
dispatchVolumeKeyEvent(@onNull KeyEvent keyEvent, int stream, boolean musicOnly)337     public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
338         dispatchVolumeKeyEventInternal(false, keyEvent, stream, musicOnly);
339     }
340 
341     /**
342      * Dispatches the volume button event as system service to the session. This only effects the
343      * {@link MediaSession.Callback#getCurrentControllerInfo()} and doesn't bypass any permission
344      * check done by the system service.
345      * <p>
346      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
347      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
348      * from the hardware devices.
349      *
350      * @param keyEvent The KeyEvent to send.
351      * @hide
352      */
dispatchVolumeKeyEventAsSystemService(@onNull KeyEvent keyEvent, int streamType)353     public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
354         dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
355     }
356 
dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent, int stream, boolean musicOnly)357     private void dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
358             int stream, boolean musicOnly) {
359         try {
360             mService.dispatchVolumeKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,
361                     stream, musicOnly);
362         } catch (RemoteException e) {
363             Log.e(TAG, "Failed to send volume key event.", e);
364         }
365     }
366 
367     /**
368      * Dispatch an adjust volume request to the system. It will be sent to the
369      * most relevant audio stream or media session. The direction must be one of
370      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
371      * {@link AudioManager#ADJUST_SAME}.
372      *
373      * @param suggestedStream The stream to fall back to if there isn't a
374      *            relevant stream
375      * @param direction The direction to adjust volume in.
376      * @param flags Any flags to include with the volume change.
377      * @hide
378      */
dispatchAdjustVolume(int suggestedStream, int direction, int flags)379     public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
380         try {
381             mService.dispatchAdjustVolume(mContext.getPackageName(), suggestedStream, direction,
382                     flags);
383         } catch (RemoteException e) {
384             Log.e(TAG, "Failed to send adjust volume.", e);
385         }
386     }
387 
388     /**
389      * Checks whether the remote user is a trusted app.
390      * <p>
391      * An app is trusted if the app holds the android.Manifest.permission.MEDIA_CONTENT_CONTROL
392      * permission or has an enabled notification listener.
393      *
394      * @param userInfo The remote user info from either
395      *            {@link MediaSession#getCurrentControllerInfo()} or
396      *            {@link MediaBrowserService#getCurrentBrowserInfo()}.
397      * @return {@code true} if the remote user is trusted and its package name matches with the UID.
398      *            {@code false} otherwise.
399      */
isTrustedForMediaControl(@onNull RemoteUserInfo userInfo)400     public boolean isTrustedForMediaControl(@NonNull RemoteUserInfo userInfo) {
401         if (userInfo == null) {
402             throw new IllegalArgumentException("userInfo may not be null");
403         }
404         if (userInfo.getPackageName() == null) {
405             return false;
406         }
407         try {
408             return mService.isTrusted(
409                     userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid());
410         } catch (RemoteException e) {
411             Log.wtf(TAG, "Cannot communicate with the service.", e);
412         }
413         return false;
414     }
415 
416     /**
417      * Called when a {@link MediaSession2} is created.
418      * @hide
419      */
createSession2(@onNull SessionToken2 token)420     public boolean createSession2(@NonNull SessionToken2 token) {
421         if (token == null) {
422             return false;
423         }
424         try {
425             return mService.createSession2(token.toBundle());
426         } catch (RemoteException e) {
427             Log.wtf(TAG, "Cannot communicate with the service.", e);
428         }
429         return false;
430     }
431 
432     /**
433      * Called when a {@link MediaSession2} is destroyed.
434      * @hide
435      */
destroySession2(@onNull SessionToken2 token)436     public void destroySession2(@NonNull SessionToken2 token) {
437         if (token == null) {
438             return;
439         }
440         try {
441             mService.destroySession2(token.toBundle());
442         } catch (RemoteException e) {
443             Log.wtf(TAG, "Cannot communicate with the service.", e);
444         }
445     }
446 
447     /**
448      * @hide
449      * Get {@link List} of {@link SessionToken2} whose sessions are active now. This list represents
450      * active sessions regardless of whether they're {@link MediaSession2} or
451      * {@link MediaSessionService2}.
452      * <p>
453      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
454      * calling app. You may also retrieve this list if your app is an enabled notification listener
455      * using the {@link NotificationListenerService} APIs.
456      *
457      * @return list of tokens
458      */
getActiveSessionTokens()459     public List<SessionToken2> getActiveSessionTokens() {
460         try {
461             List<Bundle> bundles = mService.getSessionTokens(
462                     /* activeSessionOnly */ true, /* sessionServiceOnly */ false,
463                     mContext.getPackageName());
464             return toTokenList(bundles);
465         } catch (RemoteException e) {
466             Log.wtf(TAG, "Cannot communicate with the service.", e);
467             return Collections.emptyList();
468         }
469     }
470 
471     /**
472      * @hide
473      * Get {@link List} of {@link SessionToken2} for {@link MediaSessionService2} regardless of their
474      * activeness. This list represents media apps that support background playback.
475      * <p>
476      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
477      * calling app. You may also retrieve this list if your app is an enabled notification listener
478      * using the {@link NotificationListenerService} APIs.
479      *
480      * @return list of tokens
481      */
getSessionServiceTokens()482     public List<SessionToken2> getSessionServiceTokens() {
483         try {
484             List<Bundle> bundles = mService.getSessionTokens(
485                     /* activeSessionOnly */ false, /* sessionServiceOnly */ true,
486                     mContext.getPackageName());
487             return toTokenList(bundles);
488         } catch (RemoteException e) {
489             Log.wtf(TAG, "Cannot communicate with the service.", e);
490             return Collections.emptyList();
491         }
492     }
493 
494     /**
495      * @hide
496      * Get all {@link SessionToken2}s. This is the combined list of {@link #getActiveSessionTokens()}
497      * and {@link #getSessionServiceTokens}.
498      * <p>
499      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
500      * calling app. You may also retrieve this list if your app is an enabled notification listener
501      * using the {@link NotificationListenerService} APIs.
502      *
503      * @return list of tokens
504      * @see #getActiveSessionTokens
505      * @see #getSessionServiceTokens
506      */
getAllSessionTokens()507     public List<SessionToken2> getAllSessionTokens() {
508         try {
509             List<Bundle> bundles = mService.getSessionTokens(
510                     /* activeSessionOnly */ false, /* sessionServiceOnly */ false,
511                     mContext.getPackageName());
512             return toTokenList(bundles);
513         } catch (RemoteException e) {
514             Log.wtf(TAG, "Cannot communicate with the service.", e);
515             return Collections.emptyList();
516         }
517     }
518 
519     /**
520      * @hide
521      * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
522      * <p>
523      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
524      * calling app. You may also retrieve this list if your app is an enabled notification listener
525      * using the {@link NotificationListenerService} APIs.
526      *
527      * @param executor executor to run this command
528      * @param listener The listener to add.
529      */
addOnSessionTokensChangedListener(@onNull @allbackExecutor Executor executor, @NonNull OnSessionTokensChangedListener listener)530     public void addOnSessionTokensChangedListener(@NonNull @CallbackExecutor Executor executor,
531             @NonNull OnSessionTokensChangedListener listener) {
532         addOnSessionTokensChangedListener(UserHandle.myUserId(), executor, listener);
533     }
534 
535     /**
536      * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
537      * <p>
538      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
539      * calling app. You may also retrieve this list if your app is an enabled notification listener
540      * using the {@link NotificationListenerService} APIs.
541      *
542      * @param userId The userId to listen for changes on.
543      * @param executor executor to run this command
544      * @param listener The listener to add.
545      * @hide
546      */
addOnSessionTokensChangedListener(int userId, @NonNull @CallbackExecutor Executor executor, @NonNull OnSessionTokensChangedListener listener)547     public void addOnSessionTokensChangedListener(int userId,
548             @NonNull @CallbackExecutor Executor executor,
549             @NonNull OnSessionTokensChangedListener listener) {
550         if (executor == null) {
551             throw new IllegalArgumentException("executor may not be null");
552         }
553         if (listener == null) {
554             throw new IllegalArgumentException("listener may not be null");
555         }
556         synchronized (mLock) {
557             if (mSessionTokensListener.get(listener) != null) {
558                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
559                 return;
560             }
561             SessionTokensChangedWrapper wrapper = new SessionTokensChangedWrapper(
562                     mContext, executor, listener);
563             try {
564                 mService.addSessionTokensListener(wrapper.mStub, userId, mContext.getPackageName());
565                 mSessionTokensListener.put(listener, wrapper);
566             } catch (RemoteException e) {
567                 Log.e(TAG, "Error in addSessionTokensListener.", e);
568             }
569         }
570     }
571 
572     /**
573      * @hide
574      * Stop receiving session token updates on the specified listener.
575      *
576      * @param listener The listener to remove.
577      */
removeOnSessionTokensChangedListener( @onNull OnSessionTokensChangedListener listener)578     public void removeOnSessionTokensChangedListener(
579             @NonNull OnSessionTokensChangedListener listener) {
580         if (listener == null) {
581             throw new IllegalArgumentException("listener may not be null");
582         }
583         synchronized (mLock) {
584             SessionTokensChangedWrapper wrapper = mSessionTokensListener.remove(listener);
585             if (wrapper != null) {
586                 try {
587                     mService.removeSessionTokensListener(wrapper.mStub, mContext.getPackageName());
588                 } catch (RemoteException e) {
589                     Log.e(TAG, "Error in removeSessionTokensListener.", e);
590                 } finally {
591                     wrapper.release();
592                 }
593             }
594         }
595     }
596 
toTokenList(List<Bundle> bundles)597     private static List<SessionToken2> toTokenList(List<Bundle> bundles) {
598         List<SessionToken2> tokens = new ArrayList<>();
599         if (bundles != null) {
600             for (int i = 0; i < bundles.size(); i++) {
601                 SessionToken2 token = SessionToken2.fromBundle(bundles.get(i));
602                 if (token != null) {
603                     tokens.add(token);
604                 }
605             }
606         }
607         return tokens;
608     }
609 
610     /**
611      * Check if the global priority session is currently active. This can be
612      * used to decide if media keys should be sent to the session or to the app.
613      *
614      * @hide
615      */
isGlobalPriorityActive()616     public boolean isGlobalPriorityActive() {
617         try {
618             return mService.isGlobalPriorityActive();
619         } catch (RemoteException e) {
620             Log.e(TAG, "Failed to check if the global priority is active.", e);
621         }
622         return false;
623     }
624 
625     /**
626      * Set the volume key long-press listener. While the listener is set, the listener
627      * gets the volume key long-presses instead of changing volume.
628      *
629      * <p>System can only have a single volume key long-press listener.
630      *
631      * @param listener The volume key long-press listener. {@code null} to reset.
632      * @param handler The handler on which the listener should be invoked, or {@code null}
633      *            if the listener should be invoked on the calling thread's looper.
634      * @hide
635      */
636     @SystemApi
637     @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER)
setOnVolumeKeyLongPressListener( OnVolumeKeyLongPressListener listener, @Nullable Handler handler)638     public void setOnVolumeKeyLongPressListener(
639             OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
640         synchronized (mLock) {
641             try {
642                 if (listener == null) {
643                     mOnVolumeKeyLongPressListener = null;
644                     mService.setOnVolumeKeyLongPressListener(null);
645                 } else {
646                     if (handler == null) {
647                         handler = new Handler();
648                     }
649                     mOnVolumeKeyLongPressListener =
650                             new OnVolumeKeyLongPressListenerImpl(listener, handler);
651                     mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
652                 }
653             } catch (RemoteException e) {
654                 Log.e(TAG, "Failed to set volume key long press listener", e);
655             }
656         }
657     }
658 
659     /**
660      * Set the media key listener. While the listener is set, the listener
661      * gets the media key before any other media sessions but after the global priority session.
662      * If the listener handles the key (i.e. returns {@code true}),
663      * other sessions will not get the event.
664      *
665      * <p>System can only have a single media key listener.
666      *
667      * @param listener The media key listener. {@code null} to reset.
668      * @param handler The handler on which the listener should be invoked, or {@code null}
669      *            if the listener should be invoked on the calling thread's looper.
670      * @hide
671      */
672     @SystemApi
673     @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER)
setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler)674     public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
675         synchronized (mLock) {
676             try {
677                 if (listener == null) {
678                     mOnMediaKeyListener = null;
679                     mService.setOnMediaKeyListener(null);
680                 } else {
681                     if (handler == null) {
682                         handler = new Handler();
683                     }
684                     mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
685                     mService.setOnMediaKeyListener(mOnMediaKeyListener);
686                 }
687             } catch (RemoteException e) {
688                 Log.e(TAG, "Failed to set media key listener", e);
689             }
690         }
691     }
692 
693     /**
694      * Set a {@link Callback}.
695      *
696      * <p>System can only have a single callback, and the callback can only be set by
697      * Bluetooth service process.
698      *
699      * @param callback A {@link Callback}. {@code null} to reset.
700      * @param handler The handler on which the callback should be invoked, or {@code null}
701      *            if the callback should be invoked on the calling thread's looper.
702      * @hide
703      */
setCallback(@ullable Callback callback, @Nullable Handler handler)704     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
705         synchronized (mLock) {
706             try {
707                 if (callback == null) {
708                     mCallback = null;
709                     mService.setCallback(null);
710                 } else {
711                     if (handler == null) {
712                         handler = new Handler();
713                     }
714                     mCallback = new CallbackImpl(callback, handler);
715                     mService.setCallback(mCallback);
716                 }
717             } catch (RemoteException e) {
718                 Log.e(TAG, "Failed to set media key callback", e);
719             }
720         }
721     }
722 
723     /**
724      * Listens for changes to the list of active sessions. This can be added
725      * using {@link #addOnActiveSessionsChangedListener}.
726      */
727     public interface OnActiveSessionsChangedListener {
onActiveSessionsChanged(@ullable List<MediaController> controllers)728         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
729     }
730 
731     /**
732      * @hide
733      * Listens for changes to the {@link #getAllSessionTokens()}. This can be added
734      * using {@link #addOnActiveSessionsChangedListener}.
735      */
736     public interface OnSessionTokensChangedListener {
onSessionTokensChanged(@onNull List<SessionToken2> tokens)737         void onSessionTokensChanged(@NonNull List<SessionToken2> tokens);
738     }
739 
740     /**
741      * Listens the volume key long-presses.
742      * @hide
743      */
744     @SystemApi
745     public interface OnVolumeKeyLongPressListener {
746         /**
747          * Called when the volume key is long-pressed.
748          * <p>This will be called for both down and up events.
749          */
onVolumeKeyLongPress(KeyEvent event)750         void onVolumeKeyLongPress(KeyEvent event);
751     }
752 
753     /**
754      * Listens the media key.
755      * @hide
756      */
757     @SystemApi
758     public interface OnMediaKeyListener {
759         /**
760          * Called when the media key is pressed.
761          * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with
762          * repeat count zero), it must also comsume all following key events.
763          * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP).
764          * <p>If it takes more than 1s to return, the key event will be sent to
765          * other media sessions.
766          */
onMediaKey(KeyEvent event)767         boolean onMediaKey(KeyEvent event);
768     }
769 
770     /**
771      * Callbacks for the media session service.
772      *
773      * <p>Called when a media key event is dispatched or the addressed player is changed.
774      * The addressed player is either the media session or the media button receiver that will
775      * receive media key events.
776      * @hide
777      */
778     public static abstract class Callback {
779         /**
780          * Called when a media key event is dispatched to the media session
781          * through the media session service.
782          *
783          * @param event Dispatched media key event.
784          * @param sessionToken The media session's token.
785          */
onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token sessionToken)786         public abstract void onMediaKeyEventDispatched(KeyEvent event,
787                 MediaSession.Token sessionToken);
788 
789         /**
790          * Called when a media key event is dispatched to the media button receiver
791          * through the media session service.
792          * <p>MediaSessionService may broadcast key events to the media button receiver
793          * when reviving playback after the media session is released.
794          *
795          * @param event Dispatched media key event.
796          * @param mediaButtonReceiver The media button receiver.
797          */
onMediaKeyEventDispatched(KeyEvent event, ComponentName mediaButtonReceiver)798         public abstract void onMediaKeyEventDispatched(KeyEvent event,
799                 ComponentName mediaButtonReceiver);
800 
801         /**
802          * Called when the addressed player is changed to a media session.
803          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
804          * {@link #setCallback} if the addressed player exists.
805          *
806          * @param sessionToken The media session's token.
807          */
onAddressedPlayerChanged(MediaSession.Token sessionToken)808         public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
809 
810         /**
811          * Called when the addressed player is changed to the media button receiver.
812          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
813          * {@link #setCallback} if the addressed player exists.
814          *
815          * @param mediaButtonReceiver The media button receiver.
816          */
onAddressedPlayerChanged(ComponentName mediaButtonReceiver)817         public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
818     }
819 
820     /**
821      * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}.
822      * This can be used to decide whether the remote user is trusted app, and also differentiate
823      * caller of {@link MediaSession} and {@link MediaBrowserService} callbacks.
824      * <p>
825      * See {@link #equals(Object)} to take a look at how it differentiate media controller.
826      *
827      * @see #isTrustedForMediaControl(RemoteUserInfo)
828      */
829     public static final class RemoteUserInfo {
830         private final String mPackageName;
831         private final int mPid;
832         private final int mUid;
833         private final IBinder mCallerBinder;
834 
RemoteUserInfo(@onNull String packageName, int pid, int uid)835         public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
836             this(packageName, pid, uid, null);
837         }
838 
839         /**
840          * @hide
841          */
RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder)842         public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
843             mPackageName = packageName;
844             mPid = pid;
845             mUid = uid;
846             mCallerBinder = callerBinder;
847         }
848 
849         /**
850          * @return package name of the controller
851          */
getPackageName()852         public String getPackageName() {
853             return mPackageName;
854         }
855 
856         /**
857          * @return pid of the controller
858          */
getPid()859         public int getPid() {
860             return mPid;
861         }
862 
863         /**
864          * @return uid of the controller
865          */
getUid()866         public int getUid() {
867             return mUid;
868         }
869 
870         /**
871          * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
872          * sent to the same controller (either {@link MediaController} or
873          * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
874          * would be considered as different one.
875          * <p>
876          * If you only want to compare the caller's package, compare them with the
877          * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
878          *
879          * @param obj the reference object with which to compare.
880          * @return {@code true} if equals, {@code false} otherwise
881          */
882         @Override
equals(Object obj)883         public boolean equals(Object obj) {
884             if (!(obj instanceof RemoteUserInfo)) {
885                 return false;
886             }
887             if (this == obj) {
888                 return true;
889             }
890             RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
891             return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
892                     : mCallerBinder.equals(otherUserInfo.mCallerBinder);
893         }
894 
895         @Override
hashCode()896         public int hashCode() {
897             return Objects.hash(mPackageName, mPid, mUid);
898         }
899     }
900 
901     private static final class SessionsChangedWrapper {
902         private Context mContext;
903         private OnActiveSessionsChangedListener mListener;
904         private Handler mHandler;
905 
SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, Handler handler)906         public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
907                 Handler handler) {
908             mContext = context;
909             mListener = listener;
910             mHandler = handler;
911         }
912 
913         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
914             @Override
915             public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
916                 final Handler handler = mHandler;
917                 if (handler != null) {
918                     handler.post(new Runnable() {
919                         @Override
920                         public void run() {
921                             final Context context = mContext;
922                             if (context != null) {
923                                 ArrayList<MediaController> controllers = new ArrayList<>();
924                                 int size = tokens.size();
925                                 for (int i = 0; i < size; i++) {
926                                     controllers.add(new MediaController(context, tokens.get(i)));
927                                 }
928                                 final OnActiveSessionsChangedListener listener = mListener;
929                                 if (listener != null) {
930                                     listener.onActiveSessionsChanged(controllers);
931                                 }
932                             }
933                         }
934                     });
935                 }
936             }
937         };
938 
release()939         private void release() {
940             mListener = null;
941             mContext = null;
942             mHandler = null;
943         }
944     }
945 
946     private static final class SessionTokensChangedWrapper {
947         private Context mContext;
948         private Executor mExecutor;
949         private OnSessionTokensChangedListener mListener;
950 
SessionTokensChangedWrapper(Context context, Executor executor, OnSessionTokensChangedListener listener)951         public SessionTokensChangedWrapper(Context context, Executor executor,
952                 OnSessionTokensChangedListener listener) {
953             mContext = context;
954             mExecutor = executor;
955             mListener = listener;
956         }
957 
958         private final ISessionTokensListener.Stub mStub = new ISessionTokensListener.Stub() {
959             @Override
960             public void onSessionTokensChanged(final List<Bundle> bundles) {
961                 final Executor executor = mExecutor;
962                 if (executor != null) {
963                     executor.execute(() -> {
964                         final Context context = mContext;
965                         final OnSessionTokensChangedListener listener = mListener;
966                         if (context != null && listener != null) {
967                             listener.onSessionTokensChanged(toTokenList(bundles));
968                         }
969                     });
970                 }
971             }
972         };
973 
release()974         private void release() {
975             mListener = null;
976             mContext = null;
977             mExecutor = null;
978         }
979     }
980 
981     private static final class OnVolumeKeyLongPressListenerImpl
982             extends IOnVolumeKeyLongPressListener.Stub {
983         private OnVolumeKeyLongPressListener mListener;
984         private Handler mHandler;
985 
OnVolumeKeyLongPressListenerImpl( OnVolumeKeyLongPressListener listener, Handler handler)986         public OnVolumeKeyLongPressListenerImpl(
987                 OnVolumeKeyLongPressListener listener, Handler handler) {
988             mListener = listener;
989             mHandler = handler;
990         }
991 
992         @Override
onVolumeKeyLongPress(KeyEvent event)993         public void onVolumeKeyLongPress(KeyEvent event) {
994             if (mListener == null || mHandler == null) {
995                 Log.w(TAG, "Failed to call volume key long-press listener." +
996                         " Either mListener or mHandler is null");
997                 return;
998             }
999             mHandler.post(new Runnable() {
1000                 @Override
1001                 public void run() {
1002                     mListener.onVolumeKeyLongPress(event);
1003                 }
1004             });
1005         }
1006     }
1007 
1008     private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
1009         private OnMediaKeyListener mListener;
1010         private Handler mHandler;
1011 
OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler)1012         public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
1013             mListener = listener;
1014             mHandler = handler;
1015         }
1016 
1017         @Override
onMediaKey(KeyEvent event, ResultReceiver result)1018         public void onMediaKey(KeyEvent event, ResultReceiver result) {
1019             if (mListener == null || mHandler == null) {
1020                 Log.w(TAG, "Failed to call media key listener." +
1021                         " Either mListener or mHandler is null");
1022                 return;
1023             }
1024             mHandler.post(new Runnable() {
1025                 @Override
1026                 public void run() {
1027                     boolean handled = mListener.onMediaKey(event);
1028                     Log.d(TAG, "The media key listener is returned " + handled);
1029                     if (result != null) {
1030                         result.send(
1031                                 handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
1032                                 null);
1033                     }
1034                 }
1035             });
1036         }
1037     }
1038 
1039     private static final class CallbackImpl extends ICallback.Stub {
1040         private final Callback mCallback;
1041         private final Handler mHandler;
1042 
CallbackImpl(@onNull Callback callback, @NonNull Handler handler)1043         public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
1044             mCallback = callback;
1045             mHandler = handler;
1046         }
1047 
1048         @Override
onMediaKeyEventDispatchedToMediaSession(KeyEvent event, MediaSession.Token sessionToken)1049         public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
1050                 MediaSession.Token sessionToken) {
1051             mHandler.post(new Runnable() {
1052                 @Override
1053                 public void run() {
1054                     mCallback.onMediaKeyEventDispatched(event, sessionToken);
1055                 }
1056             });
1057         }
1058 
1059         @Override
onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event, ComponentName mediaButtonReceiver)1060         public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
1061                 ComponentName mediaButtonReceiver) {
1062             mHandler.post(new Runnable() {
1063                 @Override
1064                 public void run() {
1065                     mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
1066                 }
1067             });
1068         }
1069 
1070         @Override
onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken)1071         public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
1072             mHandler.post(new Runnable() {
1073                 @Override
1074                 public void run() {
1075                     mCallback.onAddressedPlayerChanged(sessionToken);
1076                 }
1077             });
1078         }
1079 
1080         @Override
onAddressedPlayerChangedToMediaButtonReceiver( ComponentName mediaButtonReceiver)1081         public void onAddressedPlayerChangedToMediaButtonReceiver(
1082                 ComponentName mediaButtonReceiver) {
1083             mHandler.post(new Runnable() {
1084                 @Override
1085                 public void run() {
1086                     mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
1087                 }
1088             });
1089         }
1090     }
1091 }
1092