MediaSessionService.java revision a5b02329209be355eafadbdf9ee685ffa58d3148
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 com.android.server.media; 18 19import android.Manifest; 20import android.app.ActivityManager; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.pm.PackageManager; 24import android.media.routeprovider.RouteRequest; 25import android.media.session.ISession; 26import android.media.session.ISessionCallback; 27import android.media.session.ISessionController; 28import android.media.session.ISessionManager; 29import android.media.session.PlaybackState; 30import android.media.session.RouteInfo; 31import android.media.session.RouteOptions; 32import android.os.Binder; 33import android.os.Handler; 34import android.os.IBinder; 35import android.os.Process; 36import android.os.RemoteException; 37import android.os.UserHandle; 38import android.provider.Settings; 39import android.text.TextUtils; 40import android.util.Log; 41 42import com.android.server.SystemService; 43import com.android.server.Watchdog; 44import com.android.server.Watchdog.Monitor; 45 46import java.io.FileDescriptor; 47import java.io.PrintWriter; 48import java.util.ArrayList; 49import java.util.List; 50 51/** 52 * System implementation of MediaSessionManager 53 */ 54public class MediaSessionService extends SystemService implements Monitor { 55 private static final String TAG = "MediaSessionService"; 56 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 57 58 private final SessionManagerImpl mSessionManagerImpl; 59 private final MediaRouteProviderWatcher mRouteProviderWatcher; 60 private final MediaSessionStack mPriorityStack; 61 62 private final ArrayList<MediaSessionRecord> mRecords = new ArrayList<MediaSessionRecord>(); 63 private final ArrayList<MediaRouteProviderProxy> mProviders 64 = new ArrayList<MediaRouteProviderProxy>(); 65 private final Object mLock = new Object(); 66 private final Handler mHandler = new Handler(); 67 68 private MediaSessionRecord mPrioritySession; 69 70 // Used to keep track of the current request to show routes for a specific 71 // session so we drop late callbacks properly. 72 private int mShowRoutesRequestId = 0; 73 74 // TODO refactor to have per user state for providers. See 75 // MediaRouterService for an example 76 77 public MediaSessionService(Context context) { 78 super(context); 79 mSessionManagerImpl = new SessionManagerImpl(); 80 mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback, 81 mHandler, context.getUserId()); 82 mPriorityStack = new MediaSessionStack(); 83 } 84 85 @Override 86 public void onStart() { 87 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); 88 mRouteProviderWatcher.start(); 89 Watchdog.getInstance().addMonitor(this); 90 } 91 92 /** 93 * Should trigger showing the Media route picker dialog. Right now it just 94 * kicks off a query to all the providers to get routes. 95 * 96 * @param record The session to show the picker for. 97 */ 98 public void showRoutePickerForSession(MediaSessionRecord record) { 99 // TODO for now just toggle the route to test (we will only have one 100 // match for now) 101 if (record.getRoute() != null) { 102 // For now send null to mean the local route 103 record.selectRoute(null); 104 return; 105 } 106 mShowRoutesRequestId++; 107 ArrayList<MediaRouteProviderProxy> providers = mRouteProviderWatcher.getProviders(); 108 for (int i = providers.size() - 1; i >= 0; i--) { 109 MediaRouteProviderProxy provider = providers.get(i); 110 provider.getRoutes(record, mShowRoutesRequestId); 111 } 112 } 113 114 /** 115 * Connect a session to the given route. 116 * 117 * @param session The session to connect. 118 * @param route The route to connect to. 119 * @param options The options to use for the connection. 120 */ 121 public void connectToRoute(MediaSessionRecord session, RouteInfo route, 122 RouteOptions options) { 123 synchronized (mLock) { 124 MediaRouteProviderProxy proxy = getProviderLocked(route.getProvider()); 125 if (proxy == null) { 126 Log.w(TAG, "Provider for route " + route.getName() + " does not exist."); 127 return; 128 } 129 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true); 130 // TODO make connect an async call to a ThreadPoolExecutor 131 proxy.connectToRoute(session, route, request); 132 } 133 } 134 135 public void updateSession(MediaSessionRecord record) { 136 synchronized (mLock) { 137 mPriorityStack.onSessionStateChange(record); 138 if (record.isSystemPriority()) { 139 if (record.isActive()) { 140 if (mPrioritySession != null) { 141 Log.w(TAG, "Replacing existing priority session with a new session"); 142 } 143 mPrioritySession = record; 144 } else { 145 if (mPrioritySession == record) { 146 mPrioritySession = null; 147 } 148 } 149 } 150 } 151 } 152 153 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 154 synchronized (mLock) { 155 mPriorityStack.onPlaystateChange(record, oldState, newState); 156 } 157 } 158 159 @Override 160 public void monitor() { 161 synchronized (mLock) { 162 // Check for deadlock 163 } 164 } 165 166 void sessionDied(MediaSessionRecord session) { 167 synchronized (mLock) { 168 destroySessionLocked(session); 169 } 170 } 171 172 void destroySession(MediaSessionRecord session) { 173 synchronized (mLock) { 174 destroySessionLocked(session); 175 } 176 } 177 178 private void destroySessionLocked(MediaSessionRecord session) { 179 mRecords.remove(session); 180 mPriorityStack.removeSession(session); 181 if (session == mPrioritySession) { 182 mPrioritySession = null; 183 } 184 } 185 186 private void enforcePackageName(String packageName, int uid) { 187 if (TextUtils.isEmpty(packageName)) { 188 throw new IllegalArgumentException("packageName may not be empty"); 189 } 190 String[] packages = getContext().getPackageManager().getPackagesForUid(uid); 191 final int packageCount = packages.length; 192 for (int i = 0; i < packageCount; i++) { 193 if (packageName.equals(packages[i])) { 194 return; 195 } 196 } 197 throw new IllegalArgumentException("packageName is not owned by the calling process"); 198 } 199 200 protected void enforcePhoneStatePermission(int pid, int uid) { 201 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) 202 != PackageManager.PERMISSION_GRANTED) { 203 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission."); 204 } 205 } 206 207 /** 208 * Checks a caller's authorization to register an IRemoteControlDisplay. 209 * Authorization is granted if one of the following is true: 210 * <ul> 211 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL 212 * permission</li> 213 * <li>the caller's listener is one of the enabled notification listeners 214 * for the caller's user</li> 215 * </ul> 216 */ 217 private void enforceMediaPermissions(ComponentName compName, int pid, int uid, 218 int resolvedUserId) { 219 if (getContext() 220 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) 221 != PackageManager.PERMISSION_GRANTED 222 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid), 223 resolvedUserId)) { 224 throw new SecurityException("Missing permission to control media."); 225 } 226 } 227 228 /** 229 * This checks if the component is an enabled notification listener for the 230 * specified user. Enabled components may only operate on behalf of the user 231 * they're running as. 232 * 233 * @param compName The component that is enabled. 234 * @param userId The user id of the caller. 235 * @param forUserId The user id they're making the request on behalf of. 236 * @return True if the component is enabled, false otherwise 237 */ 238 private boolean isEnabledNotificationListener(ComponentName compName, int userId, 239 int forUserId) { 240 if (userId != forUserId) { 241 // You may not access another user's content as an enabled listener. 242 return false; 243 } 244 if (compName != null) { 245 final String enabledNotifListeners = Settings.Secure.getStringForUser( 246 getContext().getContentResolver(), 247 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 248 userId); 249 if (enabledNotifListeners != null) { 250 final String[] components = enabledNotifListeners.split(":"); 251 for (int i = 0; i < components.length; i++) { 252 final ComponentName component = 253 ComponentName.unflattenFromString(components[i]); 254 if (component != null) { 255 if (compName.equals(component)) { 256 if (DEBUG) { 257 Log.d(TAG, "ok to get sessions: " + component + 258 " is authorized notification listener"); 259 } 260 return true; 261 } 262 } 263 } 264 } 265 if (DEBUG) { 266 Log.d(TAG, "not ok to get sessions, " + compName + 267 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId); 268 } 269 } 270 return false; 271 } 272 273 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, 274 String callerPackageName, ISessionCallback cb, String tag) { 275 synchronized (mLock) { 276 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag); 277 } 278 } 279 280 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId, 281 String callerPackageName, ISessionCallback cb, String tag) { 282 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId, 283 callerPackageName, cb, tag, this, mHandler); 284 try { 285 cb.asBinder().linkToDeath(session, 0); 286 } catch (RemoteException e) { 287 throw new RuntimeException("Media Session owner died prematurely.", e); 288 } 289 mRecords.add(session); 290 mPriorityStack.addSession(session); 291 if (DEBUG) { 292 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag); 293 } 294 return session; 295 } 296 297 private int findIndexOfSessionForIdLocked(String sessionId) { 298 for (int i = mRecords.size() - 1; i >= 0; i--) { 299 MediaSessionRecord session = mRecords.get(i); 300 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { 301 return i; 302 } 303 } 304 return -1; 305 } 306 307 private MediaRouteProviderProxy getProviderLocked(String providerId) { 308 for (int i = mProviders.size() - 1; i >= 0; i--) { 309 MediaRouteProviderProxy provider = mProviders.get(i); 310 if (TextUtils.equals(providerId, provider.getId())) { 311 return provider; 312 } 313 } 314 return null; 315 } 316 317 private boolean isSessionDiscoverable(MediaSessionRecord record) { 318 // TODO probably want to check more than if it's published. 319 return record.isActive(); 320 } 321 322 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback 323 = new MediaRouteProviderWatcher.Callback() { 324 @Override 325 public void removeProvider(MediaRouteProviderProxy provider) { 326 synchronized (mLock) { 327 mProviders.remove(provider); 328 provider.setRoutesListener(null); 329 provider.setInterested(false); 330 } 331 } 332 333 @Override 334 public void addProvider(MediaRouteProviderProxy provider) { 335 synchronized (mLock) { 336 mProviders.add(provider); 337 provider.setRoutesListener(mRoutesCallback); 338 provider.setInterested(true); 339 } 340 } 341 }; 342 343 private MediaRouteProviderProxy.RoutesListener mRoutesCallback 344 = new MediaRouteProviderProxy.RoutesListener() { 345 @Override 346 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes, 347 int reqId) { 348 // TODO for now select the first route to test, eventually add the 349 // new routes to the dialog if it is still open 350 synchronized (mLock) { 351 int index = findIndexOfSessionForIdLocked(sessionId); 352 if (index != -1 && routes != null && routes.size() > 0) { 353 MediaSessionRecord record = mRecords.get(index); 354 record.selectRoute(routes.get(0)); 355 } 356 } 357 } 358 359 @Override 360 public void onRouteConnected(String sessionId, RouteInfo route, 361 RouteRequest options, RouteConnectionRecord connection) { 362 synchronized (mLock) { 363 int index = findIndexOfSessionForIdLocked(sessionId); 364 if (index != -1) { 365 MediaSessionRecord session = mRecords.get(index); 366 session.setRouteConnected(route, options.getConnectionOptions(), connection); 367 } 368 } 369 } 370 }; 371 372 class SessionManagerImpl extends ISessionManager.Stub { 373 // TODO add createSessionAsUser, pass user-id to 374 // ActivityManagerNative.handleIncomingUser and stash result for use 375 // when starting services on that session's behalf. 376 @Override 377 public ISession createSession(String packageName, ISessionCallback cb, String tag, 378 int userId) throws RemoteException { 379 final int pid = Binder.getCallingPid(); 380 final int uid = Binder.getCallingUid(); 381 final long token = Binder.clearCallingIdentity(); 382 try { 383 enforcePackageName(packageName, uid); 384 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 385 false /* allowAll */, true /* requireFull */, "createSession", packageName); 386 if (cb == null) { 387 throw new IllegalArgumentException("Controller callback cannot be null"); 388 } 389 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag) 390 .getSessionBinder(); 391 } finally { 392 Binder.restoreCallingIdentity(token); 393 } 394 } 395 396 @Override 397 public List<IBinder> getSessions(ComponentName componentName, int userId) { 398 final int pid = Binder.getCallingPid(); 399 final int uid = Binder.getCallingUid(); 400 final long token = Binder.clearCallingIdentity(); 401 402 try { 403 String packageName = null; 404 if (componentName != null) { 405 // If they gave us a component name verify they own the 406 // package 407 packageName = componentName.getPackageName(); 408 enforcePackageName(packageName, uid); 409 } 410 // Check that they can make calls on behalf of the user and 411 // get the final user id 412 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 413 true /* allowAll */, true /* requireFull */, "getSessions", packageName); 414 // Check if they have the permissions or their component is 415 // enabled for the user they're calling from. 416 enforceMediaPermissions(componentName, pid, uid, resolvedUserId); 417 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 418 synchronized (mLock) { 419 ArrayList<MediaSessionRecord> records = mPriorityStack 420 .getActiveSessions(resolvedUserId); 421 int size = records.size(); 422 for (int i = 0; i < size; i++) { 423 binders.add(records.get(i).getControllerBinder().asBinder()); 424 } 425 } 426 return binders; 427 } finally { 428 Binder.restoreCallingIdentity(token); 429 } 430 } 431 432 @Override 433 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 434 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) 435 != PackageManager.PERMISSION_GRANTED) { 436 pw.println("Permission Denial: can't dump MediaSessionService from from pid=" 437 + Binder.getCallingPid() 438 + ", uid=" + Binder.getCallingUid()); 439 return; 440 } 441 442 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)"); 443 pw.println(); 444 445 synchronized (mLock) { 446 pw.println("Session for calls:" + mPrioritySession); 447 if (mPrioritySession != null) { 448 mPrioritySession.dump(pw, ""); 449 } 450 int count = mRecords.size(); 451 pw.println(count + " Sessions:"); 452 for (int i = 0; i < count; i++) { 453 mRecords.get(i).dump(pw, ""); 454 pw.println(); 455 } 456 mPriorityStack.dump(pw, ""); 457 458 pw.println("Providers:"); 459 count = mProviders.size(); 460 for (int i = 0; i < count; i++) { 461 MediaRouteProviderProxy provider = mProviders.get(i); 462 provider.dump(pw, ""); 463 } 464 } 465 } 466 } 467 468} 469