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