1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.process_launcher; 6 7 import android.content.ComponentName; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.ServiceConnection; 11 import android.os.Bundle; 12 import android.os.Handler; 13 import android.os.IBinder; 14 import android.os.Looper; 15 import android.os.RemoteException; 16 17 import org.chromium.base.ChildBindingState; 18 import org.chromium.base.Log; 19 import org.chromium.base.MemoryPressureLevel; 20 import org.chromium.base.MemoryPressureListener; 21 import org.chromium.base.ThreadUtils; 22 import org.chromium.base.TraceEvent; 23 import org.chromium.base.VisibleForTesting; 24 import org.chromium.base.memory.MemoryPressureCallback; 25 26 import java.util.Arrays; 27 import java.util.List; 28 29 import javax.annotation.Nullable; 30 import javax.annotation.concurrent.GuardedBy; 31 32 /** 33 * Manages a connection between the browser activity and a child service. 34 */ 35 public class ChildProcessConnection { 36 private static final String TAG = "ChildProcessConn"; 37 private static final int NUM_BINDING_STATES = ChildBindingState.MAX_VALUE + 1; 38 39 /** 40 * Used to notify the consumer about the process start. These callbacks will be invoked before 41 * the ConnectionCallbacks. 42 */ 43 public interface ServiceCallback { 44 /** 45 * Called when the child process has successfully started and is ready for connection 46 * setup. 47 */ onChildStarted()48 void onChildStarted(); 49 50 /** 51 * Called when the child process failed to start. This can happen if the process is already 52 * in use by another client. The client will not receive any other callbacks after this one. 53 */ onChildStartFailed(ChildProcessConnection connection)54 void onChildStartFailed(ChildProcessConnection connection); 55 56 /** 57 * Called when the service has been disconnected. whether it was stopped by the client or 58 * if it stopped unexpectedly (process crash). 59 * This is the last callback from this interface that a client will receive for a specific 60 * connection. 61 */ onChildProcessDied(ChildProcessConnection connection)62 void onChildProcessDied(ChildProcessConnection connection); 63 } 64 65 /** 66 * Used to notify the consumer about the connection being established. 67 */ 68 public interface ConnectionCallback { 69 /** 70 * Called when the connection to the service is established. 71 * @param connection the connection object to the child process 72 */ onConnected(ChildProcessConnection connection)73 void onConnected(ChildProcessConnection connection); 74 } 75 76 /** 77 * Delegate that ChildServiceConnection should call when the service connects/disconnects. 78 * These callbacks are expected to happen on a background thread. 79 */ 80 @VisibleForTesting 81 protected interface ChildServiceConnectionDelegate { onServiceConnected(IBinder service)82 void onServiceConnected(IBinder service); onServiceDisconnected()83 void onServiceDisconnected(); 84 } 85 86 @VisibleForTesting 87 protected interface ChildServiceConnectionFactory { createConnection( Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate)88 ChildServiceConnection createConnection( 89 Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate); 90 } 91 92 /** Interface representing a connection to the Android service. Can be mocked in unit-tests. */ 93 @VisibleForTesting 94 protected interface ChildServiceConnection { bind()95 boolean bind(); unbind()96 void unbind(); isBound()97 boolean isBound(); 98 } 99 100 /** Implementation of ChildServiceConnection that does connect to a service. */ 101 private static class ChildServiceConnectionImpl 102 implements ChildServiceConnection, ServiceConnection { 103 private final Context mContext; 104 private final Intent mBindIntent; 105 private final int mBindFlags; 106 private final ChildServiceConnectionDelegate mDelegate; 107 private boolean mBound; 108 ChildServiceConnectionImpl(Context context, Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate)109 private ChildServiceConnectionImpl(Context context, Intent bindIntent, int bindFlags, 110 ChildServiceConnectionDelegate delegate) { 111 mContext = context; 112 mBindIntent = bindIntent; 113 mBindFlags = bindFlags; 114 mDelegate = delegate; 115 } 116 117 @Override bind()118 public boolean bind() { 119 if (!mBound) { 120 try { 121 TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind"); 122 mBound = mContext.bindService(mBindIntent, this, mBindFlags); 123 } finally { 124 TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind"); 125 } 126 } 127 return mBound; 128 } 129 130 @Override unbind()131 public void unbind() { 132 if (mBound) { 133 mContext.unbindService(this); 134 mBound = false; 135 } 136 } 137 138 @Override isBound()139 public boolean isBound() { 140 return mBound; 141 } 142 143 @Override onServiceConnected(ComponentName className, final IBinder service)144 public void onServiceConnected(ComponentName className, final IBinder service) { 145 mDelegate.onServiceConnected(service); 146 } 147 148 // Called on the main thread to notify that the child service did not disconnect gracefully. 149 @Override onServiceDisconnected(ComponentName className)150 public void onServiceDisconnected(ComponentName className) { 151 mDelegate.onServiceDisconnected(); 152 } 153 } 154 155 // Synchronize on this for access. 156 @GuardedBy("sAllBindingStateCounts") 157 private static final int[] sAllBindingStateCounts = new int[NUM_BINDING_STATES]; 158 159 @VisibleForTesting resetBindingStateCountsForTesting()160 static void resetBindingStateCountsForTesting() { 161 synchronized (sAllBindingStateCounts) { 162 for (int i = 0; i < NUM_BINDING_STATES; ++i) { 163 sAllBindingStateCounts[i] = 0; 164 } 165 } 166 } 167 168 private final Handler mLauncherHandler; 169 private final ComponentName mServiceName; 170 171 // Parameters passed to the child process through the service binding intent. 172 // If the service gets recreated by the framework the intent will be reused, so these parameters 173 // should be common to all processes of that type. 174 private final Bundle mServiceBundle; 175 176 // Whether bindToCaller should be called on the service after setup to check that only one 177 // process is bound to the service. 178 private final boolean mBindToCaller; 179 180 private static class ConnectionParams { 181 final Bundle mConnectionBundle; 182 final List<IBinder> mClientInterfaces; 183 ConnectionParams(Bundle connectionBundle, List<IBinder> clientInterfaces)184 ConnectionParams(Bundle connectionBundle, List<IBinder> clientInterfaces) { 185 mConnectionBundle = connectionBundle; 186 mClientInterfaces = clientInterfaces; 187 } 188 } 189 190 // This is set in start() and is used in onServiceConnected(). 191 private ServiceCallback mServiceCallback; 192 193 // This is set in setupConnection() and is later used in doConnectionSetup(), after which the 194 // variable is cleared. Therefore this is only valid while the connection is being set up. 195 private ConnectionParams mConnectionParams; 196 197 // Callback provided in setupConnection() that will communicate the result to the caller. This 198 // has to be called exactly once after setupConnection(), even if setup fails, so that the 199 // caller can free up resources associated with the setup attempt. This is set to null after the 200 // call. 201 private ConnectionCallback mConnectionCallback; 202 203 private IChildProcessService mService; 204 205 // Set to true when the service connection callback runs. This differs from 206 // mServiceConnectComplete, which tracks that the connection completed successfully. 207 private boolean mDidOnServiceConnected; 208 209 // Set to true when the service connected successfully. 210 private boolean mServiceConnectComplete; 211 212 // Set to true when the service disconnects, as opposed to being properly closed. This happens 213 // when the process crashes or gets killed by the system out-of-memory killer. 214 private boolean mServiceDisconnected; 215 216 // Process ID of the corresponding child process. 217 private int mPid; 218 219 // Strong binding will make the service priority equal to the priority of the activity. 220 private final ChildServiceConnection mStrongBinding; 221 222 // Moderate binding will make the service priority equal to the priority of a visible process 223 // while the app is in the foreground. 224 // This is also used as the initial binding before any priorities are set. 225 private final ChildServiceConnection mModerateBinding; 226 227 // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls 228 // to start() and stop(). 229 private final ChildServiceConnection mWaivedBinding; 230 231 // Refcount of bindings. 232 private int mStrongBindingCount; 233 private int mModerateBindingCount; 234 235 // Set to true once unbind() was called. 236 private boolean mUnbound; 237 238 // Binding state of this connection. 239 private @ChildBindingState int mBindingState; 240 241 // Protects access to instance variables that are also accessed on the client thread. 242 private final Object mClientThreadLock = new Object(); 243 244 // Same as above except it no longer updates after |unbind()|. 245 @GuardedBy("mClientThreadLock") 246 private @ChildBindingState int mBindingStateCurrentOrWhenDied; 247 248 // Indicate |kill()| was called to intentionally kill this process. 249 @GuardedBy("mClientThreadLock") 250 private boolean mKilledByUs; 251 252 // Copy of |sAllBindingStateCounts| at the time this is unbound. 253 @GuardedBy("mClientThreadLock") 254 private int[] mAllBindingStateCountsWhenDied; 255 256 private MemoryPressureCallback mMemoryPressureCallback; 257 ChildProcessConnection(Context context, ComponentName serviceName, boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle)258 public ChildProcessConnection(Context context, ComponentName serviceName, boolean bindToCaller, 259 boolean bindAsExternalService, Bundle serviceBundle) { 260 this(context, serviceName, bindToCaller, bindAsExternalService, serviceBundle, 261 null /* connectionFactory */); 262 } 263 264 @VisibleForTesting ChildProcessConnection(final Context context, ComponentName serviceName, boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle, ChildServiceConnectionFactory connectionFactory)265 public ChildProcessConnection(final Context context, ComponentName serviceName, 266 boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle, 267 ChildServiceConnectionFactory connectionFactory) { 268 mLauncherHandler = new Handler(); 269 assert isRunningOnLauncherThread(); 270 mServiceName = serviceName; 271 mServiceBundle = serviceBundle != null ? serviceBundle : new Bundle(); 272 mServiceBundle.putBoolean(ChildProcessConstants.EXTRA_BIND_TO_CALLER, bindToCaller); 273 mBindToCaller = bindToCaller; 274 275 if (connectionFactory == null) { 276 connectionFactory = new ChildServiceConnectionFactory() { 277 @Override 278 public ChildServiceConnection createConnection( 279 Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate) { 280 return new ChildServiceConnectionImpl(context, bindIntent, bindFlags, delegate); 281 } 282 }; 283 } 284 285 ChildServiceConnectionDelegate delegate = new ChildServiceConnectionDelegate() { 286 @Override 287 public void onServiceConnected(final IBinder service) { 288 mLauncherHandler.post(new Runnable() { 289 @Override 290 public void run() { 291 onServiceConnectedOnLauncherThread(service); 292 } 293 }); 294 } 295 296 @Override 297 public void onServiceDisconnected() { 298 mLauncherHandler.post(new Runnable() { 299 @Override 300 public void run() { 301 onServiceDisconnectedOnLauncherThread(); 302 } 303 }); 304 } 305 }; 306 307 Intent intent = new Intent(); 308 intent.setComponent(serviceName); 309 if (serviceBundle != null) { 310 intent.putExtras(serviceBundle); 311 } 312 313 int defaultFlags = Context.BIND_AUTO_CREATE 314 | (bindAsExternalService ? Context.BIND_EXTERNAL_SERVICE : 0); 315 316 mModerateBinding = connectionFactory.createConnection(intent, defaultFlags, delegate); 317 mStrongBinding = connectionFactory.createConnection( 318 intent, defaultFlags | Context.BIND_IMPORTANT, delegate); 319 mWaivedBinding = connectionFactory.createConnection( 320 intent, defaultFlags | Context.BIND_WAIVE_PRIORITY, delegate); 321 } 322 getService()323 public final IChildProcessService getService() { 324 assert isRunningOnLauncherThread(); 325 return mService; 326 } 327 getServiceName()328 public final ComponentName getServiceName() { 329 assert isRunningOnLauncherThread(); 330 return mServiceName; 331 } 332 isConnected()333 public boolean isConnected() { 334 return mService != null; 335 } 336 337 /** 338 * @return the connection pid, or 0 if not yet connected 339 */ getPid()340 public int getPid() { 341 assert isRunningOnLauncherThread(); 342 return mPid; 343 } 344 345 /** 346 * Starts a connection to an IChildProcessService. This must be followed by a call to 347 * setupConnection() to setup the connection parameters. start() and setupConnection() are 348 * separate to allow to pass whatever parameters are available in start(), and complete the 349 * remainder addStrongBinding while reducing the connection setup latency. 350 * @param useStrongBinding whether a strong binding should be bound by default. If false, an 351 * initial moderate binding is used. 352 * @param serviceCallback (optional) callbacks invoked when the child process starts or fails to 353 * start and when the service stops. 354 */ start(boolean useStrongBinding, ServiceCallback serviceCallback)355 public void start(boolean useStrongBinding, ServiceCallback serviceCallback) { 356 try { 357 TraceEvent.begin("ChildProcessConnection.start"); 358 assert isRunningOnLauncherThread(); 359 assert mConnectionParams 360 == null : "setupConnection() called before start() in ChildProcessConnection."; 361 362 mServiceCallback = serviceCallback; 363 364 if (!bind(useStrongBinding)) { 365 Log.e(TAG, "Failed to establish the service connection."); 366 // We have to notify the caller so that they can free-up associated resources. 367 // TODO(ppi): Can we hard-fail here? 368 notifyChildProcessDied(); 369 } 370 } finally { 371 TraceEvent.end("ChildProcessConnection.start"); 372 } 373 } 374 375 /** 376 * Sets-up the connection after it was started with start(). 377 * @param connectionBundle a bundle passed to the service that can be used to pass various 378 * parameters to the service 379 * @param clientInterfaces optional client specified interfaces that the child can use to 380 * communicate with the parent process 381 * @param connectionCallback will be called exactly once after the connection is set up or the 382 * setup fails 383 */ setupConnection(Bundle connectionBundle, @Nullable List<IBinder> clientInterfaces, ConnectionCallback connectionCallback)384 public void setupConnection(Bundle connectionBundle, @Nullable List<IBinder> clientInterfaces, 385 ConnectionCallback connectionCallback) { 386 assert isRunningOnLauncherThread(); 387 assert mConnectionParams == null; 388 if (mServiceDisconnected) { 389 Log.w(TAG, "Tried to setup a connection that already disconnected."); 390 connectionCallback.onConnected(null); 391 return; 392 } 393 try { 394 TraceEvent.begin("ChildProcessConnection.setupConnection"); 395 mConnectionCallback = connectionCallback; 396 mConnectionParams = new ConnectionParams(connectionBundle, clientInterfaces); 397 // Run the setup if the service is already connected. If not, doConnectionSetup() will 398 // be called from onServiceConnected(). 399 if (mServiceConnectComplete) { 400 doConnectionSetup(); 401 } 402 } finally { 403 TraceEvent.end("ChildProcessConnection.setupConnection"); 404 } 405 } 406 407 /** 408 * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call 409 * this multiple times. 410 */ stop()411 public void stop() { 412 assert isRunningOnLauncherThread(); 413 unbind(); 414 notifyChildProcessDied(); 415 } 416 kill()417 public void kill() { 418 assert isRunningOnLauncherThread(); 419 IChildProcessService service = mService; 420 unbind(); 421 try { 422 if (service != null) service.forceKill(); 423 } catch (RemoteException e) { 424 // Intentionally ignore since we are killing it anyway. 425 } 426 synchronized (mClientThreadLock) { 427 mKilledByUs = true; 428 } 429 notifyChildProcessDied(); 430 } 431 onServiceConnectedOnLauncherThread(IBinder service)432 private void onServiceConnectedOnLauncherThread(IBinder service) { 433 assert isRunningOnLauncherThread(); 434 // A flag from the parent class ensures we run the post-connection logic only once 435 // (instead of once per each ChildServiceConnection). 436 if (mDidOnServiceConnected) { 437 return; 438 } 439 try { 440 TraceEvent.begin("ChildProcessConnection.ChildServiceConnection.onServiceConnected"); 441 mDidOnServiceConnected = true; 442 mService = IChildProcessService.Stub.asInterface(service); 443 444 if (mBindToCaller) { 445 try { 446 if (!mService.bindToCaller()) { 447 if (mServiceCallback != null) { 448 mServiceCallback.onChildStartFailed(this); 449 } 450 unbind(); 451 return; 452 } 453 } catch (RemoteException ex) { 454 // Do not trigger the StartCallback here, since the service is already 455 // dead and the onChildStopped callback will run from onServiceDisconnected(). 456 Log.e(TAG, "Failed to bind service to connection.", ex); 457 return; 458 } 459 } 460 461 if (mServiceCallback != null) { 462 mServiceCallback.onChildStarted(); 463 } 464 465 mServiceConnectComplete = true; 466 467 if (mMemoryPressureCallback == null) { 468 final MemoryPressureCallback callback = this ::onMemoryPressure; 469 ThreadUtils.postOnUiThread(() -> MemoryPressureListener.addCallback(callback)); 470 mMemoryPressureCallback = callback; 471 } 472 473 // Run the setup if the connection parameters have already been provided. If 474 // not, doConnectionSetup() will be called from setupConnection(). 475 if (mConnectionParams != null) { 476 doConnectionSetup(); 477 } 478 } finally { 479 TraceEvent.end("ChildProcessConnection.ChildServiceConnection.onServiceConnected"); 480 } 481 } 482 onServiceDisconnectedOnLauncherThread()483 private void onServiceDisconnectedOnLauncherThread() { 484 assert isRunningOnLauncherThread(); 485 // Ensure that the disconnection logic runs only once (instead of once per each 486 // ChildServiceConnection). 487 if (mServiceDisconnected) { 488 return; 489 } 490 mServiceDisconnected = true; 491 Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid); 492 stop(); // We don't want to auto-restart on crash. Let the browser do that. 493 494 // If we have a pending connection callback, we need to communicate the failure to 495 // the caller. 496 if (mConnectionCallback != null) { 497 mConnectionCallback.onConnected(null); 498 mConnectionCallback = null; 499 } 500 } 501 onSetupConnectionResult(int pid)502 private void onSetupConnectionResult(int pid) { 503 mPid = pid; 504 assert mPid != 0 : "Child service claims to be run by a process of pid=0."; 505 506 if (mConnectionCallback != null) { 507 mConnectionCallback.onConnected(this); 508 } 509 mConnectionCallback = null; 510 } 511 512 /** 513 * Called after the connection parameters have been set (in setupConnection()) *and* a 514 * connection has been established (as signaled by onServiceConnected()). These two events can 515 * happen in any order. 516 */ doConnectionSetup()517 private void doConnectionSetup() { 518 try { 519 TraceEvent.begin("ChildProcessConnection.doConnectionSetup"); 520 assert mServiceConnectComplete && mService != null; 521 assert mConnectionParams != null; 522 523 ICallbackInt pidCallback = new ICallbackInt.Stub() { 524 @Override 525 public void call(final int pid) { 526 mLauncherHandler.post(new Runnable() { 527 @Override 528 public void run() { 529 onSetupConnectionResult(pid); 530 } 531 }); 532 } 533 }; 534 try { 535 mService.setupConnection(mConnectionParams.mConnectionBundle, pidCallback, 536 mConnectionParams.mClientInterfaces); 537 } catch (RemoteException re) { 538 Log.e(TAG, "Failed to setup connection.", re); 539 } 540 mConnectionParams = null; 541 } finally { 542 TraceEvent.end("ChildProcessConnection.doConnectionSetup"); 543 } 544 } 545 bind(boolean useStrongBinding)546 private boolean bind(boolean useStrongBinding) { 547 assert isRunningOnLauncherThread(); 548 assert !mUnbound; 549 550 boolean success; 551 if (useStrongBinding) { 552 success = mStrongBinding.bind(); 553 } else { 554 mModerateBindingCount++; 555 success = mModerateBinding.bind(); 556 } 557 if (!success) return false; 558 559 mWaivedBinding.bind(); 560 updateBindingState(); 561 return true; 562 } 563 564 @VisibleForTesting unbind()565 protected void unbind() { 566 assert isRunningOnLauncherThread(); 567 mService = null; 568 mConnectionParams = null; 569 mUnbound = true; 570 mStrongBinding.unbind(); 571 mWaivedBinding.unbind(); 572 mModerateBinding.unbind(); 573 updateBindingState(); 574 575 int[] bindingStateCounts; 576 synchronized (sAllBindingStateCounts) { 577 bindingStateCounts = Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES); 578 } 579 synchronized (mClientThreadLock) { 580 mAllBindingStateCountsWhenDied = bindingStateCounts; 581 } 582 583 if (mMemoryPressureCallback != null) { 584 final MemoryPressureCallback callback = mMemoryPressureCallback; 585 ThreadUtils.postOnUiThread(() -> MemoryPressureListener.removeCallback(callback)); 586 mMemoryPressureCallback = null; 587 } 588 } 589 isStrongBindingBound()590 public boolean isStrongBindingBound() { 591 assert isRunningOnLauncherThread(); 592 return mStrongBinding.isBound(); 593 } 594 addStrongBinding()595 public void addStrongBinding() { 596 assert isRunningOnLauncherThread(); 597 if (!isConnected()) { 598 Log.w(TAG, "The connection is not bound for %d", getPid()); 599 return; 600 } 601 if (mStrongBindingCount == 0) { 602 mStrongBinding.bind(); 603 updateBindingState(); 604 } 605 mStrongBindingCount++; 606 } 607 removeStrongBinding()608 public void removeStrongBinding() { 609 assert isRunningOnLauncherThread(); 610 if (!isConnected()) { 611 Log.w(TAG, "The connection is not bound for %d", getPid()); 612 return; 613 } 614 assert mStrongBindingCount > 0; 615 mStrongBindingCount--; 616 if (mStrongBindingCount == 0) { 617 mStrongBinding.unbind(); 618 updateBindingState(); 619 } 620 } 621 isModerateBindingBound()622 public boolean isModerateBindingBound() { 623 assert isRunningOnLauncherThread(); 624 return mModerateBinding.isBound(); 625 } 626 addModerateBinding()627 public void addModerateBinding() { 628 assert isRunningOnLauncherThread(); 629 if (!isConnected()) { 630 Log.w(TAG, "The connection is not bound for %d", getPid()); 631 return; 632 } 633 if (mModerateBindingCount == 0) { 634 mModerateBinding.bind(); 635 updateBindingState(); 636 } 637 mModerateBindingCount++; 638 } 639 removeModerateBinding()640 public void removeModerateBinding() { 641 assert isRunningOnLauncherThread(); 642 if (!isConnected()) { 643 Log.w(TAG, "The connection is not bound for %d", getPid()); 644 return; 645 } 646 assert mModerateBindingCount > 0; 647 mModerateBindingCount--; 648 if (mModerateBindingCount == 0) { 649 mModerateBinding.unbind(); 650 updateBindingState(); 651 } 652 } 653 654 /** 655 * @return true if the connection is bound and only bound with the waived binding or if the 656 * connection is unbound and was only bound with the waived binding when it disconnected. 657 */ bindingStateCurrentOrWhenDied()658 public @ChildBindingState int bindingStateCurrentOrWhenDied() { 659 // WARNING: this method can be called from a thread other than the launcher thread. 660 // Note that it returns the current waived bound only state and is racy. This not really 661 // preventable without changing the caller's API, short of blocking. 662 synchronized (mClientThreadLock) { 663 return mBindingStateCurrentOrWhenDied; 664 } 665 } 666 667 /** 668 * @return true if the connection is intentionally killed by calling kill(). 669 */ isKilledByUs()670 public boolean isKilledByUs() { 671 // WARNING: this method can be called from a thread other than the launcher thread. 672 // Note that it returns the current waived bound only state and is racy. This not really 673 // preventable without changing the caller's API, short of blocking. 674 synchronized (mClientThreadLock) { 675 return mKilledByUs; 676 } 677 } 678 bindingStateCountsCurrentOrWhenDied()679 public int[] bindingStateCountsCurrentOrWhenDied() { 680 // WARNING: this method can be called from a thread other than the launcher thread. 681 // Note that it returns the current waived bound only state and is racy. This not really 682 // preventable without changing the caller's API, short of blocking. 683 synchronized (mClientThreadLock) { 684 if (mAllBindingStateCountsWhenDied != null) { 685 return Arrays.copyOf(mAllBindingStateCountsWhenDied, NUM_BINDING_STATES); 686 } 687 } 688 synchronized (sAllBindingStateCounts) { 689 return Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES); 690 } 691 } 692 693 // Should be called any binding is bound or unbound. updateBindingState()694 private void updateBindingState() { 695 int oldBindingState = mBindingState; 696 if (mUnbound) { 697 mBindingState = ChildBindingState.UNBOUND; 698 } else if (mStrongBinding.isBound()) { 699 mBindingState = ChildBindingState.STRONG; 700 } else if (mModerateBinding.isBound()) { 701 mBindingState = ChildBindingState.MODERATE; 702 } else { 703 assert mWaivedBinding.isBound(); 704 mBindingState = ChildBindingState.WAIVED; 705 } 706 707 if (mBindingState != oldBindingState) { 708 synchronized (sAllBindingStateCounts) { 709 if (oldBindingState != ChildBindingState.UNBOUND) { 710 assert sAllBindingStateCounts[oldBindingState] > 0; 711 sAllBindingStateCounts[oldBindingState]--; 712 } 713 if (mBindingState != ChildBindingState.UNBOUND) { 714 sAllBindingStateCounts[mBindingState]++; 715 } 716 } 717 } 718 719 if (!mUnbound) { 720 synchronized (mClientThreadLock) { 721 mBindingStateCurrentOrWhenDied = mBindingState; 722 } 723 } 724 } 725 notifyChildProcessDied()726 private void notifyChildProcessDied() { 727 if (mServiceCallback != null) { 728 // Guard against nested calls to this method. 729 ServiceCallback serviceCallback = mServiceCallback; 730 mServiceCallback = null; 731 serviceCallback.onChildProcessDied(this); 732 } 733 } 734 isRunningOnLauncherThread()735 private boolean isRunningOnLauncherThread() { 736 return mLauncherHandler.getLooper() == Looper.myLooper(); 737 } 738 739 @VisibleForTesting crashServiceForTesting()740 public void crashServiceForTesting() throws RemoteException { 741 mService.forceKill(); 742 } 743 744 @VisibleForTesting didOnServiceConnectedForTesting()745 public boolean didOnServiceConnectedForTesting() { 746 return mDidOnServiceConnected; 747 } 748 749 @VisibleForTesting getLauncherHandler()750 protected Handler getLauncherHandler() { 751 return mLauncherHandler; 752 } 753 onMemoryPressure(@emoryPressureLevel int pressure)754 private void onMemoryPressure(@MemoryPressureLevel int pressure) { 755 mLauncherHandler.post(() -> onMemoryPressureOnLauncherThread(pressure)); 756 } 757 onMemoryPressureOnLauncherThread(@emoryPressureLevel int pressure)758 private void onMemoryPressureOnLauncherThread(@MemoryPressureLevel int pressure) { 759 if (mService == null) return; 760 try { 761 mService.onMemoryPressure(pressure); 762 } catch (RemoteException ex) { 763 // Ignore 764 } 765 } 766 } 767