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