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