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