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