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