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