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