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.NonNull;
20import android.annotation.Nullable;
21import android.annotation.RequiresPermission;
22import android.annotation.SystemApi;
23import android.annotation.SystemService;
24import android.content.ComponentName;
25import android.content.Context;
26import android.media.AudioManager;
27import android.media.IRemoteVolumeController;
28import android.media.session.ISessionManager;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.RemoteException;
32import android.os.ResultReceiver;
33import android.os.ServiceManager;
34import android.os.UserHandle;
35import android.service.notification.NotificationListenerService;
36import android.util.ArrayMap;
37import android.util.Log;
38import android.view.KeyEvent;
39
40import java.util.ArrayList;
41import java.util.List;
42
43/**
44 * Provides support for interacting with {@link MediaSession media sessions}
45 * that applications have published to express their ongoing media playback
46 * state.
47 *
48 * @see MediaSession
49 * @see MediaController
50 */
51@SystemService(Context.MEDIA_SESSION_SERVICE)
52public final class MediaSessionManager {
53    private static final String TAG = "SessionManager";
54
55    /**
56     * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
57     * @hide
58     */
59    public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
60
61    /**
62     * Used by IOnMediaKeyListener to indicate that the media key event is handled.
63     * @hide
64     */
65    public static final int RESULT_MEDIA_KEY_HANDLED = 1;
66
67    private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
68            = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
69    private final Object mLock = new Object();
70    private final ISessionManager mService;
71
72    private Context mContext;
73
74    private CallbackImpl mCallback;
75    private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
76    private OnMediaKeyListenerImpl mOnMediaKeyListener;
77
78    /**
79     * @hide
80     */
81    public MediaSessionManager(Context context) {
82        // Consider rewriting like DisplayManagerGlobal
83        // Decide if we need context
84        mContext = context;
85        IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
86        mService = ISessionManager.Stub.asInterface(b);
87    }
88
89    /**
90     * Create a new session in the system and get the binder for it.
91     *
92     * @param tag A short name for debugging purposes.
93     * @return The binder object from the system
94     * @hide
95     */
96    public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
97            @NonNull String tag, int userId) throws RemoteException {
98        return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
99    }
100
101    /**
102     * Get a list of controllers for all ongoing sessions. The controllers will
103     * be provided in priority order with the most important controller at index
104     * 0.
105     * <p>
106     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
107     * permission be held by the calling app. You may also retrieve this list if
108     * your app is an enabled notification listener using the
109     * {@link NotificationListenerService} APIs, in which case you must pass the
110     * {@link ComponentName} of your enabled listener.
111     *
112     * @param notificationListener The enabled notification listener component.
113     *            May be null.
114     * @return A list of controllers for ongoing sessions.
115     */
116    public @NonNull List<MediaController> getActiveSessions(
117            @Nullable ComponentName notificationListener) {
118        return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
119    }
120
121    /**
122     * Get active sessions for a specific user. To retrieve actions for a user
123     * other than your own you must hold the
124     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
125     * in addition to any other requirements. If you are an enabled notification
126     * listener you may only get sessions for the users you are enabled for.
127     *
128     * @param notificationListener The enabled notification listener component.
129     *            May be null.
130     * @param userId The user id to fetch sessions for.
131     * @return A list of controllers for ongoing sessions.
132     * @hide
133     */
134    public @NonNull List<MediaController> getActiveSessionsForUser(
135            @Nullable ComponentName notificationListener, int userId) {
136        ArrayList<MediaController> controllers = new ArrayList<MediaController>();
137        try {
138            List<IBinder> binders = mService.getSessions(notificationListener, userId);
139            int size = binders.size();
140            for (int i = 0; i < size; i++) {
141                MediaController controller = new MediaController(mContext, ISessionController.Stub
142                        .asInterface(binders.get(i)));
143                controllers.add(controller);
144            }
145        } catch (RemoteException e) {
146            Log.e(TAG, "Failed to get active sessions: ", e);
147        }
148        return controllers;
149    }
150
151    /**
152     * Add a listener to be notified when the list of active sessions
153     * changes.This requires the
154     * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
155     * the calling app. You may also retrieve this list if your app is an
156     * enabled notification listener using the
157     * {@link NotificationListenerService} APIs, in which case you must pass the
158     * {@link ComponentName} of your enabled listener. Updates will be posted to
159     * the thread that registered the listener.
160     *
161     * @param sessionListener The listener to add.
162     * @param notificationListener The enabled notification listener component.
163     *            May be null.
164     */
165    public void addOnActiveSessionsChangedListener(
166            @NonNull OnActiveSessionsChangedListener sessionListener,
167            @Nullable ComponentName notificationListener) {
168        addOnActiveSessionsChangedListener(sessionListener, notificationListener, null);
169    }
170
171    /**
172     * Add a listener to be notified when the list of active sessions
173     * changes.This requires the
174     * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
175     * the calling app. You may also retrieve this list if your app is an
176     * enabled notification listener using the
177     * {@link NotificationListenerService} APIs, in which case you must pass the
178     * {@link ComponentName} of your enabled listener. Updates will be posted to
179     * the handler specified or to the caller's thread if the handler is null.
180     *
181     * @param sessionListener The listener to add.
182     * @param notificationListener The enabled notification listener component.
183     *            May be null.
184     * @param handler The handler to post events to.
185     */
186    public void addOnActiveSessionsChangedListener(
187            @NonNull OnActiveSessionsChangedListener sessionListener,
188            @Nullable ComponentName notificationListener, @Nullable Handler handler) {
189        addOnActiveSessionsChangedListener(sessionListener, notificationListener,
190                UserHandle.myUserId(), handler);
191    }
192
193    /**
194     * Add a listener to be notified when the list of active sessions
195     * changes.This requires the
196     * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
197     * the calling app. You may also retrieve this list if your app is an
198     * enabled notification listener using the
199     * {@link NotificationListenerService} APIs, in which case you must pass the
200     * {@link ComponentName} of your enabled listener.
201     *
202     * @param sessionListener The listener to add.
203     * @param notificationListener The enabled notification listener component.
204     *            May be null.
205     * @param userId The userId to listen for changes on.
206     * @param handler The handler to post updates on.
207     * @hide
208     */
209    public void addOnActiveSessionsChangedListener(
210            @NonNull OnActiveSessionsChangedListener sessionListener,
211            @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
212        if (sessionListener == null) {
213            throw new IllegalArgumentException("listener may not be null");
214        }
215        if (handler == null) {
216            handler = new Handler();
217        }
218        synchronized (mLock) {
219            if (mListeners.get(sessionListener) != null) {
220                Log.w(TAG, "Attempted to add session listener twice, ignoring.");
221                return;
222            }
223            SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
224                    handler);
225            try {
226                mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
227                mListeners.put(sessionListener, wrapper);
228            } catch (RemoteException e) {
229                Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
230            }
231        }
232    }
233
234    /**
235     * Stop receiving active sessions updates on the specified listener.
236     *
237     * @param listener The listener to remove.
238     */
239    public void removeOnActiveSessionsChangedListener(
240            @NonNull OnActiveSessionsChangedListener listener) {
241        if (listener == null) {
242            throw new IllegalArgumentException("listener may not be null");
243        }
244        synchronized (mLock) {
245            SessionsChangedWrapper wrapper = mListeners.remove(listener);
246            if (wrapper != null) {
247                try {
248                    mService.removeSessionsListener(wrapper.mStub);
249                } catch (RemoteException e) {
250                    Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e);
251                } finally {
252                    wrapper.release();
253                }
254            }
255        }
256    }
257
258    /**
259     * Set the remote volume controller to receive volume updates on. Only for
260     * use by system UI.
261     *
262     * @param rvc The volume controller to receive updates on.
263     * @hide
264     */
265    public void setRemoteVolumeController(IRemoteVolumeController rvc) {
266        try {
267            mService.setRemoteVolumeController(rvc);
268        } catch (RemoteException e) {
269            Log.e(TAG, "Error in setRemoteVolumeController.", e);
270        }
271    }
272
273    /**
274     * Send a media key event. The receiver will be selected automatically.
275     *
276     * @param keyEvent The KeyEvent to send.
277     * @hide
278     */
279    public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
280        dispatchMediaKeyEvent(keyEvent, false);
281    }
282
283    /**
284     * Send a media key event. The receiver will be selected automatically.
285     *
286     * @param keyEvent The KeyEvent to send.
287     * @param needWakeLock True if a wake lock should be held while sending the key.
288     * @hide
289     */
290    public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
291        try {
292            mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
293        } catch (RemoteException e) {
294            Log.e(TAG, "Failed to send key event.", e);
295        }
296    }
297
298    /**
299     * Send a volume key event. The receiver will be selected automatically.
300     *
301     * @param keyEvent The volume KeyEvent to send.
302     * @param needWakeLock True if a wake lock should be held while sending the key.
303     * @hide
304     */
305    public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
306        try {
307            mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
308        } catch (RemoteException e) {
309            Log.e(TAG, "Failed to send volume key event.", e);
310        }
311    }
312
313    /**
314     * Dispatch an adjust volume request to the system. It will be sent to the
315     * most relevant audio stream or media session. The direction must be one of
316     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
317     * {@link AudioManager#ADJUST_SAME}.
318     *
319     * @param suggestedStream The stream to fall back to if there isn't a
320     *            relevant stream
321     * @param direction The direction to adjust volume in.
322     * @param flags Any flags to include with the volume change.
323     * @hide
324     */
325    public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
326        try {
327            mService.dispatchAdjustVolume(suggestedStream, direction, flags);
328        } catch (RemoteException e) {
329            Log.e(TAG, "Failed to send adjust volume.", e);
330        }
331    }
332
333    /**
334     * Check if the global priority session is currently active. This can be
335     * used to decide if media keys should be sent to the session or to the app.
336     *
337     * @hide
338     */
339    public boolean isGlobalPriorityActive() {
340        try {
341            return mService.isGlobalPriorityActive();
342        } catch (RemoteException e) {
343            Log.e(TAG, "Failed to check if the global priority is active.", e);
344        }
345        return false;
346    }
347
348    /**
349     * Set the volume key long-press listener. While the listener is set, the listener
350     * gets the volume key long-presses instead of changing volume.
351     *
352     * <p>System can only have a single volume key long-press listener.
353     *
354     * @param listener The volume key long-press listener. {@code null} to reset.
355     * @param handler The handler on which the listener should be invoked, or {@code null}
356     *            if the listener should be invoked on the calling thread's looper.
357     * @hide
358     */
359    @SystemApi
360    @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER)
361    public void setOnVolumeKeyLongPressListener(
362            OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
363        synchronized (mLock) {
364            try {
365                if (listener == null) {
366                    mOnVolumeKeyLongPressListener = null;
367                    mService.setOnVolumeKeyLongPressListener(null);
368                } else {
369                    if (handler == null) {
370                        handler = new Handler();
371                    }
372                    mOnVolumeKeyLongPressListener =
373                            new OnVolumeKeyLongPressListenerImpl(listener, handler);
374                    mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
375                }
376            } catch (RemoteException e) {
377                Log.e(TAG, "Failed to set volume key long press listener", e);
378            }
379        }
380    }
381
382    /**
383     * Set the media key listener. While the listener is set, the listener
384     * gets the media key before any other media sessions but after the global priority session.
385     * If the listener handles the key (i.e. returns {@code true}),
386     * other sessions will not get the event.
387     *
388     * <p>System can only have a single media key listener.
389     *
390     * @param listener The media key listener. {@code null} to reset.
391     * @param handler The handler on which the listener should be invoked, or {@code null}
392     *            if the listener should be invoked on the calling thread's looper.
393     * @hide
394     */
395    @SystemApi
396    @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER)
397    public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
398        synchronized (mLock) {
399            try {
400                if (listener == null) {
401                    mOnMediaKeyListener = null;
402                    mService.setOnMediaKeyListener(null);
403                } else {
404                    if (handler == null) {
405                        handler = new Handler();
406                    }
407                    mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
408                    mService.setOnMediaKeyListener(mOnMediaKeyListener);
409                }
410            } catch (RemoteException e) {
411                Log.e(TAG, "Failed to set media key listener", e);
412            }
413        }
414    }
415
416    /**
417     * Set a {@link Callback}.
418     *
419     * <p>System can only have a single callback, and the callback can only be set by
420     * Bluetooth service process.
421     *
422     * @param callback A {@link Callback}. {@code null} to reset.
423     * @param handler The handler on which the callback should be invoked, or {@code null}
424     *            if the callback should be invoked on the calling thread's looper.
425     * @hide
426     */
427    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
428        synchronized (mLock) {
429            try {
430                if (callback == null) {
431                    mCallback = null;
432                    mService.setCallback(null);
433                } else {
434                    if (handler == null) {
435                        handler = new Handler();
436                    }
437                    mCallback = new CallbackImpl(callback, handler);
438                    mService.setCallback(mCallback);
439                }
440            } catch (RemoteException e) {
441                Log.e(TAG, "Failed to set media key callback", e);
442            }
443        }
444    }
445
446    /**
447     * Listens for changes to the list of active sessions. This can be added
448     * using {@link #addOnActiveSessionsChangedListener}.
449     */
450    public interface OnActiveSessionsChangedListener {
451        public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
452    }
453
454    /**
455     * Listens the volume key long-presses.
456     * @hide
457     */
458    @SystemApi
459    public interface OnVolumeKeyLongPressListener {
460        /**
461         * Called when the volume key is long-pressed.
462         * <p>This will be called for both down and up events.
463         */
464        void onVolumeKeyLongPress(KeyEvent event);
465    }
466
467    /**
468     * Listens the media key.
469     * @hide
470     */
471    @SystemApi
472    public interface OnMediaKeyListener {
473        /**
474         * Called when the media key is pressed.
475         * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with
476         * repeat count zero), it must also comsume all following key events.
477         * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP).
478         * <p>If it takes more than 1s to return, the key event will be sent to
479         * other media sessions.
480         */
481        boolean onMediaKey(KeyEvent event);
482    }
483
484    /**
485     * Callbacks for the media session service.
486     *
487     * <p>Called when a media key event is dispatched or the addressed player is changed.
488     * The addressed player is either the media session or the media button receiver that will
489     * receive media key events.
490     * @hide
491     */
492    public static abstract class Callback {
493        /**
494         * Called when a media key event is dispatched to the media session
495         * through the media session service.
496         *
497         * @param event Dispatched media key event.
498         * @param sessionToken The media session's token.
499         */
500        public abstract void onMediaKeyEventDispatched(KeyEvent event,
501                MediaSession.Token sessionToken);
502
503        /**
504         * Called when a media key event is dispatched to the media button receiver
505         * through the media session service.
506         * <p>MediaSessionService may broadcast key events to the media button receiver
507         * when reviving playback after the media session is released.
508         *
509         * @param event Dispatched media key event.
510         * @param mediaButtonReceiver The media button receiver.
511         */
512        public abstract void onMediaKeyEventDispatched(KeyEvent event,
513                ComponentName mediaButtonReceiver);
514
515        /**
516         * Called when the addressed player is changed to a media session.
517         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
518         * {@link #setCallback} if the addressed player exists.
519         *
520         * @param sessionToken The media session's token.
521         */
522        public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
523
524        /**
525         * Called when the addressed player is changed to the media button receiver.
526         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
527         * {@link #setCallback} if the addressed player exists.
528         *
529         * @param mediaButtonReceiver The media button receiver.
530         */
531        public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
532    }
533
534    private static final class SessionsChangedWrapper {
535        private Context mContext;
536        private OnActiveSessionsChangedListener mListener;
537        private Handler mHandler;
538
539        public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
540                Handler handler) {
541            mContext = context;
542            mListener = listener;
543            mHandler = handler;
544        }
545
546        private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
547            @Override
548            public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
549                if (mHandler != null) {
550                    mHandler.post(new Runnable() {
551                        @Override
552                        public void run() {
553                            if (mListener != null) {
554                                ArrayList<MediaController> controllers
555                                        = new ArrayList<MediaController>();
556                                int size = tokens.size();
557                                for (int i = 0; i < size; i++) {
558                                    controllers.add(new MediaController(mContext, tokens.get(i)));
559                                }
560                                mListener.onActiveSessionsChanged(controllers);
561                            }
562                        }
563                    });
564                }
565            }
566        };
567
568        private void release() {
569            mContext = null;
570            mListener = null;
571            mHandler = null;
572        }
573    }
574
575    private static final class OnVolumeKeyLongPressListenerImpl
576            extends IOnVolumeKeyLongPressListener.Stub {
577        private OnVolumeKeyLongPressListener mListener;
578        private Handler mHandler;
579
580        public OnVolumeKeyLongPressListenerImpl(
581                OnVolumeKeyLongPressListener listener, Handler handler) {
582            mListener = listener;
583            mHandler = handler;
584        }
585
586        @Override
587        public void onVolumeKeyLongPress(KeyEvent event) {
588            if (mListener == null || mHandler == null) {
589                Log.w(TAG, "Failed to call volume key long-press listener." +
590                        " Either mListener or mHandler is null");
591                return;
592            }
593            mHandler.post(new Runnable() {
594                @Override
595                public void run() {
596                    mListener.onVolumeKeyLongPress(event);
597                }
598            });
599        }
600    }
601
602    private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
603        private OnMediaKeyListener mListener;
604        private Handler mHandler;
605
606        public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
607            mListener = listener;
608            mHandler = handler;
609        }
610
611        @Override
612        public void onMediaKey(KeyEvent event, ResultReceiver result) {
613            if (mListener == null || mHandler == null) {
614                Log.w(TAG, "Failed to call media key listener." +
615                        " Either mListener or mHandler is null");
616                return;
617            }
618            mHandler.post(new Runnable() {
619                @Override
620                public void run() {
621                    boolean handled = mListener.onMediaKey(event);
622                    Log.d(TAG, "The media key listener is returned " + handled);
623                    if (result != null) {
624                        result.send(
625                                handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
626                                null);
627                    }
628                }
629            });
630        }
631    }
632
633    private static final class CallbackImpl extends ICallback.Stub {
634        private final Callback mCallback;
635        private final Handler mHandler;
636
637        public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
638            mCallback = callback;
639            mHandler = handler;
640        }
641
642        @Override
643        public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
644                MediaSession.Token sessionToken) {
645            mHandler.post(new Runnable() {
646                @Override
647                public void run() {
648                    mCallback.onMediaKeyEventDispatched(event, sessionToken);
649                }
650            });
651        }
652
653        @Override
654        public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
655                ComponentName mediaButtonReceiver) {
656            mHandler.post(new Runnable() {
657                @Override
658                public void run() {
659                    mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
660                }
661            });
662        }
663
664        @Override
665        public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
666            mHandler.post(new Runnable() {
667                @Override
668                public void run() {
669                    mCallback.onAddressedPlayerChanged(sessionToken);
670                }
671            });
672        }
673
674        @Override
675        public void onAddressedPlayerChangedToMediaButtonReceiver(
676                ComponentName mediaButtonReceiver) {
677            mHandler.post(new Runnable() {
678                @Override
679                public void run() {
680                    mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
681                }
682            });
683        }
684    }
685}
686