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