summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-04-05 09:45:40 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-04-05 09:45:40 +0000
commite0283ded0a7b2a2010f614943f939e1adc81ebeb (patch)
tree2bc0e45d144a9bea3ab12f08471b5cd8b5e987ed
parent4bb49a7b073302dc3a9655451d875ea3256271c2 (diff)
parentcb148b2ce07e91b4b3b1a6b55f84584bb3cbb1a8 (diff)
Merge "Refactor PackageWatchdog explicit health checks" into qt-dev
-rw-r--r--services/core/java/com/android/server/ExplicitHealthCheckController.java316
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java227
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java47
3 files changed, 334 insertions, 256 deletions
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 164837ab98dd..27ad208ef8a1 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -41,10 +41,14 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
+// TODO(b/120598832): Add tests
/**
* Controls the connections with {@link ExplicitHealthCheckService}.
*/
@@ -53,18 +57,30 @@ class ExplicitHealthCheckController {
private final Object mLock = new Object();
private final Context mContext;
- // Called everytime the service is connected, so the watchdog can sync it's state with
- // the health check service. In practice, should never be null after it has been #setEnabled.
- @GuardedBy("mLock") @Nullable private Runnable mOnConnected;
// Called everytime a package passes the health check, so the watchdog is notified of the
// passing check. In practice, should never be null after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
@GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
+ // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
+ // supporting health checks and update its internal state. In practice, should never be null
+ // after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
+ @GuardedBy("mLock") @Nullable private Consumer<List<String>> mSupportedConsumer;
+ // Called everytime we need to notify the watchdog to sync requests between itself and the
+ // health check service. In practice, should never be null after it has been #setEnabled.
+ // To prevent deadlocks between the controller and watchdog threads, we have
+ // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
+ // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
+ @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
// Actual binder object to the explicit health check service.
@GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
- // Cache for packages supporting explicit health checks. This cache should not change while
- // the health check service is running.
- @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
- // Connection to the explicit health check service, necessary to unbind
+ // Connection to the explicit health check service, necessary to unbind.
+ // We should only try to bind if mConnection is null, non-null indicates we
+ // are connected or at least connecting.
@GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
// Bind state of the explicit health check service.
@GuardedBy("mLock") private boolean mEnabled;
@@ -73,23 +89,117 @@ class ExplicitHealthCheckController {
mContext = context;
}
+ /** Enables or disables explicit health checks. */
+ public void setEnabled(boolean enabled) {
+ synchronized (mLock) {
+ Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
+ mEnabled = enabled;
+ }
+ }
+
+ /**
+ * Sets callbacks to listen to important events from the controller.
+ *
+ * <p> Should be called once at initialization before any other calls to the controller to
+ * ensure a happens-before relationship of the set parameters and visibility on other threads.
+ */
+ public void setCallbacks(Consumer<String> passedConsumer,
+ Consumer<List<String>> supportedConsumer, Runnable notifySyncRunnable) {
+ synchronized (mLock) {
+ if (mPassedConsumer != null || mSupportedConsumer != null
+ || mNotifySyncRunnable != null) {
+ Slog.wtf(TAG, "Resetting health check controller callbacks");
+ }
+
+ mPassedConsumer = Preconditions.checkNotNull(passedConsumer);
+ mSupportedConsumer = Preconditions.checkNotNull(supportedConsumer);
+ mNotifySyncRunnable = Preconditions.checkNotNull(notifySyncRunnable);
+ }
+ }
+
+ /**
+ * Calls the health check service to request or cancel packages based on
+ * {@code newRequestedPackages}.
+ *
+ * <p> Supported packages in {@code newRequestedPackages} that have not been previously
+ * requested will be requested while supported packages not in {@code newRequestedPackages}
+ * but were previously requested will be cancelled.
+ *
+ * <p> This handles binding and unbinding to the health check service as required.
+ *
+ * <p> Note, calling this may modify {@code newRequestedPackages}.
+ *
+ * <p> Note, this method is not thread safe, all calls should be serialized.
+ */
+ public void syncRequests(Set<String> newRequestedPackages) {
+ boolean enabled;
+ synchronized (mLock) {
+ enabled = mEnabled;
+ }
+
+ if (!enabled) {
+ Slog.i(TAG, "Health checks disabled, no supported packages");
+ // Call outside lock
+ mSupportedConsumer.accept(Collections.emptyList());
+ return;
+ }
+
+ getSupportedPackages(supportedPackages -> {
+ // Notify the watchdog without lock held
+ mSupportedConsumer.accept(supportedPackages);
+ getRequestedPackages(previousRequestedPackages -> {
+ synchronized (mLock) {
+ // Hold lock so requests and cancellations are sent atomically.
+ // It is important we don't mix requests from multiple threads.
+
+ // Note, this may modify newRequestedPackages
+ newRequestedPackages.retainAll(supportedPackages);
+
+ // Cancel packages no longer requested
+ actOnDifference(previousRequestedPackages,
+ newRequestedPackages, p -> cancel(p));
+ // Request packages not yet requested
+ actOnDifference(newRequestedPackages,
+ previousRequestedPackages, p -> request(p));
+
+ if (newRequestedPackages.isEmpty()) {
+ Slog.i(TAG, "No more health check requests, unbinding...");
+ unbindService();
+ return;
+ }
+ }
+ });
+ });
+ }
+
+ private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
+ Consumer<String> action) {
+ Iterator<String> iterator = collection1.iterator();
+ while (iterator.hasNext()) {
+ String packageName = iterator.next();
+ if (!collection2.contains(packageName)) {
+ action.accept(packageName);
+ }
+ }
+ }
+
/**
* Requests an explicit health check for {@code packageName}.
* After this request, the callback registered on {@link #setCallbacks} can receive explicit
* health check passed results.
- *
- * @throws IllegalStateException if the service is not started
*/
- public void request(String packageName) throws RemoteException {
+ private void request(String packageName) {
synchronized (mLock) {
- if (!mEnabled) {
+ if (!prepareServiceLocked("request health check for " + packageName)) {
return;
}
- enforceServiceReadyLocked();
-
Slog.i(TAG, "Requesting health check for package " + packageName);
- mRemoteService.request(packageName);
+ try {
+ mRemoteService.request(packageName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to request health check for package " + packageName, e);
+ }
}
}
@@ -97,46 +207,46 @@ class ExplicitHealthCheckController {
* Cancels all explicit health checks for {@code packageName}.
* After this request, the callback registered on {@link #setCallbacks} can no longer receive
* explicit health check passed results.
- *
- * @throws IllegalStateException if the service is not started
*/
- public void cancel(String packageName) throws RemoteException {
+ private void cancel(String packageName) {
synchronized (mLock) {
- if (!mEnabled) {
+ if (!prepareServiceLocked("cancel health check for " + packageName)) {
return;
}
- enforceServiceReadyLocked();
-
Slog.i(TAG, "Cancelling health check for package " + packageName);
- mRemoteService.cancel(packageName);
+ try {
+ mRemoteService.cancel(packageName);
+ } catch (RemoteException e) {
+ // Do nothing, if the service is down, when it comes up, we will sync requests,
+ // if there's some other error, retrying wouldn't fix anyways.
+ Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
+ }
}
}
/**
* Returns the packages that we can request explicit health checks for.
* The packages will be returned to the {@code consumer}.
- *
- * @throws IllegalStateException if the service is not started
*/
- public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ private void getSupportedPackages(Consumer<List<String>> consumer) {
synchronized (mLock) {
- if (!mEnabled) {
- consumer.accept(Collections.emptyList());
+ if (!prepareServiceLocked("get health check supported packages")) {
return;
}
- enforceServiceReadyLocked();
-
- if (mSupportedPackages == null) {
- Slog.d(TAG, "Getting health check supported packages");
+ Slog.d(TAG, "Getting health check supported packages");
+ try {
mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
- mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
- consumer.accept(mSupportedPackages);
+ List<String> packages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
+ Slog.i(TAG, "Explicit health check supported packages " + packages);
+ consumer.accept(packages);
}));
- } else {
- Slog.d(TAG, "Getting cached health check supported packages");
- consumer.accept(mSupportedPackages);
+ } catch (RemoteException e) {
+ // Request failed, treat as if all observed packages are supported, if any packages
+ // expire during this period, we may incorrectly treat it as failing health checks
+ // even if we don't support health checks for the package.
+ Slog.w(TAG, "Failed to get health check supported packages", e);
}
}
}
@@ -144,57 +254,42 @@ class ExplicitHealthCheckController {
/**
* Returns the packages for which health checks are currently in progress.
* The packages will be returned to the {@code consumer}.
- *
- * @throws IllegalStateException if the service is not started
*/
- public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ private void getRequestedPackages(Consumer<List<String>> consumer) {
synchronized (mLock) {
- if (!mEnabled) {
- consumer.accept(Collections.emptyList());
+ if (!prepareServiceLocked("get health check requested packages")) {
return;
}
- enforceServiceReadyLocked();
-
Slog.d(TAG, "Getting health check requested packages");
- mRemoteService.getRequestedPackages(new RemoteCallback(
- result -> consumer.accept(
- result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
- }
- }
-
- /** Enables or disables explicit health checks. */
- public void setEnabled(boolean enabled) {
- synchronized (mLock) {
- if (enabled == mEnabled) {
- return;
- }
-
- Slog.i(TAG, "Setting explicit health checks enabled " + enabled);
- mEnabled = enabled;
- if (enabled) {
- bindService();
- } else {
- unbindService();
+ try {
+ mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
+ List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
+ Slog.i(TAG, "Explicit health check requested packages " + packages);
+ consumer.accept(packages);
+ }));
+ } catch (RemoteException e) {
+ // Request failed, treat as if we haven't requested any packages, if any packages
+ // were actually requested, they will not be cancelled now. May be cancelled later
+ Slog.w(TAG, "Failed to get health check requested packages", e);
}
}
}
/**
- * Sets callbacks to listen to important events from the controller.
- * Should be called at initialization.
+ * Binds to the explicit health check service if the controller is enabled and
+ * not already bound.
*/
- public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) {
- Preconditions.checkNotNull(onConnected);
- Preconditions.checkNotNull(passedConsumer);
- mOnConnected = onConnected;
- mPassedConsumer = passedConsumer;
- }
-
- /** Binds to the explicit health check service. */
private void bindService() {
synchronized (mLock) {
- if (mRemoteService != null) {
+ if (!mEnabled || mConnection != null || mRemoteService != null) {
+ if (!mEnabled) {
+ Slog.i(TAG, "Not binding to service, service disabled");
+ } else if (mRemoteService != null) {
+ Slog.i(TAG, "Not binding to service, service already connected");
+ } else {
+ Slog.i(TAG, "Not binding to service, service already connecting");
+ }
return;
}
ComponentName component = getServiceComponentNameLocked();
@@ -205,14 +300,11 @@ class ExplicitHealthCheckController {
Intent intent = new Intent();
intent.setComponent(component);
- // TODO: Fix potential race conditions during mConnection state transitions.
- // E.g after #onServiceDisconected, the mRemoteService object is invalid until
- // we get an #onServiceConnected.
mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- initState(service);
Slog.i(TAG, "Explicit health check service is connected " + name);
+ initState(service);
}
@Override
@@ -221,19 +313,18 @@ class ExplicitHealthCheckController {
// Service crashed or process was killed, #onServiceConnected will be called.
// Don't need to re-bind.
Slog.i(TAG, "Explicit health check service is disconnected " + name);
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
}
@Override
public void onBindingDied(ComponentName name) {
// Application hosting service probably got updated
// Need to re-bind.
- synchronized (mLock) {
- if (mEnabled) {
- unbindService();
- bindService();
- }
- }
- Slog.i(TAG, "Explicit health check service binding is dead " + name);
+ Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
+ unbindService();
+ bindService();
}
@Override
@@ -243,9 +334,9 @@ class ExplicitHealthCheckController {
}
};
- Slog.i(TAG, "Binding to explicit health service");
- mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
- UserHandle.of(UserHandle.USER_SYSTEM));
+ mContext.bindServiceAsUser(intent, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM));
+ Slog.i(TAG, "Explicit health check service is bound");
}
}
@@ -253,10 +344,11 @@ class ExplicitHealthCheckController {
private void unbindService() {
synchronized (mLock) {
if (mRemoteService != null) {
- Slog.i(TAG, "Unbinding from explicit health service");
mContext.unbindService(mConnection);
mRemoteService = null;
+ mConnection = null;
}
+ Slog.i(TAG, "Explicit health check service is unbound");
}
}
@@ -301,40 +393,54 @@ class ExplicitHealthCheckController {
private void initState(IBinder service) {
synchronized (mLock) {
- mSupportedPackages = null;
+ if (!mEnabled) {
+ Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
+ // Very unlikely, but we disabled the service after binding but before we connected
+ unbindService();
+ return;
+ }
mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
try {
mRemoteService.setCallback(new RemoteCallback(result -> {
String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
if (!TextUtils.isEmpty(packageName)) {
- synchronized (mLock) {
- if (mPassedConsumer == null) {
- Slog.w(TAG, "Health check passed for package " + packageName
- + "but no consumer registered.");
- } else {
- mPassedConsumer.accept(packageName);
- }
+ if (mPassedConsumer == null) {
+ Slog.wtf(TAG, "Health check passed for package " + packageName
+ + "but no consumer registered.");
+ } else {
+ // Call without lock held
+ mPassedConsumer.accept(packageName);
}
} else {
- Slog.w(TAG, "Empty package passed explicit health check?");
+ Slog.wtf(TAG, "Empty package passed explicit health check?");
}
}));
- if (mOnConnected == null) {
- Slog.w(TAG, "Health check service connected but no runnable registered.");
- } else {
- mOnConnected.run();
- }
+ Slog.i(TAG, "Service initialized, syncing requests");
} catch (RemoteException e) {
Slog.wtf(TAG, "Could not setCallback on explicit health check service");
}
}
+ // Calling outside lock
+ mNotifySyncRunnable.run();
}
+ /**
+ * Prepares the health check service to receive requests.
+ *
+ * @return {@code true} if it is ready and we can proceed with a request,
+ * {@code false} otherwise. If it is not ready, and the service is enabled,
+ * we will bind and the request should be automatically attempted later.
+ */
@GuardedBy("mLock")
- private void enforceServiceReadyLocked() {
- if (mRemoteService == null) {
- // TODO: Try to bind to service
- throw new IllegalStateException("Explicit health check service not ready");
+ private boolean prepareServiceLocked(String action) {
+ if (mRemoteService != null && mEnabled) {
+ return true;
+ }
+ Slog.i(TAG, "Service not ready to " + action
+ + (mEnabled ? ". Binding..." : ". Disabled"));
+ if (mEnabled) {
+ bindService();
}
+ return false;
}
}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 2ba4d975a6b0..feffe2f72b6f 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -26,7 +26,6 @@ import android.content.pm.VersionedPackage;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -55,12 +54,10 @@ import java.io.InputStream;
import java.lang.annotation.Retention;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Monitors the health of packages on the system and notifies interested observers when packages
@@ -154,10 +151,11 @@ public class PackageWatchdog {
public void onPackagesReady() {
synchronized (mLock) {
mIsPackagesReady = true;
- mHealthCheckController.setCallbacks(this::updateHealthChecks,
- packageName -> onHealthCheckPassed(packageName));
- // Controller is disabled at creation until here where we may enable it
- mHealthCheckController.setEnabled(mIsHealthCheckEnabled);
+ mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
+ packages -> onSupportedPackages(packages),
+ () -> syncRequestsAsync());
+ // Controller is initially disabled until here where we may enable it and sync requests
+ setExplicitHealthCheckEnabled(mIsHealthCheckEnabled);
}
}
@@ -196,41 +194,23 @@ public class PackageWatchdog {
* @throws IllegalArgumentException if {@code packageNames} is empty
* or {@code durationMs} is less than 1
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packages,
+ public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
- if (packages.isEmpty() || durationMs < 1) {
- throw new IllegalArgumentException("Observation not started, no packages specified"
- + "or invalid duration");
- }
- if (!mIsPackagesReady) {
- // TODO: Queue observation requests when packages are not ready
- Slog.w(TAG, "Attempt to observe when packages not ready");
+ if (packageNames.isEmpty()) {
+ Slog.wtf(TAG, "No packages to observe, " + observer.getName());
return;
}
-
- try {
- Slog.i(TAG, "Getting packages supporting explicit health check");
- mHealthCheckController.getSupportedPackages(supportedPackages ->
- startObservingInner(observer, packages, durationMs, supportedPackages));
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Failed to fetch supported explicit health check packages");
+ if (durationMs < 1) {
+ // TODO: Instead of failing, monitor for default? 48hrs?
+ throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer "
+ + observer.getName() + ". Not observing packages " + packageNames);
}
- }
- private void startObservingInner(PackageHealthObserver observer,
- List<String> packageNames, long durationMs,
- List<String> healthCheckSupportedPackages) {
- Slog.i(TAG, "Start observing packages " + packageNames
- + ". Explicit health check supported packages " + healthCheckSupportedPackages);
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
- String packageName = packageNames.get(i);
- boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName);
- // If we should enable explicit health check for a package,
- // MonitoredPackage#mHasHealthCheckPassed will be false
- // until PackageWatchdog#onHealthCheckPassed
- packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck));
+ packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
}
+
synchronized (mLock) {
ObserverInternal oldObserver = mAllObservers.get(observer.getName());
if (oldObserver == null) {
@@ -248,97 +228,11 @@ public class PackageWatchdog {
// Always reschedule because we may need to expire packages
// earlier than we are already scheduled for
rescheduleCleanup();
- updateHealthChecks();
+ Slog.i(TAG, "Syncing health check requests, observing packages " + packageNames);
+ syncRequestsAsync();
saveToFileAsync();
}
- private void requestCheck(String packageName) {
- try {
- Slog.d(TAG, "Requesting explicit health check for " + packageName);
- mHealthCheckController.request(packageName);
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e);
- }
- }
-
- private void cancelCheck(String packageName) {
- try {
- Slog.d(TAG, "Cancelling explicit health check for " + packageName);
- mHealthCheckController.cancel(packageName);
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e);
- }
- }
-
- private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
- Consumer<String> action) {
- Iterator<String> iterator = collection1.iterator();
- while (iterator.hasNext()) {
- String packageName = iterator.next();
- if (!collection2.contains(packageName)) {
- action.accept(packageName);
- }
- }
- }
-
- private void updateChecksInner(List<String> supportedPackages,
- List<String> previousRequestedPackages) {
- boolean shouldUpdateFile = false;
-
- synchronized (mLock) {
- Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages
- + ". Requested packages: " + previousRequestedPackages);
- Set<String> newRequestedPackages = new ArraySet<>();
- Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
- while (oit.hasNext()) {
- ObserverInternal observer = oit.next();
- Iterator<MonitoredPackage> pit =
- observer.mPackages.values().iterator();
- while (pit.hasNext()) {
- MonitoredPackage monitoredPackage = pit.next();
- String packageName = monitoredPackage.mName;
- if (!monitoredPackage.mHasPassedHealthCheck) {
- if (supportedPackages.contains(packageName)) {
- newRequestedPackages.add(packageName);
- } else {
- shouldUpdateFile = true;
- monitoredPackage.mHasPassedHealthCheck = true;
- }
- }
- }
- }
- // TODO: Support ending the binding if newRequestedPackages is empty.
- // Will have to re-bind when we #startObservingHealth.
-
- // Cancel packages no longer requested
- actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p));
- // Request packages not yet requested
- actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p));
- }
-
- if (shouldUpdateFile) {
- saveToFileAsync();
- }
- }
-
- private void updateHealthChecks() {
- mShortTaskHandler.post(() -> {
- try {
- Slog.i(TAG, "Updating explicit health checks for all available packages");
- mHealthCheckController.getSupportedPackages(supported -> {
- try {
- mHealthCheckController.getRequestedPackages(
- requested -> updateChecksInner(supported, requested));
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get requested health check packages", e);
- }
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get supported health check package", e);
- }
- });
- }
-
/**
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
@@ -441,6 +335,9 @@ public class PackageWatchdog {
synchronized (mLock) {
mIsHealthCheckEnabled = enabled;
mHealthCheckController.setEnabled(enabled);
+ Slog.i(TAG, "Syncing health check requests, explicit health check is "
+ + (enabled ? "enabled" : "disabled"));
+ syncRequestsAsync();
}
}
@@ -487,6 +384,35 @@ public class PackageWatchdog {
}
/**
+ * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
+ */
+ private void syncRequestsAsync() {
+ if (!mShortTaskHandler.hasCallbacks(this::syncRequests)) {
+ mShortTaskHandler.post(this::syncRequests);
+ }
+ }
+
+ /**
+ * Syncs health check requests with the {@link ExplicitHealthCheckController}.
+ * Calls to this must be serialized.
+ *
+ * @see #syncRequestsAsync
+ */
+ private void syncRequests() {
+ Set<String> packages = null;
+ synchronized (mLock) {
+ if (mIsPackagesReady) {
+ packages = getPackagesPendingHealthChecksLocked();
+ } // else, we will sync requests when packages become ready
+ }
+
+ // Call outside lock to avoid holding lock when calling into the controller.
+ if (packages != null) {
+ mHealthCheckController.syncRequests(packages);
+ }
+ }
+
+ /**
* Updates the observers monitoring {@code packageName} that explicit health check has passed.
*
* <p> This update is strictly for registered observers at the time of the call
@@ -512,11 +438,64 @@ public class PackageWatchdog {
}
}
}
+
+ // So we can unbind from the service if this was the last result we expected
+ Slog.i(TAG, "Syncing health check requests, health check passed for " + packageName);
+ syncRequestsAsync();
+
+ if (shouldUpdateFile) {
+ saveToFileAsync();
+ }
+ }
+
+ private void onSupportedPackages(List<String> supportedPackages) {
+ boolean shouldUpdateFile = false;
+
+ synchronized (mLock) {
+ Slog.i(TAG, "Received supported packages " + supportedPackages);
+ Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+ while (oit.hasNext()) {
+ ObserverInternal observer = oit.next();
+ Iterator<MonitoredPackage> pit =
+ observer.mPackages.values().iterator();
+ while (pit.hasNext()) {
+ MonitoredPackage monitoredPackage = pit.next();
+ String packageName = monitoredPackage.mName;
+ if (!monitoredPackage.mHasPassedHealthCheck
+ && !supportedPackages.contains(packageName)) {
+ // Hasn't passed health check but health check is not supported
+ Slog.i(TAG, packageName + " does not support health checks, passing");
+ shouldUpdateFile = true;
+ monitoredPackage.mHasPassedHealthCheck = true;
+ }
+ }
+ }
+ }
+
if (shouldUpdateFile) {
saveToFileAsync();
}
}
+ private Set<String> getPackagesPendingHealthChecksLocked() {
+ Slog.d(TAG, "Getting all observed packages pending health checks");
+ Set<String> packages = new ArraySet<>();
+ Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+ while (oit.hasNext()) {
+ ObserverInternal observer = oit.next();
+ Iterator<MonitoredPackage> pit =
+ observer.mPackages.values().iterator();
+ while (pit.hasNext()) {
+ MonitoredPackage monitoredPackage = pit.next();
+ String packageName = monitoredPackage.mName;
+ if (!monitoredPackage.mHasPassedHealthCheck) {
+ packages.add(packageName);
+ }
+ }
+ }
+ return packages;
+ }
+
/** Reschedules handler to prune expired packages from observers. */
private void rescheduleCleanup() {
synchronized (mLock) {
@@ -591,7 +570,8 @@ public class PackageWatchdog {
}
}
}
- updateHealthChecks();
+ Slog.i(TAG, "Syncing health check requests pruned packages");
+ syncRequestsAsync();
saveToFileAsync();
}
@@ -690,6 +670,7 @@ public class PackageWatchdog {
}
private void saveToFileAsync() {
+ // TODO(b/120598832): Use Handler#hasCallbacks instead of removing and posting
mLongTaskHandler.removeCallbacks(this::saveToFile);
mLongTaskHandler.post(this::saveToFile);
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 28af7cee8f38..33bb4cceb3a9 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -26,7 +26,6 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.pm.VersionedPackage;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.test.TestLooper;
import android.util.AtomicFile;
@@ -43,6 +42,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -635,43 +635,34 @@ public class PackageWatchdogTest {
private boolean mIsEnabled;
private List<String> mSupportedPackages = new ArrayList<>();
private List<String> mRequestedPackages = new ArrayList<>();
- private Runnable mStateChangedRunnable;
private Consumer<String> mPassedConsumer;
+ private Consumer<List<String>> mSupportedConsumer;
+ private Runnable mNotifySyncRunnable;
@Override
- public void request(String packageName) throws RemoteException {
- if (!mRequestedPackages.contains(packageName)) {
- mRequestedPackages.add(packageName);
+ public void setEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ if (!mIsEnabled) {
+ mSupportedPackages.clear();
}
}
@Override
- public void cancel(String packageName) throws RemoteException {
- mRequestedPackages.remove(packageName);
- }
-
- @Override
- public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
- consumer.accept(mIsEnabled ? mSupportedPackages : Collections.emptyList());
- }
-
- @Override
- public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
- // Pass copy to prevent ConcurrentModificationException during test
- consumer.accept(
- mIsEnabled ? new ArrayList<>(mRequestedPackages) : Collections.emptyList());
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mIsEnabled = enabled;
- mStateChangedRunnable.run();
+ public void setCallbacks(Consumer<String> passedConsumer,
+ Consumer<List<String>> supportedConsumer, Runnable notifySyncRunnable) {
+ mPassedConsumer = passedConsumer;
+ mSupportedConsumer = supportedConsumer;
+ mNotifySyncRunnable = notifySyncRunnable;
}
@Override
- public void setCallbacks(Runnable stateChangedRunnable, Consumer<String> passedConsumer) {
- mStateChangedRunnable = stateChangedRunnable;
- mPassedConsumer = passedConsumer;
+ public void syncRequests(Set<String> packages) {
+ mRequestedPackages.clear();
+ if (mIsEnabled) {
+ packages.retainAll(mSupportedPackages);
+ mRequestedPackages.addAll(packages);
+ }
+ mSupportedConsumer.accept(mSupportedPackages);
}
public void setSupportedPackages(List<String> packages) {