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