MediaSessionService.java revision 2e7a9167aeefeb451f8d8c769175b9a0163744f3
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.Activity; 21import android.app.ActivityManager; 22import android.app.KeyguardManager; 23import android.content.ActivityNotFoundException; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.PackageManager; 29import android.media.AudioManager; 30import android.media.IAudioService; 31import android.media.routeprovider.RouteRequest; 32import android.media.session.IActiveSessionsListener; 33import android.media.session.ISession; 34import android.media.session.ISessionCallback; 35import android.media.session.ISessionManager; 36import android.media.session.MediaSessionToken; 37import android.media.session.RouteInfo; 38import android.media.session.RouteOptions; 39import android.media.session.MediaSession; 40import android.os.Binder; 41import android.os.Bundle; 42import android.os.Handler; 43import android.os.IBinder; 44import android.os.Message; 45import android.os.PowerManager; 46import android.os.RemoteException; 47import android.os.ResultReceiver; 48import android.os.ServiceManager; 49import android.os.UserHandle; 50import android.provider.Settings; 51import android.speech.RecognizerIntent; 52import android.text.TextUtils; 53import android.util.Log; 54import android.util.SparseArray; 55import android.view.KeyEvent; 56 57import com.android.server.SystemService; 58import com.android.server.Watchdog; 59import com.android.server.Watchdog.Monitor; 60 61import java.io.FileDescriptor; 62import java.io.PrintWriter; 63import java.util.ArrayList; 64import java.util.List; 65 66/** 67 * System implementation of MediaSessionManager 68 */ 69public class MediaSessionService extends SystemService implements Monitor { 70 private static final String TAG = "MediaSessionService"; 71 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 72 73 private static final int WAKELOCK_TIMEOUT = 5000; 74 75 private final SessionManagerImpl mSessionManagerImpl; 76 // private final MediaRouteProviderWatcher mRouteProviderWatcher; 77 private final MediaSessionStack mPriorityStack; 78 79 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>(); 80 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); 81 private final ArrayList<SessionsListenerRecord> mSessionsListeners 82 = new ArrayList<SessionsListenerRecord>(); 83 // private final ArrayList<MediaRouteProviderProxy> mProviders 84 // = new ArrayList<MediaRouteProviderProxy>(); 85 private final Object mLock = new Object(); 86 private final MessageHandler mHandler = new MessageHandler(); 87 private final PowerManager.WakeLock mMediaEventWakeLock; 88 89 private KeyguardManager mKeyguardManager; 90 private IAudioService mAudioService; 91 92 private MediaSessionRecord mPrioritySession; 93 private int mCurrentUserId = -1; 94 95 // Used to keep track of the current request to show routes for a specific 96 // session so we drop late callbacks properly. 97 private int mShowRoutesRequestId = 0; 98 99 // TODO refactor to have per user state for providers. See 100 // MediaRouterService for an example 101 102 public MediaSessionService(Context context) { 103 super(context); 104 mSessionManagerImpl = new SessionManagerImpl(); 105 mPriorityStack = new MediaSessionStack(); 106 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 107 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); 108 } 109 110 @Override 111 public void onStart() { 112 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); 113 Watchdog.getInstance().addMonitor(this); 114 updateUser(); 115 mKeyguardManager = 116 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); 117 mAudioService = getAudioService(); 118 } 119 120 private IAudioService getAudioService() { 121 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 122 return IAudioService.Stub.asInterface(b); 123 } 124 125 /** 126 * Should trigger showing the Media route picker dialog. Right now it just 127 * kicks off a query to all the providers to get routes. 128 * 129 * @param record The session to show the picker for. 130 */ 131 public void showRoutePickerForSession(MediaSessionRecord record) { 132 // TODO for now just toggle the route to test (we will only have one 133 // match for now) 134 synchronized (mLock) { 135 if (!mAllSessions.contains(record)) { 136 Log.d(TAG, "Unknown session tried to show route picker. Ignoring."); 137 return; 138 } 139 RouteInfo current = record.getRoute(); 140 UserRecord user = mUserRecords.get(record.getUserId()); 141 if (current != null) { 142 // For now send null to mean the local route 143 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider()); 144 if (proxy != null) { 145 proxy.removeSession(record); 146 } 147 record.selectRoute(null); 148 return; 149 } 150 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked(); 151 mShowRoutesRequestId++; 152 for (int i = providers.size() - 1; i >= 0; i--) { 153 MediaRouteProviderProxy provider = providers.get(i); 154 provider.getRoutes(record, mShowRoutesRequestId); 155 } 156 } 157 } 158 159 /** 160 * Connect a session to the given route. 161 * 162 * @param session The session to connect. 163 * @param route The route to connect to. 164 * @param options The options to use for the connection. 165 */ 166 public void connectToRoute(MediaSessionRecord session, RouteInfo route, 167 RouteOptions options) { 168 synchronized (mLock) { 169 if (!mAllSessions.contains(session)) { 170 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring"); 171 return; 172 } 173 UserRecord user = mUserRecords.get(session.getUserId()); 174 if (user == null) { 175 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist."); 176 return; 177 } 178 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider()); 179 if (proxy == null) { 180 Log.w(TAG, "Provider for route " + route.getName() + " does not exist."); 181 return; 182 } 183 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true); 184 proxy.connectToRoute(session, route, request); 185 } 186 } 187 188 public void updateSession(MediaSessionRecord record) { 189 synchronized (mLock) { 190 if (!mAllSessions.contains(record)) { 191 Log.d(TAG, "Unknown session updated. Ignoring."); 192 return; 193 } 194 mPriorityStack.onSessionStateChange(record); 195 if (record.isSystemPriority()) { 196 if (record.isActive()) { 197 if (mPrioritySession != null) { 198 Log.w(TAG, "Replacing existing priority session with a new session"); 199 } 200 mPrioritySession = record; 201 } else { 202 if (mPrioritySession == record) { 203 mPrioritySession = null; 204 } 205 } 206 } 207 } 208 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0); 209 } 210 211 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 212 boolean updateSessions = false; 213 synchronized (mLock) { 214 if (!mAllSessions.contains(record)) { 215 Log.d(TAG, "Unknown session changed playback state. Ignoring."); 216 return; 217 } 218 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState); 219 } 220 if (updateSessions) { 221 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0); 222 } 223 } 224 225 @Override 226 public void onStartUser(int userHandle) { 227 updateUser(); 228 } 229 230 @Override 231 public void onSwitchUser(int userHandle) { 232 updateUser(); 233 } 234 235 @Override 236 public void onStopUser(int userHandle) { 237 synchronized (mLock) { 238 UserRecord user = mUserRecords.get(userHandle); 239 if (user != null) { 240 destroyUserLocked(user); 241 } 242 } 243 } 244 245 @Override 246 public void monitor() { 247 synchronized (mLock) { 248 // Check for deadlock 249 } 250 } 251 252 protected void enforcePhoneStatePermission(int pid, int uid) { 253 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) 254 != PackageManager.PERMISSION_GRANTED) { 255 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission."); 256 } 257 } 258 259 void sessionDied(MediaSessionRecord session) { 260 synchronized (mLock) { 261 destroySessionLocked(session); 262 } 263 } 264 265 void destroySession(MediaSessionRecord session) { 266 synchronized (mLock) { 267 destroySessionLocked(session); 268 } 269 } 270 271 private void updateUser() { 272 synchronized (mLock) { 273 int userId = ActivityManager.getCurrentUser(); 274 if (mCurrentUserId != userId) { 275 final int oldUserId = mCurrentUserId; 276 mCurrentUserId = userId; // do this first 277 278 UserRecord oldUser = mUserRecords.get(oldUserId); 279 if (oldUser != null) { 280 oldUser.stopLocked(); 281 } 282 283 UserRecord newUser = getOrCreateUser(userId); 284 newUser.startLocked(); 285 } 286 } 287 } 288 289 /** 290 * Stop the user and unbind from everything. 291 * 292 * @param user The user to dispose of 293 */ 294 private void destroyUserLocked(UserRecord user) { 295 user.stopLocked(); 296 user.destroyLocked(); 297 mUserRecords.remove(user.mUserId); 298 } 299 300 /* 301 * When a session is removed several things need to happen. 302 * 1. We need to remove it from the relevant user. 303 * 2. We need to remove it from the priority stack. 304 * 3. We need to remove it from all sessions. 305 * 4. If this is the system priority session we need to clear it. 306 * 5. We need to unlink to death from the cb binder 307 * 6. We need to tell the session to do any final cleanup (onDestroy) 308 */ 309 private void destroySessionLocked(MediaSessionRecord session) { 310 int userId = session.getUserId(); 311 UserRecord user = mUserRecords.get(userId); 312 if (user != null) { 313 user.removeSessionLocked(session); 314 } 315 316 mPriorityStack.removeSession(session); 317 mAllSessions.remove(session); 318 if (session == mPrioritySession) { 319 mPrioritySession = null; 320 } 321 322 try { 323 session.getCallback().asBinder().unlinkToDeath(session, 0); 324 } catch (Exception e) { 325 // ignore exceptions while destroying a session. 326 } 327 session.onDestroy(); 328 329 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0); 330 } 331 332 private void enforcePackageName(String packageName, int uid) { 333 if (TextUtils.isEmpty(packageName)) { 334 throw new IllegalArgumentException("packageName may not be empty"); 335 } 336 String[] packages = getContext().getPackageManager().getPackagesForUid(uid); 337 final int packageCount = packages.length; 338 for (int i = 0; i < packageCount; i++) { 339 if (packageName.equals(packages[i])) { 340 return; 341 } 342 } 343 throw new IllegalArgumentException("packageName is not owned by the calling process"); 344 } 345 346 /** 347 * Checks a caller's authorization to register an IRemoteControlDisplay. 348 * Authorization is granted if one of the following is true: 349 * <ul> 350 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL 351 * permission</li> 352 * <li>the caller's listener is one of the enabled notification listeners 353 * for the caller's user</li> 354 * </ul> 355 */ 356 private void enforceMediaPermissions(ComponentName compName, int pid, int uid, 357 int resolvedUserId) { 358 if (getContext() 359 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) 360 != PackageManager.PERMISSION_GRANTED 361 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid), 362 resolvedUserId)) { 363 throw new SecurityException("Missing permission to control media."); 364 } 365 } 366 367 /** 368 * This checks if the component is an enabled notification listener for the 369 * specified user. Enabled components may only operate on behalf of the user 370 * they're running as. 371 * 372 * @param compName The component that is enabled. 373 * @param userId The user id of the caller. 374 * @param forUserId The user id they're making the request on behalf of. 375 * @return True if the component is enabled, false otherwise 376 */ 377 private boolean isEnabledNotificationListener(ComponentName compName, int userId, 378 int forUserId) { 379 if (userId != forUserId) { 380 // You may not access another user's content as an enabled listener. 381 return false; 382 } 383 if (compName != null) { 384 final String enabledNotifListeners = Settings.Secure.getStringForUser( 385 getContext().getContentResolver(), 386 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 387 userId); 388 if (enabledNotifListeners != null) { 389 final String[] components = enabledNotifListeners.split(":"); 390 for (int i = 0; i < components.length; i++) { 391 final ComponentName component = 392 ComponentName.unflattenFromString(components[i]); 393 if (component != null) { 394 if (compName.equals(component)) { 395 if (DEBUG) { 396 Log.d(TAG, "ok to get sessions: " + component + 397 " is authorized notification listener"); 398 } 399 return true; 400 } 401 } 402 } 403 } 404 if (DEBUG) { 405 Log.d(TAG, "not ok to get sessions, " + compName + 406 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId); 407 } 408 } 409 return false; 410 } 411 412 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, 413 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException { 414 synchronized (mLock) { 415 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag); 416 } 417 } 418 419 /* 420 * When a session is created the following things need to happen. 421 * 1. Its callback binder needs a link to death 422 * 2. It needs to be added to all sessions. 423 * 3. It needs to be added to the priority stack. 424 * 4. It needs to be added to the relevant user record. 425 */ 426 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId, 427 String callerPackageName, ISessionCallback cb, String tag) { 428 429 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId, 430 callerPackageName, cb, tag, this, mHandler); 431 try { 432 cb.asBinder().linkToDeath(session, 0); 433 } catch (RemoteException e) { 434 throw new RuntimeException("Media Session owner died prematurely.", e); 435 } 436 437 mAllSessions.add(session); 438 mPriorityStack.addSession(session); 439 440 UserRecord user = getOrCreateUser(userId); 441 user.addSessionLocked(session); 442 443 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0); 444 445 if (DEBUG) { 446 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag); 447 } 448 return session; 449 } 450 451 private UserRecord getOrCreateUser(int userId) { 452 UserRecord user = mUserRecords.get(userId); 453 if (user == null) { 454 user = new UserRecord(getContext(), userId); 455 mUserRecords.put(userId, user); 456 } 457 return user; 458 } 459 460 private int findIndexOfSessionForIdLocked(String sessionId) { 461 for (int i = mAllSessions.size() - 1; i >= 0; i--) { 462 MediaSessionRecord session = mAllSessions.get(i); 463 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { 464 return i; 465 } 466 } 467 return -1; 468 } 469 470 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) { 471 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { 472 if (mSessionsListeners.get(i).mListener == listener) { 473 return i; 474 } 475 } 476 return -1; 477 } 478 479 private boolean isSessionDiscoverable(MediaSessionRecord record) { 480 // TODO probably want to check more than if it's active. 481 return record.isActive(); 482 } 483 484 private void pushSessionsChanged(int userId) { 485 synchronized (mLock) { 486 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId); 487 int size = records.size(); 488 ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>(); 489 for (int i = 0; i < size; i++) { 490 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder())); 491 } 492 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { 493 SessionsListenerRecord record = mSessionsListeners.get(i); 494 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) { 495 try { 496 record.mListener.onActiveSessionsChanged(tokens); 497 } catch (RemoteException e) { 498 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing", 499 e); 500 mSessionsListeners.remove(i); 501 } 502 } 503 } 504 } 505 } 506 507 private MediaRouteProviderProxy.RoutesListener mRoutesCallback 508 = new MediaRouteProviderProxy.RoutesListener() { 509 @Override 510 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes, 511 int reqId) { 512 // TODO for now select the first route to test, eventually add the 513 // new routes to the dialog if it is still open 514 synchronized (mLock) { 515 int index = findIndexOfSessionForIdLocked(sessionId); 516 if (index != -1 && routes != null && routes.size() > 0) { 517 MediaSessionRecord record = mAllSessions.get(index); 518 RouteInfo route = routes.get(0); 519 record.selectRoute(route); 520 UserRecord user = mUserRecords.get(record.getUserId()); 521 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider()); 522 provider.addSession(record); 523 } 524 } 525 } 526 527 @Override 528 public void onRouteConnected(String sessionId, RouteInfo route, 529 RouteRequest options, RouteConnectionRecord connection) { 530 synchronized (mLock) { 531 int index = findIndexOfSessionForIdLocked(sessionId); 532 if (index != -1) { 533 MediaSessionRecord session = mAllSessions.get(index); 534 session.setRouteConnected(route, options.getConnectionOptions(), connection); 535 } 536 } 537 } 538 }; 539 540 /** 541 * Information about a particular user. The contents of this object is 542 * guarded by mLock. 543 */ 544 final class UserRecord { 545 private final int mUserId; 546 private final MediaRouteProviderWatcher mRouteProviderWatcher; 547 private final ArrayList<MediaRouteProviderProxy> mProviders 548 = new ArrayList<MediaRouteProviderProxy>(); 549 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); 550 551 public UserRecord(Context context, int userId) { 552 mUserId = userId; 553 mRouteProviderWatcher = new MediaRouteProviderWatcher(context, 554 mProviderWatcherCallback, mHandler, userId); 555 } 556 557 public void startLocked() { 558 mRouteProviderWatcher.start(); 559 } 560 561 public void stopLocked() { 562 mRouteProviderWatcher.stop(); 563 updateInterestLocked(); 564 } 565 566 public void destroyLocked() { 567 for (int i = mSessions.size() - 1; i >= 0; i--) { 568 MediaSessionRecord session = mSessions.get(i); 569 MediaSessionService.this.destroySessionLocked(session); 570 if (session.isConnected()) { 571 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING); 572 } 573 } 574 } 575 576 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() { 577 return mProviders; 578 } 579 580 public ArrayList<MediaSessionRecord> getSessionsLocked() { 581 return mSessions; 582 } 583 584 public void addSessionLocked(MediaSessionRecord session) { 585 mSessions.add(session); 586 updateInterestLocked(); 587 } 588 589 public void removeSessionLocked(MediaSessionRecord session) { 590 mSessions.remove(session); 591 RouteInfo route = session.getRoute(); 592 if (route != null) { 593 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider()); 594 if (provider != null) { 595 provider.removeSession(session); 596 } 597 } 598 updateInterestLocked(); 599 } 600 601 public void dumpLocked(PrintWriter pw, String prefix) { 602 pw.println(prefix + "Record for user " + mUserId); 603 String indent = prefix + " "; 604 int size = mProviders.size(); 605 pw.println(indent + size + " Providers:"); 606 for (int i = 0; i < size; i++) { 607 mProviders.get(i).dump(pw, indent); 608 } 609 pw.println(); 610 size = mSessions.size(); 611 pw.println(indent + size + " Sessions:"); 612 for (int i = 0; i < size; i++) { 613 // Just print the session info, the full session dump will 614 // already be in the list of all sessions. 615 pw.println(indent + mSessions.get(i).getSessionInfo()); 616 } 617 } 618 619 public void updateInterestLocked() { 620 // TODO go through the sessions and build up the set of interfaces 621 // we're interested in. Update the provider watcher. 622 // For now, just express interest in all providers for the current 623 // user 624 boolean interested = mUserId == mCurrentUserId; 625 for (int i = mProviders.size() - 1; i >= 0; i--) { 626 mProviders.get(i).setInterested(interested); 627 } 628 } 629 630 private MediaRouteProviderProxy getProviderLocked(String providerId) { 631 for (int i = mProviders.size() - 1; i >= 0; i--) { 632 MediaRouteProviderProxy provider = mProviders.get(i); 633 if (TextUtils.equals(providerId, provider.getId())) { 634 return provider; 635 } 636 } 637 return null; 638 } 639 640 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback 641 = new MediaRouteProviderWatcher.Callback() { 642 @Override 643 public void removeProvider(MediaRouteProviderProxy provider) { 644 synchronized (mLock) { 645 mProviders.remove(provider); 646 provider.setRoutesListener(null); 647 provider.setInterested(false); 648 } 649 } 650 651 @Override 652 public void addProvider(MediaRouteProviderProxy provider) { 653 synchronized (mLock) { 654 mProviders.add(provider); 655 provider.setRoutesListener(mRoutesCallback); 656 provider.setInterested(true); 657 } 658 } 659 }; 660 } 661 662 final class SessionsListenerRecord implements IBinder.DeathRecipient { 663 private final IActiveSessionsListener mListener; 664 private final int mUserId; 665 666 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) { 667 mListener = listener; 668 mUserId = userId; 669 } 670 671 @Override 672 public void binderDied() { 673 synchronized (mLock) { 674 mSessionsListeners.remove(this); 675 } 676 } 677 } 678 679 class SessionManagerImpl extends ISessionManager.Stub { 680 private static final String EXTRA_WAKELOCK_ACQUIRED = 681 "android.media.AudioService.WAKELOCK_ACQUIRED"; 682 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number 683 684 private boolean mVoiceButtonDown = false; 685 private boolean mVoiceButtonHandled = false; 686 687 @Override 688 public ISession createSession(String packageName, ISessionCallback cb, String tag, 689 int userId) throws RemoteException { 690 final int pid = Binder.getCallingPid(); 691 final int uid = Binder.getCallingUid(); 692 final long token = Binder.clearCallingIdentity(); 693 try { 694 enforcePackageName(packageName, uid); 695 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 696 false /* allowAll */, true /* requireFull */, "createSession", packageName); 697 if (cb == null) { 698 throw new IllegalArgumentException("Controller callback cannot be null"); 699 } 700 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag) 701 .getSessionBinder(); 702 } finally { 703 Binder.restoreCallingIdentity(token); 704 } 705 } 706 707 @Override 708 public List<IBinder> getSessions(ComponentName componentName, int userId) { 709 final int pid = Binder.getCallingPid(); 710 final int uid = Binder.getCallingUid(); 711 final long token = Binder.clearCallingIdentity(); 712 713 try { 714 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid); 715 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 716 synchronized (mLock) { 717 ArrayList<MediaSessionRecord> records = mPriorityStack 718 .getActiveSessions(resolvedUserId); 719 int size = records.size(); 720 for (int i = 0; i < size; i++) { 721 binders.add(records.get(i).getControllerBinder().asBinder()); 722 } 723 } 724 return binders; 725 } finally { 726 Binder.restoreCallingIdentity(token); 727 } 728 } 729 730 @Override 731 public void addSessionsListener(IActiveSessionsListener listener, 732 ComponentName componentName, int userId) throws RemoteException { 733 final int pid = Binder.getCallingPid(); 734 final int uid = Binder.getCallingUid(); 735 final long token = Binder.clearCallingIdentity(); 736 737 try { 738 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid); 739 synchronized (mLock) { 740 int index = findIndexOfSessionsListenerLocked(listener); 741 if (index != -1) { 742 Log.w(TAG, "ActiveSessionsListener is already added, ignoring"); 743 return; 744 } 745 SessionsListenerRecord record = new SessionsListenerRecord(listener, 746 resolvedUserId); 747 try { 748 listener.asBinder().linkToDeath(record, 0); 749 } catch (RemoteException e) { 750 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e); 751 return; 752 } 753 mSessionsListeners.add(record); 754 } 755 } finally { 756 Binder.restoreCallingIdentity(token); 757 } 758 } 759 760 @Override 761 public void removeSessionsListener(IActiveSessionsListener listener) 762 throws RemoteException { 763 synchronized (mLock) { 764 int index = findIndexOfSessionsListenerLocked(listener); 765 if (index != -1) { 766 SessionsListenerRecord record = mSessionsListeners.remove(index); 767 try { 768 record.mListener.asBinder().unlinkToDeath(record, 0); 769 } catch (Exception e) { 770 // ignore exceptions, the record is being removed 771 } 772 } 773 } 774 } 775 776 /** 777 * Handles the dispatching of the media button events to one of the 778 * registered listeners, or if there was none, broadcast an 779 * ACTION_MEDIA_BUTTON intent to the rest of the system. 780 * 781 * @param keyEvent a non-null KeyEvent whose key code is one of the 782 * supported media buttons 783 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held 784 * while this key event is dispatched. 785 */ 786 @Override 787 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { 788 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) { 789 Log.w(TAG, "Attempted to dispatch null or non-media key event."); 790 return; 791 } 792 final int pid = Binder.getCallingPid(); 793 final int uid = Binder.getCallingUid(); 794 final long token = Binder.clearCallingIdentity(); 795 796 try { 797 synchronized (mLock) { 798 MediaSessionRecord session = mPriorityStack 799 .getDefaultMediaButtonSession(mCurrentUserId); 800 if (isVoiceKey(keyEvent.getKeyCode())) { 801 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); 802 } else { 803 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); 804 } 805 } 806 } finally { 807 Binder.restoreCallingIdentity(token); 808 } 809 } 810 811 @Override 812 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) 813 throws RemoteException { 814 final int pid = Binder.getCallingPid(); 815 final int uid = Binder.getCallingUid(); 816 final long token = Binder.clearCallingIdentity(); 817 try { 818 synchronized (mLock) { 819 MediaSessionRecord session = mPriorityStack 820 .getDefaultVolumeSession(mCurrentUserId); 821 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session); 822 } 823 } finally { 824 Binder.restoreCallingIdentity(token); 825 } 826 } 827 828 @Override 829 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 830 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) 831 != PackageManager.PERMISSION_GRANTED) { 832 pw.println("Permission Denial: can't dump MediaSessionService from from pid=" 833 + Binder.getCallingPid() 834 + ", uid=" + Binder.getCallingUid()); 835 return; 836 } 837 838 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)"); 839 pw.println(); 840 841 synchronized (mLock) { 842 pw.println("Session for calls:" + mPrioritySession); 843 if (mPrioritySession != null) { 844 mPrioritySession.dump(pw, ""); 845 } 846 int count = mAllSessions.size(); 847 pw.println(count + " Sessions:"); 848 for (int i = 0; i < count; i++) { 849 mAllSessions.get(i).dump(pw, ""); 850 pw.println(); 851 } 852 mPriorityStack.dump(pw, ""); 853 854 pw.println("User Records:"); 855 count = mUserRecords.size(); 856 for (int i = 0; i < count; i++) { 857 UserRecord user = mUserRecords.get(i); 858 user.dumpLocked(pw, ""); 859 } 860 } 861 } 862 863 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid, 864 final int uid) { 865 String packageName = null; 866 if (componentName != null) { 867 // If they gave us a component name verify they own the 868 // package 869 packageName = componentName.getPackageName(); 870 enforcePackageName(packageName, uid); 871 } 872 // Check that they can make calls on behalf of the user and 873 // get the final user id 874 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 875 true /* allowAll */, true /* requireFull */, "getSessions", packageName); 876 // Check if they have the permissions or their component is 877 // enabled for the user they're calling from. 878 enforceMediaPermissions(componentName, pid, uid, resolvedUserId); 879 return resolvedUserId; 880 } 881 882 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags, 883 MediaSessionRecord session) { 884 int direction = 0; 885 int steps = delta; 886 if (delta > 0) { 887 direction = 1; 888 } else if (delta < 0) { 889 direction = -1; 890 steps = -delta; 891 } 892 if (DEBUG) { 893 String sessionInfo = session == null ? null : session.getSessionInfo().toString(); 894 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags 895 + ", suggestedStream=" + suggestedStream); 896 897 } 898 if (session == null) { 899 for (int i = 0; i < steps; i++) { 900 try { 901 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, 902 flags, getContext().getOpPackageName()); 903 } catch (RemoteException e) { 904 Log.e(TAG, "Error adjusting default volume.", e); 905 } 906 } 907 } else { 908 if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) { 909 for (int i = 0; i < steps; i++) { 910 try { 911 mAudioService.adjustSuggestedStreamVolume(direction, 912 session.getAudioStream(), flags, 913 getContext().getOpPackageName()); 914 } catch (RemoteException e) { 915 Log.e(TAG, "Error adjusting volume for stream " 916 + session.getAudioStream(), e); 917 } 918 } 919 } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) { 920 session.adjustVolumeBy(delta); 921 } 922 } 923 } 924 925 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, 926 MediaSessionRecord session) { 927 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { 928 // If the phone app has priority just give it the event 929 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); 930 return; 931 } 932 int action = keyEvent.getAction(); 933 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; 934 if (action == KeyEvent.ACTION_DOWN) { 935 if (keyEvent.getRepeatCount() == 0) { 936 mVoiceButtonDown = true; 937 mVoiceButtonHandled = false; 938 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) { 939 mVoiceButtonHandled = true; 940 startVoiceInput(needWakeLock); 941 } 942 } else if (action == KeyEvent.ACTION_UP) { 943 if (mVoiceButtonDown) { 944 mVoiceButtonDown = false; 945 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { 946 // Resend the down then send this event through 947 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN); 948 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session); 949 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); 950 } 951 } 952 } 953 } 954 955 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, 956 MediaSessionRecord session) { 957 if (session != null) { 958 if (DEBUG) { 959 Log.d(TAG, "Sending media key to " + session.getSessionInfo()); 960 } 961 if (needWakeLock) { 962 mKeyEventReceiver.aquireWakeLockLocked(); 963 } 964 // If we don't need a wakelock use -1 as the id so we 965 // won't release it later 966 session.sendMediaButton(keyEvent, 967 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, 968 mKeyEventReceiver); 969 } else { 970 if (needWakeLock) { 971 mMediaEventWakeLock.acquire(); 972 } 973 if (DEBUG) { 974 Log.d(TAG, "Sending media key ordered broadcast"); 975 } 976 // Fallback to legacy behavior 977 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); 978 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 979 if (needWakeLock) { 980 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, 981 WAKELOCK_RELEASE_ON_FINISHED); 982 } 983 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, 984 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null); 985 } 986 } 987 988 private void startVoiceInput(boolean needWakeLock) { 989 Intent voiceIntent = null; 990 // select which type of search to launch: 991 // - screen on and device unlocked: action is ACTION_WEB_SEARCH 992 // - device locked or screen off: action is 993 // ACTION_VOICE_SEARCH_HANDS_FREE 994 // with EXTRA_SECURE set to true if the device is securely locked 995 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 996 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 997 if (!isLocked && pm.isScreenOn()) { 998 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); 999 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); 1000 } else { 1001 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); 1002 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, 1003 isLocked && mKeyguardManager.isKeyguardSecure()); 1004 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); 1005 } 1006 // start the search activity 1007 if (needWakeLock) { 1008 mMediaEventWakeLock.acquire(); 1009 } 1010 try { 1011 if (voiceIntent != null) { 1012 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1013 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1014 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT); 1015 } 1016 } catch (ActivityNotFoundException e) { 1017 Log.w(TAG, "No activity for search: " + e); 1018 } finally { 1019 if (needWakeLock) { 1020 mMediaEventWakeLock.release(); 1021 } 1022 } 1023 } 1024 1025 private boolean isVoiceKey(int keyCode) { 1026 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK; 1027 } 1028 1029 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler); 1030 1031 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable { 1032 private final Handler mHandler; 1033 private int mRefCount = 0; 1034 private int mLastTimeoutId = 0; 1035 1036 public KeyEventWakeLockReceiver(Handler handler) { 1037 super(handler); 1038 mHandler = handler; 1039 } 1040 1041 public void onTimeout() { 1042 synchronized (mLock) { 1043 if (mRefCount == 0) { 1044 // We've already released it, so just return 1045 return; 1046 } 1047 mLastTimeoutId++; 1048 mRefCount = 0; 1049 releaseWakeLockLocked(); 1050 } 1051 } 1052 1053 public void aquireWakeLockLocked() { 1054 if (mRefCount == 0) { 1055 mMediaEventWakeLock.acquire(); 1056 } 1057 mRefCount++; 1058 mHandler.removeCallbacks(this); 1059 mHandler.postDelayed(this, WAKELOCK_TIMEOUT); 1060 1061 } 1062 1063 @Override 1064 public void run() { 1065 onTimeout(); 1066 } 1067 1068 @Override 1069 protected void onReceiveResult(int resultCode, Bundle resultData) { 1070 if (resultCode < mLastTimeoutId) { 1071 // Ignore results from calls that were before the last 1072 // timeout, just in case. 1073 return; 1074 } else { 1075 synchronized (mLock) { 1076 if (mRefCount > 0) { 1077 mRefCount--; 1078 if (mRefCount == 0) { 1079 releaseWakeLockLocked(); 1080 } 1081 } 1082 } 1083 } 1084 } 1085 1086 private void releaseWakeLockLocked() { 1087 mMediaEventWakeLock.release(); 1088 mHandler.removeCallbacks(this); 1089 } 1090 }; 1091 1092 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { 1093 @Override 1094 public void onReceive(Context context, Intent intent) { 1095 if (intent == null) { 1096 return; 1097 } 1098 Bundle extras = intent.getExtras(); 1099 if (extras == null) { 1100 return; 1101 } 1102 synchronized (mLock) { 1103 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED) 1104 && mMediaEventWakeLock.isHeld()) { 1105 mMediaEventWakeLock.release(); 1106 } 1107 } 1108 } 1109 }; 1110 } 1111 1112 final class MessageHandler extends Handler { 1113 private static final int MSG_SESSIONS_CHANGED = 1; 1114 1115 @Override 1116 public void handleMessage(Message msg) { 1117 switch (msg.what) { 1118 case MSG_SESSIONS_CHANGED: 1119 pushSessionsChanged(msg.arg1); 1120 break; 1121 } 1122 } 1123 1124 public void post(int what, int arg1, int arg2) { 1125 obtainMessage(what, arg1, arg2).sendToTarget(); 1126 } 1127 } 1128} 1129