Home | History | Annotate | Download | only in session
      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.content.ComponentName;
     22 import android.content.Context;
     23 import android.media.AudioManager;
     24 import android.media.IRemoteVolumeController;
     25 import android.media.session.ISessionManager;
     26 import android.os.Handler;
     27 import android.os.IBinder;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.os.UserHandle;
     31 import android.service.notification.NotificationListenerService;
     32 import android.util.ArrayMap;
     33 import android.util.Log;
     34 import android.view.KeyEvent;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 
     39 /**
     40  * Provides support for interacting with {@link MediaSession media sessions}
     41  * that applications have published to express their ongoing media playback
     42  * state.
     43  * <p>
     44  * Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
     45  * get an instance of this class.
     46  *
     47  * @see MediaSession
     48  * @see MediaController
     49  */
     50 public final class MediaSessionManager {
     51     private static final String TAG = "SessionManager";
     52 
     53     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
     54             = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
     55     private final Object mLock = new Object();
     56     private final ISessionManager mService;
     57 
     58     private Context mContext;
     59 
     60     /**
     61      * @hide
     62      */
     63     public MediaSessionManager(Context context) {
     64         // Consider rewriting like DisplayManagerGlobal
     65         // Decide if we need context
     66         mContext = context;
     67         IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
     68         mService = ISessionManager.Stub.asInterface(b);
     69     }
     70 
     71     /**
     72      * Create a new session in the system and get the binder for it.
     73      *
     74      * @param tag A short name for debugging purposes.
     75      * @return The binder object from the system
     76      * @hide
     77      */
     78     public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
     79             @NonNull String tag, int userId) throws RemoteException {
     80         return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
     81     }
     82 
     83     /**
     84      * Get a list of controllers for all ongoing sessions. The controllers will
     85      * be provided in priority order with the most important controller at index
     86      * 0.
     87      * <p>
     88      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
     89      * permission be held by the calling app. You may also retrieve this list if
     90      * your app is an enabled notification listener using the
     91      * {@link NotificationListenerService} APIs, in which case you must pass the
     92      * {@link ComponentName} of your enabled listener.
     93      *
     94      * @param notificationListener The enabled notification listener component.
     95      *            May be null.
     96      * @return A list of controllers for ongoing sessions.
     97      */
     98     public @NonNull List<MediaController> getActiveSessions(
     99             @Nullable ComponentName notificationListener) {
    100         return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
    101     }
    102 
    103     /**
    104      * Get active sessions for a specific user. To retrieve actions for a user
    105      * other than your own you must hold the
    106      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
    107      * in addition to any other requirements. If you are an enabled notification
    108      * listener you may only get sessions for the users you are enabled for.
    109      *
    110      * @param notificationListener The enabled notification listener component.
    111      *            May be null.
    112      * @param userId The user id to fetch sessions for.
    113      * @return A list of controllers for ongoing sessions.
    114      * @hide
    115      */
    116     public @NonNull List<MediaController> getActiveSessionsForUser(
    117             @Nullable ComponentName notificationListener, int userId) {
    118         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
    119         try {
    120             List<IBinder> binders = mService.getSessions(notificationListener, userId);
    121             int size = binders.size();
    122             for (int i = 0; i < size; i++) {
    123                 MediaController controller = new MediaController(mContext, ISessionController.Stub
    124                         .asInterface(binders.get(i)));
    125                 controllers.add(controller);
    126             }
    127         } catch (RemoteException e) {
    128             Log.e(TAG, "Failed to get active sessions: ", e);
    129         }
    130         return controllers;
    131     }
    132 
    133     /**
    134      * Add a listener to be notified when the list of active sessions
    135      * changes.This requires the
    136      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    137      * the calling app. You may also retrieve this list if your app is an
    138      * enabled notification listener using the
    139      * {@link NotificationListenerService} APIs, in which case you must pass the
    140      * {@link ComponentName} of your enabled listener. Updates will be posted to
    141      * the thread that registered the listener.
    142      *
    143      * @param sessionListener The listener to add.
    144      * @param notificationListener The enabled notification listener component.
    145      *            May be null.
    146      */
    147     public void addOnActiveSessionsChangedListener(
    148             @NonNull OnActiveSessionsChangedListener sessionListener,
    149             @Nullable ComponentName notificationListener) {
    150         addOnActiveSessionsChangedListener(sessionListener, notificationListener, null);
    151     }
    152 
    153     /**
    154      * Add a listener to be notified when the list of active sessions
    155      * changes.This requires the
    156      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    157      * the calling app. You may also retrieve this list if your app is an
    158      * enabled notification listener using the
    159      * {@link NotificationListenerService} APIs, in which case you must pass the
    160      * {@link ComponentName} of your enabled listener. Updates will be posted to
    161      * the handler specified or to the caller's thread if the handler is null.
    162      *
    163      * @param sessionListener The listener to add.
    164      * @param notificationListener The enabled notification listener component.
    165      *            May be null.
    166      * @param handler The handler to post events to.
    167      */
    168     public void addOnActiveSessionsChangedListener(
    169             @NonNull OnActiveSessionsChangedListener sessionListener,
    170             @Nullable ComponentName notificationListener, @Nullable Handler handler) {
    171         addOnActiveSessionsChangedListener(sessionListener, notificationListener,
    172                 UserHandle.myUserId(), handler);
    173     }
    174 
    175     /**
    176      * Add a listener to be notified when the list of active sessions
    177      * changes.This requires the
    178      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    179      * the calling app. You may also retrieve this list if your app is an
    180      * enabled notification listener using the
    181      * {@link NotificationListenerService} APIs, in which case you must pass the
    182      * {@link ComponentName} of your enabled listener.
    183      *
    184      * @param sessionListener The listener to add.
    185      * @param notificationListener The enabled notification listener component.
    186      *            May be null.
    187      * @param userId The userId to listen for changes on.
    188      * @param handler The handler to post updates on.
    189      * @hide
    190      */
    191     public void addOnActiveSessionsChangedListener(
    192             @NonNull OnActiveSessionsChangedListener sessionListener,
    193             @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
    194         if (sessionListener == null) {
    195             throw new IllegalArgumentException("listener may not be null");
    196         }
    197         if (handler == null) {
    198             handler = new Handler();
    199         }
    200         synchronized (mLock) {
    201             if (mListeners.get(sessionListener) != null) {
    202                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
    203                 return;
    204             }
    205             SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
    206                     handler);
    207             try {
    208                 mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
    209                 mListeners.put(sessionListener, wrapper);
    210             } catch (RemoteException e) {
    211                 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * Stop receiving active sessions updates on the specified listener.
    218      *
    219      * @param listener The listener to remove.
    220      */
    221     public void removeOnActiveSessionsChangedListener(
    222             @NonNull OnActiveSessionsChangedListener listener) {
    223         if (listener == null) {
    224             throw new IllegalArgumentException("listener may not be null");
    225         }
    226         synchronized (mLock) {
    227             SessionsChangedWrapper wrapper = mListeners.remove(listener);
    228             if (wrapper != null) {
    229                 try {
    230                     mService.removeSessionsListener(wrapper.mStub);
    231                 } catch (RemoteException e) {
    232                     Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e);
    233                 } finally {
    234                     wrapper.release();
    235                 }
    236             }
    237         }
    238     }
    239 
    240     /**
    241      * Set the remote volume controller to receive volume updates on. Only for
    242      * use by system UI.
    243      *
    244      * @param rvc The volume controller to receive updates on.
    245      * @hide
    246      */
    247     public void setRemoteVolumeController(IRemoteVolumeController rvc) {
    248         try {
    249             mService.setRemoteVolumeController(rvc);
    250         } catch (RemoteException e) {
    251             Log.e(TAG, "Error in setRemoteVolumeController.", e);
    252         }
    253     }
    254 
    255     /**
    256      * Send a media key event. The receiver will be selected automatically.
    257      *
    258      * @param keyEvent The KeyEvent to send.
    259      * @hide
    260      */
    261     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
    262         dispatchMediaKeyEvent(keyEvent, false);
    263     }
    264 
    265     /**
    266      * Send a media key event. The receiver will be selected automatically.
    267      *
    268      * @param keyEvent The KeyEvent to send.
    269      * @param needWakeLock True if a wake lock should be held while sending the key.
    270      * @hide
    271      */
    272     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
    273         try {
    274             mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
    275         } catch (RemoteException e) {
    276             Log.e(TAG, "Failed to send key event.", e);
    277         }
    278     }
    279 
    280     /**
    281      * Dispatch an adjust volume request to the system. It will be sent to the
    282      * most relevant audio stream or media session. The direction must be one of
    283      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
    284      * {@link AudioManager#ADJUST_SAME}.
    285      *
    286      * @param suggestedStream The stream to fall back to if there isn't a
    287      *            relevant stream
    288      * @param direction The direction to adjust volume in.
    289      * @param flags Any flags to include with the volume change.
    290      * @hide
    291      */
    292     public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
    293         try {
    294             mService.dispatchAdjustVolume(suggestedStream, direction, flags);
    295         } catch (RemoteException e) {
    296             Log.e(TAG, "Failed to send adjust volume.", e);
    297         }
    298     }
    299 
    300     /**
    301      * Check if the global priority session is currently active. This can be
    302      * used to decide if media keys should be sent to the session or to the app.
    303      *
    304      * @hide
    305      */
    306     public boolean isGlobalPriorityActive() {
    307         try {
    308             return mService.isGlobalPriorityActive();
    309         } catch (RemoteException e) {
    310             Log.e(TAG, "Failed to check if the global priority is active.", e);
    311         }
    312         return false;
    313     }
    314 
    315     /**
    316      * Listens for changes to the list of active sessions. This can be added
    317      * using {@link #addOnActiveSessionsChangedListener}.
    318      */
    319     public interface OnActiveSessionsChangedListener {
    320         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
    321     }
    322 
    323     private static final class SessionsChangedWrapper {
    324         private Context mContext;
    325         private OnActiveSessionsChangedListener mListener;
    326         private Handler mHandler;
    327 
    328         public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
    329                 Handler handler) {
    330             mContext = context;
    331             mListener = listener;
    332             mHandler = handler;
    333         }
    334 
    335         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
    336             @Override
    337             public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
    338                 if (mHandler != null) {
    339                     mHandler.post(new Runnable() {
    340                         @Override
    341                         public void run() {
    342                             if (mListener != null) {
    343                                 ArrayList<MediaController> controllers
    344                                         = new ArrayList<MediaController>();
    345                                 int size = tokens.size();
    346                                 for (int i = 0; i < size; i++) {
    347                                     controllers.add(new MediaController(mContext, tokens.get(i)));
    348                                 }
    349                                 mListener.onActiveSessionsChanged(controllers);
    350                             }
    351                         }
    352                     });
    353                 }
    354             }
    355         };
    356 
    357         private void release() {
    358             mContext = null;
    359             mListener = null;
    360             mHandler = null;
    361         }
    362     }
    363 }
    364