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