MediaSessionService.java revision 4646d288821d62fdfe481be67d8b7fed7d7eabd8
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.ISessionManager; 28import android.media.session.RouteInfo; 29import android.media.session.RouteOptions; 30import android.media.session.Session; 31import android.os.Binder; 32import android.os.Handler; 33import android.os.IBinder; 34import android.os.RemoteException; 35import android.os.UserHandle; 36import android.provider.Settings; 37import android.text.TextUtils; 38import android.util.Log; 39import android.util.SparseArray; 40 41import com.android.server.SystemService; 42import com.android.server.Watchdog; 43import com.android.server.Watchdog.Monitor; 44 45import java.io.FileDescriptor; 46import java.io.PrintWriter; 47import java.util.ArrayList; 48import java.util.List; 49 50/** 51 * System implementation of MediaSessionManager 52 */ 53public class MediaSessionService extends SystemService implements Monitor { 54 private static final String TAG = "MediaSessionService"; 55 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 56 57 private final SessionManagerImpl mSessionManagerImpl; 58 // private final MediaRouteProviderWatcher mRouteProviderWatcher; 59 private final MediaSessionStack mPriorityStack; 60 61 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>(); 62 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); 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 private int mCurrentUserId = -1; 70 71 // Used to keep track of the current request to show routes for a specific 72 // session so we drop late callbacks properly. 73 private int mShowRoutesRequestId = 0; 74 75 // TODO refactor to have per user state for providers. See 76 // MediaRouterService for an example 77 78 public MediaSessionService(Context context) { 79 super(context); 80 mSessionManagerImpl = new SessionManagerImpl(); 81 mPriorityStack = new MediaSessionStack(); 82 } 83 84 @Override 85 public void onStart() { 86 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); 87 Watchdog.getInstance().addMonitor(this); 88 updateUser(); 89 } 90 91 /** 92 * Should trigger showing the Media route picker dialog. Right now it just 93 * kicks off a query to all the providers to get routes. 94 * 95 * @param record The session to show the picker for. 96 */ 97 public void showRoutePickerForSession(MediaSessionRecord record) { 98 // TODO for now just toggle the route to test (we will only have one 99 // match for now) 100 synchronized (mLock) { 101 if (!mAllSessions.contains(record)) { 102 Log.d(TAG, "Unknown session tried to show route picker. Ignoring."); 103 return; 104 } 105 RouteInfo current = record.getRoute(); 106 UserRecord user = mUserRecords.get(record.getUserId()); 107 if (current != null) { 108 // For now send null to mean the local route 109 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider()); 110 if (proxy != null) { 111 proxy.removeSession(record); 112 } 113 record.selectRoute(null); 114 return; 115 } 116 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked(); 117 mShowRoutesRequestId++; 118 for (int i = providers.size() - 1; i >= 0; i--) { 119 MediaRouteProviderProxy provider = providers.get(i); 120 provider.getRoutes(record, mShowRoutesRequestId); 121 } 122 } 123 } 124 125 /** 126 * Connect a session to the given route. 127 * 128 * @param session The session to connect. 129 * @param route The route to connect to. 130 * @param options The options to use for the connection. 131 */ 132 public void connectToRoute(MediaSessionRecord session, RouteInfo route, 133 RouteOptions options) { 134 synchronized (mLock) { 135 if (!mAllSessions.contains(session)) { 136 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring"); 137 return; 138 } 139 UserRecord user = mUserRecords.get(session.getUserId()); 140 if (user == null) { 141 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist."); 142 return; 143 } 144 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider()); 145 if (proxy == null) { 146 Log.w(TAG, "Provider for route " + route.getName() + " does not exist."); 147 return; 148 } 149 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true); 150 proxy.connectToRoute(session, route, request); 151 } 152 } 153 154 public void updateSession(MediaSessionRecord record) { 155 synchronized (mLock) { 156 if (!mAllSessions.contains(record)) { 157 Log.d(TAG, "Unknown session updated. Ignoring."); 158 return; 159 } 160 mPriorityStack.onSessionStateChange(record); 161 if (record.isSystemPriority()) { 162 if (record.isActive()) { 163 if (mPrioritySession != null) { 164 Log.w(TAG, "Replacing existing priority session with a new session"); 165 } 166 mPrioritySession = record; 167 } else { 168 if (mPrioritySession == record) { 169 mPrioritySession = null; 170 } 171 } 172 } 173 } 174 } 175 176 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 177 synchronized (mLock) { 178 if (!mAllSessions.contains(record)) { 179 Log.d(TAG, "Unknown session changed playback state. Ignoring."); 180 return; 181 } 182 mPriorityStack.onPlaystateChange(record, oldState, newState); 183 } 184 } 185 186 @Override 187 public void onStartUser(int userHandle) { 188 updateUser(); 189 } 190 191 @Override 192 public void onSwitchUser(int userHandle) { 193 updateUser(); 194 } 195 196 @Override 197 public void onStopUser(int userHandle) { 198 synchronized (mLock) { 199 UserRecord user = mUserRecords.get(userHandle); 200 if (user != null) { 201 destroyUserLocked(user); 202 } 203 } 204 } 205 206 @Override 207 public void monitor() { 208 synchronized (mLock) { 209 // Check for deadlock 210 } 211 } 212 213 protected void enforcePhoneStatePermission(int pid, int uid) { 214 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) 215 != PackageManager.PERMISSION_GRANTED) { 216 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission."); 217 } 218 } 219 220 void sessionDied(MediaSessionRecord session) { 221 synchronized (mLock) { 222 destroySessionLocked(session); 223 } 224 } 225 226 void destroySession(MediaSessionRecord session) { 227 synchronized (mLock) { 228 destroySessionLocked(session); 229 } 230 } 231 232 private void updateUser() { 233 synchronized (mLock) { 234 int userId = ActivityManager.getCurrentUser(); 235 if (mCurrentUserId != userId) { 236 final int oldUserId = mCurrentUserId; 237 mCurrentUserId = userId; // do this first 238 239 UserRecord oldUser = mUserRecords.get(oldUserId); 240 if (oldUser != null) { 241 oldUser.stopLocked(); 242 } 243 244 UserRecord newUser = getOrCreateUser(userId); 245 newUser.startLocked(); 246 } 247 } 248 } 249 250 /** 251 * Stop the user and unbind from everything. 252 * 253 * @param user The user to dispose of 254 */ 255 private void destroyUserLocked(UserRecord user) { 256 user.stopLocked(); 257 user.destroyLocked(); 258 mUserRecords.remove(user.mUserId); 259 } 260 261 /* 262 * When a session is removed several things need to happen. 263 * 1. We need to remove it from the relevant user. 264 * 2. We need to remove it from the priority stack. 265 * 3. We need to remove it from all sessions. 266 * 4. If this is the system priority session we need to clear it. 267 * 5. We need to unlink to death from the cb binder 268 * 6. We need to tell the session to do any final cleanup (onDestroy) 269 */ 270 private void destroySessionLocked(MediaSessionRecord session) { 271 int userId = session.getUserId(); 272 UserRecord user = mUserRecords.get(userId); 273 if (user != null) { 274 user.removeSessionLocked(session); 275 } 276 277 mPriorityStack.removeSession(session); 278 mAllSessions.remove(session); 279 if (session == mPrioritySession) { 280 mPrioritySession = null; 281 } 282 283 try { 284 session.getCallback().asBinder().unlinkToDeath(session, 0); 285 } catch (Exception e) { 286 // ignore exceptions while destroying a session. 287 } 288 session.onDestroy(); 289 } 290 291 private void enforcePackageName(String packageName, int uid) { 292 if (TextUtils.isEmpty(packageName)) { 293 throw new IllegalArgumentException("packageName may not be empty"); 294 } 295 String[] packages = getContext().getPackageManager().getPackagesForUid(uid); 296 final int packageCount = packages.length; 297 for (int i = 0; i < packageCount; i++) { 298 if (packageName.equals(packages[i])) { 299 return; 300 } 301 } 302 throw new IllegalArgumentException("packageName is not owned by the calling process"); 303 } 304 305 /** 306 * Checks a caller's authorization to register an IRemoteControlDisplay. 307 * Authorization is granted if one of the following is true: 308 * <ul> 309 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL 310 * permission</li> 311 * <li>the caller's listener is one of the enabled notification listeners 312 * for the caller's user</li> 313 * </ul> 314 */ 315 private void enforceMediaPermissions(ComponentName compName, int pid, int uid, 316 int resolvedUserId) { 317 if (getContext() 318 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) 319 != PackageManager.PERMISSION_GRANTED 320 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid), 321 resolvedUserId)) { 322 throw new SecurityException("Missing permission to control media."); 323 } 324 } 325 326 /** 327 * This checks if the component is an enabled notification listener for the 328 * specified user. Enabled components may only operate on behalf of the user 329 * they're running as. 330 * 331 * @param compName The component that is enabled. 332 * @param userId The user id of the caller. 333 * @param forUserId The user id they're making the request on behalf of. 334 * @return True if the component is enabled, false otherwise 335 */ 336 private boolean isEnabledNotificationListener(ComponentName compName, int userId, 337 int forUserId) { 338 if (userId != forUserId) { 339 // You may not access another user's content as an enabled listener. 340 return false; 341 } 342 if (compName != null) { 343 final String enabledNotifListeners = Settings.Secure.getStringForUser( 344 getContext().getContentResolver(), 345 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 346 userId); 347 if (enabledNotifListeners != null) { 348 final String[] components = enabledNotifListeners.split(":"); 349 for (int i = 0; i < components.length; i++) { 350 final ComponentName component = 351 ComponentName.unflattenFromString(components[i]); 352 if (component != null) { 353 if (compName.equals(component)) { 354 if (DEBUG) { 355 Log.d(TAG, "ok to get sessions: " + component + 356 " is authorized notification listener"); 357 } 358 return true; 359 } 360 } 361 } 362 } 363 if (DEBUG) { 364 Log.d(TAG, "not ok to get sessions, " + compName + 365 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId); 366 } 367 } 368 return false; 369 } 370 371 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, 372 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException { 373 synchronized (mLock) { 374 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag); 375 } 376 } 377 378 /* 379 * When a session is created the following things need to happen. 380 * 1. It's callback binder needs a link to death 381 * 2. It needs to be added to all sessions. 382 * 3. It needs to be added to the priority stack. 383 * 4. It needs to be added to the relevant user record. 384 */ 385 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId, 386 String callerPackageName, ISessionCallback cb, String tag) { 387 388 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId, 389 callerPackageName, cb, tag, this, mHandler); 390 try { 391 cb.asBinder().linkToDeath(session, 0); 392 } catch (RemoteException e) { 393 throw new RuntimeException("Media Session owner died prematurely.", e); 394 } 395 396 mAllSessions.add(session); 397 mPriorityStack.addSession(session); 398 399 UserRecord user = getOrCreateUser(userId); 400 user.addSessionLocked(session); 401 402 if (DEBUG) { 403 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag); 404 } 405 return session; 406 } 407 408 private UserRecord getOrCreateUser(int userId) { 409 UserRecord user = mUserRecords.get(userId); 410 if (user == null) { 411 user = new UserRecord(getContext(), userId); 412 mUserRecords.put(userId, user); 413 } 414 return user; 415 } 416 417 private int findIndexOfSessionForIdLocked(String sessionId) { 418 for (int i = mAllSessions.size() - 1; i >= 0; i--) { 419 MediaSessionRecord session = mAllSessions.get(i); 420 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { 421 return i; 422 } 423 } 424 return -1; 425 } 426 427 private boolean isSessionDiscoverable(MediaSessionRecord record) { 428 // TODO probably want to check more than if it's active. 429 return record.isActive(); 430 } 431 432 private MediaRouteProviderProxy.RoutesListener mRoutesCallback 433 = new MediaRouteProviderProxy.RoutesListener() { 434 @Override 435 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes, 436 int reqId) { 437 // TODO for now select the first route to test, eventually add the 438 // new routes to the dialog if it is still open 439 synchronized (mLock) { 440 int index = findIndexOfSessionForIdLocked(sessionId); 441 if (index != -1 && routes != null && routes.size() > 0) { 442 MediaSessionRecord record = mAllSessions.get(index); 443 RouteInfo route = routes.get(0); 444 record.selectRoute(route); 445 UserRecord user = mUserRecords.get(record.getUserId()); 446 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider()); 447 provider.addSession(record); 448 } 449 } 450 } 451 452 @Override 453 public void onRouteConnected(String sessionId, RouteInfo route, 454 RouteRequest options, RouteConnectionRecord connection) { 455 synchronized (mLock) { 456 int index = findIndexOfSessionForIdLocked(sessionId); 457 if (index != -1) { 458 MediaSessionRecord session = mAllSessions.get(index); 459 session.setRouteConnected(route, options.getConnectionOptions(), connection); 460 } 461 } 462 } 463 }; 464 465 /** 466 * Information about a particular user. The contents of this object is 467 * guarded by mLock. 468 */ 469 final class UserRecord { 470 private final int mUserId; 471 private final MediaRouteProviderWatcher mRouteProviderWatcher; 472 private final ArrayList<MediaRouteProviderProxy> mProviders 473 = new ArrayList<MediaRouteProviderProxy>(); 474 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); 475 476 public UserRecord(Context context, int userId) { 477 mUserId = userId; 478 mRouteProviderWatcher = new MediaRouteProviderWatcher(context, 479 mProviderWatcherCallback, mHandler, userId); 480 } 481 482 public void startLocked() { 483 mRouteProviderWatcher.start(); 484 } 485 486 public void stopLocked() { 487 mRouteProviderWatcher.stop(); 488 updateInterestLocked(); 489 } 490 491 public void destroyLocked() { 492 for (int i = mSessions.size() - 1; i >= 0; i--) { 493 MediaSessionRecord session = mSessions.get(i); 494 MediaSessionService.this.destroySessionLocked(session); 495 if (session.isConnected()) { 496 session.disconnect(Session.DISCONNECT_REASON_USER_STOPPING); 497 } 498 } 499 } 500 501 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() { 502 return mProviders; 503 } 504 505 public ArrayList<MediaSessionRecord> getSessionsLocked() { 506 return mSessions; 507 } 508 509 public void addSessionLocked(MediaSessionRecord session) { 510 mSessions.add(session); 511 updateInterestLocked(); 512 } 513 514 public void removeSessionLocked(MediaSessionRecord session) { 515 mSessions.remove(session); 516 RouteInfo route = session.getRoute(); 517 if (route != null) { 518 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider()); 519 if (provider != null) { 520 provider.removeSession(session); 521 } 522 } 523 updateInterestLocked(); 524 } 525 526 public void dumpLocked(PrintWriter pw, String prefix) { 527 pw.println(prefix + "Record for user " + mUserId); 528 String indent = prefix + " "; 529 int size = mProviders.size(); 530 pw.println(indent + size + " Providers:"); 531 for (int i = 0; i < size; i++) { 532 mProviders.get(i).dump(pw, indent); 533 } 534 pw.println(); 535 size = mSessions.size(); 536 pw.println(indent + size + " Sessions:"); 537 for (int i = 0; i < size; i++) { 538 // Just print the session info, the full session dump will 539 // already be in the list of all sessions. 540 pw.println(indent + mSessions.get(i).getSessionInfo()); 541 } 542 } 543 544 public void updateInterestLocked() { 545 // TODO go through the sessions and build up the set of interfaces 546 // we're interested in. Update the provider watcher. 547 // For now, just express interest in all providers for the current 548 // user 549 boolean interested = mUserId == mCurrentUserId; 550 for (int i = mProviders.size() - 1; i >= 0; i--) { 551 mProviders.get(i).setInterested(interested); 552 } 553 } 554 555 private MediaRouteProviderProxy getProviderLocked(String providerId) { 556 for (int i = mProviders.size() - 1; i >= 0; i--) { 557 MediaRouteProviderProxy provider = mProviders.get(i); 558 if (TextUtils.equals(providerId, provider.getId())) { 559 return provider; 560 } 561 } 562 return null; 563 } 564 565 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback 566 = new MediaRouteProviderWatcher.Callback() { 567 @Override 568 public void removeProvider(MediaRouteProviderProxy provider) { 569 synchronized (mLock) { 570 mProviders.remove(provider); 571 provider.setRoutesListener(null); 572 provider.setInterested(false); 573 } 574 } 575 576 @Override 577 public void addProvider(MediaRouteProviderProxy provider) { 578 synchronized (mLock) { 579 mProviders.add(provider); 580 provider.setRoutesListener(mRoutesCallback); 581 provider.setInterested(true); 582 } 583 } 584 }; 585 } 586 587 class SessionManagerImpl extends ISessionManager.Stub { 588 // TODO add createSessionAsUser, pass user-id to 589 // ActivityManagerNative.handleIncomingUser and stash result for use 590 // when starting services on that session's behalf. 591 @Override 592 public ISession createSession(String packageName, ISessionCallback cb, String tag, 593 int userId) throws RemoteException { 594 final int pid = Binder.getCallingPid(); 595 final int uid = Binder.getCallingUid(); 596 final long token = Binder.clearCallingIdentity(); 597 try { 598 enforcePackageName(packageName, uid); 599 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 600 false /* allowAll */, true /* requireFull */, "createSession", packageName); 601 if (cb == null) { 602 throw new IllegalArgumentException("Controller callback cannot be null"); 603 } 604 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag) 605 .getSessionBinder(); 606 } finally { 607 Binder.restoreCallingIdentity(token); 608 } 609 } 610 611 @Override 612 public List<IBinder> getSessions(ComponentName componentName, int userId) { 613 final int pid = Binder.getCallingPid(); 614 final int uid = Binder.getCallingUid(); 615 final long token = Binder.clearCallingIdentity(); 616 617 try { 618 String packageName = null; 619 if (componentName != null) { 620 // If they gave us a component name verify they own the 621 // package 622 packageName = componentName.getPackageName(); 623 enforcePackageName(packageName, uid); 624 } 625 // Check that they can make calls on behalf of the user and 626 // get the final user id 627 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 628 true /* allowAll */, true /* requireFull */, "getSessions", packageName); 629 // Check if they have the permissions or their component is 630 // enabled for the user they're calling from. 631 enforceMediaPermissions(componentName, pid, uid, resolvedUserId); 632 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 633 synchronized (mLock) { 634 ArrayList<MediaSessionRecord> records = mPriorityStack 635 .getActiveSessions(resolvedUserId); 636 int size = records.size(); 637 for (int i = 0; i < size; i++) { 638 binders.add(records.get(i).getControllerBinder().asBinder()); 639 } 640 } 641 return binders; 642 } finally { 643 Binder.restoreCallingIdentity(token); 644 } 645 } 646 647 @Override 648 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 649 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) 650 != PackageManager.PERMISSION_GRANTED) { 651 pw.println("Permission Denial: can't dump MediaSessionService from from pid=" 652 + Binder.getCallingPid() 653 + ", uid=" + Binder.getCallingUid()); 654 return; 655 } 656 657 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)"); 658 pw.println(); 659 660 synchronized (mLock) { 661 pw.println("Session for calls:" + mPrioritySession); 662 if (mPrioritySession != null) { 663 mPrioritySession.dump(pw, ""); 664 } 665 int count = mAllSessions.size(); 666 pw.println(count + " Sessions:"); 667 for (int i = 0; i < count; i++) { 668 mAllSessions.get(i).dump(pw, ""); 669 pw.println(); 670 } 671 mPriorityStack.dump(pw, ""); 672 673 pw.println("User Records:"); 674 count = mUserRecords.size(); 675 for (int i = 0; i < count; i++) { 676 UserRecord user = mUserRecords.get(i); 677 user.dumpLocked(pw, ""); 678 } 679 } 680 } 681 } 682 683} 684