MediaSessionManager.java revision a66c40bf6e0fb79ead6d8a9fc29c5671fa7b1206
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 17package android.media.session; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.ComponentName; 22import android.content.Context; 23import android.media.AudioManager; 24import android.media.IRemoteVolumeController; 25import android.media.session.ISessionManager; 26import android.os.Handler; 27import android.os.IBinder; 28import android.os.RemoteException; 29import android.os.ServiceManager; 30import android.os.UserHandle; 31import android.service.notification.NotificationListenerService; 32import android.text.TextUtils; 33import android.util.ArrayMap; 34import android.util.Log; 35import android.view.KeyEvent; 36 37import java.util.ArrayList; 38import 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 */ 51public 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, 152 UserHandle.myUserId(), null); 153 } 154 155 /** 156 * Add a listener to be notified when the list of active sessions 157 * changes.This requires the 158 * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by 159 * the calling app. You may also retrieve this list if your app is an 160 * enabled notification listener using the 161 * {@link NotificationListenerService} APIs, in which case you must pass the 162 * {@link ComponentName} of your enabled listener. 163 * 164 * @param sessionListener The listener to add. 165 * @param notificationListener The enabled notification listener component. 166 * May be null. 167 * @param userId The userId to listen for changes on. 168 * @param handler The handler to post updates on. 169 * @hide 170 */ 171 public void addOnActiveSessionsChangedListener( 172 @NonNull OnActiveSessionsChangedListener sessionListener, 173 @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) { 174 if (sessionListener == null) { 175 throw new IllegalArgumentException("listener may not be null"); 176 } 177 if (handler == null) { 178 handler = new Handler(); 179 } 180 synchronized (mLock) { 181 if (mListeners.get(sessionListener) != null) { 182 Log.w(TAG, "Attempted to add session listener twice, ignoring."); 183 return; 184 } 185 SessionsChangedWrapper wrapper = new SessionsChangedWrapper(sessionListener, handler); 186 try { 187 mService.addSessionsListener(wrapper.mStub, notificationListener, userId); 188 mListeners.put(sessionListener, wrapper); 189 } catch (RemoteException e) { 190 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e); 191 } 192 } 193 } 194 195 /** 196 * Stop receiving active sessions updates on the specified listener. 197 * 198 * @param listener The listener to remove. 199 */ 200 public void removeOnActiveSessionsChangedListener( 201 @NonNull OnActiveSessionsChangedListener listener) { 202 if (listener == null) { 203 throw new IllegalArgumentException("listener may not be null"); 204 } 205 synchronized (mLock) { 206 SessionsChangedWrapper wrapper = mListeners.remove(listener); 207 if (wrapper != null) { 208 try { 209 mService.removeSessionsListener(wrapper.mStub); 210 } catch (RemoteException e) { 211 Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e); 212 } 213 } 214 } 215 } 216 217 /** 218 * Set the remote volume controller to receive volume updates on. Only for 219 * use by system UI. 220 * 221 * @param rvc The volume controller to receive updates on. 222 * @hide 223 */ 224 public void setRemoteVolumeController(IRemoteVolumeController rvc) { 225 try { 226 mService.setRemoteVolumeController(rvc); 227 } catch (RemoteException e) { 228 Log.e(TAG, "Error in setRemoteVolumeController.", e); 229 } 230 } 231 232 /** 233 * Send a media key event. The receiver will be selected automatically. 234 * 235 * @param keyEvent The KeyEvent to send. 236 * @hide 237 */ 238 public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) { 239 dispatchMediaKeyEvent(keyEvent, false); 240 } 241 242 /** 243 * Send a media key event. The receiver will be selected automatically. 244 * 245 * @param keyEvent The KeyEvent to send. 246 * @param needWakeLock True if a wake lock should be held while sending the key. 247 * @hide 248 */ 249 public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) { 250 try { 251 mService.dispatchMediaKeyEvent(keyEvent, needWakeLock); 252 } catch (RemoteException e) { 253 Log.e(TAG, "Failed to send key event.", e); 254 } 255 } 256 257 /** 258 * Dispatch an adjust volume request to the system. It will be sent to the 259 * most relevant audio stream or media session. The direction must be one of 260 * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, 261 * {@link AudioManager#ADJUST_SAME}. 262 * 263 * @param suggestedStream The stream to fall back to if there isn't a 264 * relevant stream 265 * @param direction The direction to adjust volume in. 266 * @param flags Any flags to include with the volume change. 267 * @hide 268 */ 269 public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) { 270 try { 271 mService.dispatchAdjustVolume(suggestedStream, direction, flags); 272 } catch (RemoteException e) { 273 Log.e(TAG, "Failed to send adjust volume.", e); 274 } 275 } 276 277 /** 278 * Listens for changes to the list of active sessions. This can be added 279 * using {@link #addOnActiveSessionsChangedListener}. 280 */ 281 public interface OnActiveSessionsChangedListener { 282 public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); 283 } 284 285 private final class SessionsChangedWrapper { 286 private final OnActiveSessionsChangedListener mListener; 287 private final Handler mHandler; 288 289 public SessionsChangedWrapper(OnActiveSessionsChangedListener listener, Handler handler) { 290 mListener = listener; 291 mHandler = handler; 292 } 293 294 private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() { 295 @Override 296 public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) { 297 if (mHandler != null) { 298 mHandler.post(new Runnable() { 299 @Override 300 public void run() { 301 ArrayList<MediaController> controllers 302 = new ArrayList<MediaController>(); 303 int size = tokens.size(); 304 for (int i = 0; i < size; i++) { 305 controllers.add(new MediaController(mContext, tokens.get(i))); 306 } 307 mListener.onActiveSessionsChanged(controllers); 308 } 309 }); 310 } 311 } 312 }; 313 } 314} 315