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 */ MediaSessionManager(Context context)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 */ createSession(@onNull MediaSession.CallbackStub cbStub, @NonNull String tag, int userId)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 */ getActiveSessions( @ullable ComponentName notificationListener)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 */ getActiveSessionsForUser( @ullable ComponentName notificationListener, int userId)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 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener)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 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler)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 */ addOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler)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 */ removeOnActiveSessionsChangedListener( @onNull OnActiveSessionsChangedListener listener)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 */ setRemoteVolumeController(IRemoteVolumeController rvc)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 */ dispatchMediaKeyEvent(@onNull KeyEvent keyEvent)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 */ dispatchMediaKeyEvent(@onNull KeyEvent keyEvent, boolean needWakeLock)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 */ dispatchAdjustVolume(int suggestedStream, int direction, int flags)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 */ isGlobalPriorityActive()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 { onActiveSessionsChanged(@ullable List<MediaController> controllers)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 SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, Handler handler)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 release()357 private void release() { 358 mContext = null; 359 mListener = null; 360 mHandler = null; 361 } 362 } 363 } 364