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
17package android.media.session;
18
19import android.annotation.CallbackExecutor;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.RequiresPermission;
23import android.annotation.SystemApi;
24import android.annotation.SystemService;
25import android.content.ComponentName;
26import android.content.Context;
27import android.media.AudioManager;
28import android.media.IRemoteVolumeController;
29import android.media.ISessionTokensListener;
30import android.media.MediaSession2;
31import android.media.MediaSessionService2;
32import android.media.SessionToken2;
33import android.media.browse.MediaBrowser;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.RemoteException;
38import android.os.ResultReceiver;
39import android.os.ServiceManager;
40import android.os.UserHandle;
41import android.service.media.MediaBrowserService;
42import android.service.notification.NotificationListenerService;
43import android.text.TextUtils;
44import android.util.ArrayMap;
45import android.util.Log;
46import android.view.KeyEvent;
47
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.List;
51import java.util.Objects;
52import 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)
63public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
317    public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) {
318        dispatchMediaKeyEventInternal(true, keyEvent, false);
319    }
320
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     */
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     */
353    public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
354        dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
355    }
356
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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)
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)
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     */
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 {
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 {
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         */
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         */
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         */
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         */
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         */
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         */
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
835        public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
836            this(packageName, pid, uid, null);
837        }
838
839        /**
840         * @hide
841         */
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         */
852        public String getPackageName() {
853            return mPackageName;
854        }
855
856        /**
857         * @return pid of the controller
858         */
859        public int getPid() {
860            return mPid;
861        }
862
863        /**
864         * @return uid of the controller
865         */
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
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
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
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
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
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
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
986        public OnVolumeKeyLongPressListenerImpl(
987                OnVolumeKeyLongPressListener listener, Handler handler) {
988            mListener = listener;
989            mHandler = handler;
990        }
991
992        @Override
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
1012        public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
1013            mListener = listener;
1014            mHandler = handler;
1015        }
1016
1017        @Override
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
1043        public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
1044            mCallback = callback;
1045            mHandler = handler;
1046        }
1047
1048        @Override
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
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
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
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