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