blob: 2ba4d975a6b076599d18ee40e6d49e83d606e155 [file] [log] [blame]
Zimuzo6efba542018-11-29 12:47:58 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
Zimuzoe5009cd2019-01-23 18:11:58 +000019import static java.lang.annotation.RetentionPolicy.SOURCE;
20
21import android.annotation.IntDef;
Zimuzo3eee4382019-01-08 20:42:39 +000022import android.annotation.Nullable;
Zimuzo6efba542018-11-29 12:47:58 +000023import android.content.Context;
Zimuzoef65fb82019-02-28 10:44:29 +000024import android.content.pm.PackageManager;
Zimuzo972e1cd2019-01-28 16:30:01 +000025import android.content.pm.VersionedPackage;
Zimuzo6efba542018-11-29 12:47:58 +000026import android.os.Environment;
27import android.os.Handler;
Zimuzo6efba542018-11-29 12:47:58 +000028import android.os.Looper;
Zimuzocaa435e2019-03-20 11:16:06 +000029import android.os.RemoteException;
Zimuzo6efba542018-11-29 12:47:58 +000030import android.os.SystemClock;
31import android.text.TextUtils;
32import android.util.ArrayMap;
Zimuzocaa435e2019-03-20 11:16:06 +000033import android.util.ArraySet;
Zimuzo6efba542018-11-29 12:47:58 +000034import android.util.AtomicFile;
Zimuzo6efba542018-11-29 12:47:58 +000035import android.util.Slog;
36import android.util.Xml;
37
38import com.android.internal.annotations.GuardedBy;
Zimuzo3eee4382019-01-08 20:42:39 +000039import com.android.internal.annotations.VisibleForTesting;
Zimuzocfaed762019-01-03 21:13:01 +000040import com.android.internal.os.BackgroundThread;
Zimuzo6efba542018-11-29 12:47:58 +000041import com.android.internal.util.FastXmlSerializer;
42import com.android.internal.util.XmlUtils;
43
44import libcore.io.IoUtils;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import java.io.File;
51import java.io.FileNotFoundException;
52import java.io.FileOutputStream;
53import java.io.IOException;
54import java.io.InputStream;
Zimuzoe5009cd2019-01-23 18:11:58 +000055import java.lang.annotation.Retention;
Zimuzo6efba542018-11-29 12:47:58 +000056import java.nio.charset.StandardCharsets;
57import java.util.ArrayList;
Zimuzocaa435e2019-03-20 11:16:06 +000058import java.util.Collection;
Zimuzo3eee4382019-01-08 20:42:39 +000059import java.util.Collections;
Zimuzo6efba542018-11-29 12:47:58 +000060import java.util.Iterator;
61import java.util.List;
Zimuzo3eee4382019-01-08 20:42:39 +000062import java.util.Set;
Zimuzocaa435e2019-03-20 11:16:06 +000063import java.util.function.Consumer;
Zimuzo6efba542018-11-29 12:47:58 +000064
65/**
66 * Monitors the health of packages on the system and notifies interested observers when packages
Zimuzoe5009cd2019-01-23 18:11:58 +000067 * fail. On failure, the registered observer with the least user impacting mitigation will
68 * be notified.
Zimuzo6efba542018-11-29 12:47:58 +000069 */
70public class PackageWatchdog {
71 private static final String TAG = "PackageWatchdog";
72 // Duration to count package failures before it resets to 0
73 private static final int TRIGGER_DURATION_MS = 60000;
74 // Number of package failures within the duration above before we notify observers
Zimuzo3eee4382019-01-08 20:42:39 +000075 static final int TRIGGER_FAILURE_COUNT = 5;
Zimuzo6efba542018-11-29 12:47:58 +000076 private static final int DB_VERSION = 1;
77 private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
78 private static final String TAG_PACKAGE = "package";
79 private static final String TAG_OBSERVER = "observer";
80 private static final String ATTR_VERSION = "version";
81 private static final String ATTR_NAME = "name";
82 private static final String ATTR_DURATION = "duration";
Zimuzo9284e742019-02-22 12:09:28 +000083 private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
Zimuzo6efba542018-11-29 12:47:58 +000084
85 private static PackageWatchdog sPackageWatchdog;
86
87 private final Object mLock = new Object();
88 // System server context
89 private final Context mContext;
Zimuzocaa435e2019-03-20 11:16:06 +000090 // Handler to run short running tasks
91 private final Handler mShortTaskHandler;
92 // Handler for processing IO and long running tasks
93 private final Handler mLongTaskHandler;
Zimuzo3eee4382019-01-08 20:42:39 +000094 // Contains (observer-name -> observer-handle) that have ever been registered from
Zimuzocfaed762019-01-03 21:13:01 +000095 // previous boots. Observers with all packages expired are periodically pruned.
96 // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
Zimuzo6efba542018-11-29 12:47:58 +000097 @GuardedBy("mLock")
Zimuzo3eee4382019-01-08 20:42:39 +000098 private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
Zimuzocfaed762019-01-03 21:13:01 +000099 // File containing the XML data of monitored packages /data/system/package-watchdog.xml
Zimuzo3eee4382019-01-08 20:42:39 +0000100 private final AtomicFile mPolicyFile;
Zimuzo6efba542018-11-29 12:47:58 +0000101 // Runnable to prune monitored packages that have expired
102 private final Runnable mPackageCleanup;
Zimuzocaa435e2019-03-20 11:16:06 +0000103 private final ExplicitHealthCheckController mHealthCheckController;
104 // Flag to control whether explicit health checks are supported or not
105 @GuardedBy("mLock")
106 private boolean mIsHealthCheckEnabled = true;
107 @GuardedBy("mLock")
108 private boolean mIsPackagesReady;
Zimuzo6efba542018-11-29 12:47:58 +0000109 // Last SystemClock#uptimeMillis a package clean up was executed.
110 // 0 if mPackageCleanup not running.
111 private long mUptimeAtLastRescheduleMs;
112 // Duration a package cleanup was last scheduled for.
113 // 0 if mPackageCleanup not running.
114 private long mDurationAtLastReschedule;
115
116 private PackageWatchdog(Context context) {
Zimuzocaa435e2019-03-20 11:16:06 +0000117 // Needs to be constructed inline
118 this(context, new AtomicFile(
119 new File(new File(Environment.getDataDirectory(), "system"),
120 "package-watchdog.xml")),
121 new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
122 new ExplicitHealthCheckController(context));
Zimuzo6efba542018-11-29 12:47:58 +0000123 }
124
Zimuzo3eee4382019-01-08 20:42:39 +0000125 /**
Zimuzocaa435e2019-03-20 11:16:06 +0000126 * Creates a PackageWatchdog that allows injecting dependencies.
Zimuzo3eee4382019-01-08 20:42:39 +0000127 */
128 @VisibleForTesting
Zimuzocaa435e2019-03-20 11:16:06 +0000129 PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
130 Handler longTaskHandler, ExplicitHealthCheckController controller) {
Zimuzo3eee4382019-01-08 20:42:39 +0000131 mContext = context;
Zimuzocaa435e2019-03-20 11:16:06 +0000132 mPolicyFile = policyFile;
133 mShortTaskHandler = shortTaskHandler;
134 mLongTaskHandler = longTaskHandler;
Zimuzo3eee4382019-01-08 20:42:39 +0000135 mPackageCleanup = this::rescheduleCleanup;
Zimuzocaa435e2019-03-20 11:16:06 +0000136 mHealthCheckController = controller;
Zimuzo3eee4382019-01-08 20:42:39 +0000137 loadFromFile();
138 }
139
Zimuzo6efba542018-11-29 12:47:58 +0000140 /** Creates or gets singleton instance of PackageWatchdog. */
Zimuzocfaed762019-01-03 21:13:01 +0000141 public static PackageWatchdog getInstance(Context context) {
142 synchronized (PackageWatchdog.class) {
143 if (sPackageWatchdog == null) {
144 sPackageWatchdog = new PackageWatchdog(context);
145 }
146 return sPackageWatchdog;
Zimuzo6efba542018-11-29 12:47:58 +0000147 }
Zimuzo6efba542018-11-29 12:47:58 +0000148 }
149
150 /**
Zimuzocaa435e2019-03-20 11:16:06 +0000151 * Called during boot to notify when packages are ready on the device so we can start
152 * binding.
153 */
154 public void onPackagesReady() {
155 synchronized (mLock) {
156 mIsPackagesReady = true;
157 mHealthCheckController.setCallbacks(this::updateHealthChecks,
158 packageName -> onHealthCheckPassed(packageName));
159 // Controller is disabled at creation until here where we may enable it
160 mHealthCheckController.setEnabled(mIsHealthCheckEnabled);
161 }
162 }
163
164 /**
Zimuzo6efba542018-11-29 12:47:58 +0000165 * Registers {@code observer} to listen for package failures
166 *
167 * <p>Observers are expected to call this on boot. It does not specify any packages but
168 * it will resume observing any packages requested from a previous boot.
169 */
170 public void registerHealthObserver(PackageHealthObserver observer) {
171 synchronized (mLock) {
Zimuzo3eee4382019-01-08 20:42:39 +0000172 ObserverInternal internalObserver = mAllObservers.get(observer.getName());
173 if (internalObserver != null) {
174 internalObserver.mRegisteredObserver = observer;
175 }
Zimuzo6efba542018-11-29 12:47:58 +0000176 if (mDurationAtLastReschedule == 0) {
177 // Nothing running, schedule
178 rescheduleCleanup();
179 }
180 }
181 }
182
183 /**
184 * Starts observing the health of the {@code packages} for {@code observer} and notifies
185 * {@code observer} of any package failures within the monitoring duration.
186 *
Zimuzocaa435e2019-03-20 11:16:06 +0000187 * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
188 * duration if {@link #onHealthCheckPassed} was never called,
Zimuzo9284e742019-02-22 12:09:28 +0000189 * {@link PackageHealthObserver#execute} will be called as if the package failed.
190 *
Zimuzo6efba542018-11-29 12:47:58 +0000191 * <p>If {@code observer} is already monitoring a package in {@code packageNames},
Zimuzo9284e742019-02-22 12:09:28 +0000192 * the monitoring window of that package will be reset to {@code durationMs} and the health
Zimuzocaa435e2019-03-20 11:16:06 +0000193 * check state will be reset to a default depending on if the package is contained in
194 * {@link mPackagesWithExplicitHealthCheckEnabled}.
Zimuzo6efba542018-11-29 12:47:58 +0000195 *
196 * @throws IllegalArgumentException if {@code packageNames} is empty
Zimuzocfaed762019-01-03 21:13:01 +0000197 * or {@code durationMs} is less than 1
Zimuzo6efba542018-11-29 12:47:58 +0000198 */
Zimuzocaa435e2019-03-20 11:16:06 +0000199 public void startObservingHealth(PackageHealthObserver observer, List<String> packages,
200 long durationMs) {
201 if (packages.isEmpty() || durationMs < 1) {
Zimuzo6efba542018-11-29 12:47:58 +0000202 throw new IllegalArgumentException("Observation not started, no packages specified"
Zimuzocfaed762019-01-03 21:13:01 +0000203 + "or invalid duration");
Zimuzo6efba542018-11-29 12:47:58 +0000204 }
Zimuzocaa435e2019-03-20 11:16:06 +0000205 if (!mIsPackagesReady) {
206 // TODO: Queue observation requests when packages are not ready
207 Slog.w(TAG, "Attempt to observe when packages not ready");
208 return;
209 }
210
211 try {
212 Slog.i(TAG, "Getting packages supporting explicit health check");
213 mHealthCheckController.getSupportedPackages(supportedPackages ->
214 startObservingInner(observer, packages, durationMs, supportedPackages));
215 } catch (RemoteException e) {
216 Slog.wtf(TAG, "Failed to fetch supported explicit health check packages");
217 }
218 }
219
220 private void startObservingInner(PackageHealthObserver observer,
221 List<String> packageNames, long durationMs,
222 List<String> healthCheckSupportedPackages) {
223 Slog.i(TAG, "Start observing packages " + packageNames
224 + ". Explicit health check supported packages " + healthCheckSupportedPackages);
Zimuzo6efba542018-11-29 12:47:58 +0000225 List<MonitoredPackage> packages = new ArrayList<>();
Zimuzocfaed762019-01-03 21:13:01 +0000226 for (int i = 0; i < packageNames.size(); i++) {
Zimuzocaa435e2019-03-20 11:16:06 +0000227 String packageName = packageNames.get(i);
228 boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName);
229 // If we should enable explicit health check for a package,
230 // MonitoredPackage#mHasHealthCheckPassed will be false
231 // until PackageWatchdog#onHealthCheckPassed
232 packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck));
Zimuzo6efba542018-11-29 12:47:58 +0000233 }
234 synchronized (mLock) {
235 ObserverInternal oldObserver = mAllObservers.get(observer.getName());
236 if (oldObserver == null) {
Zimuzocaa435e2019-03-20 11:16:06 +0000237 Slog.d(TAG, observer.getName() + " started monitoring health "
238 + "of packages " + packageNames);
Zimuzo6efba542018-11-29 12:47:58 +0000239 mAllObservers.put(observer.getName(),
240 new ObserverInternal(observer.getName(), packages));
241 } else {
Zimuzocaa435e2019-03-20 11:16:06 +0000242 Slog.d(TAG, observer.getName() + " added the following "
243 + "packages to monitor " + packageNames);
Zimuzo6efba542018-11-29 12:47:58 +0000244 oldObserver.updatePackages(packages);
245 }
246 }
247 registerHealthObserver(observer);
248 // Always reschedule because we may need to expire packages
249 // earlier than we are already scheduled for
250 rescheduleCleanup();
Zimuzocaa435e2019-03-20 11:16:06 +0000251 updateHealthChecks();
Zimuzocfaed762019-01-03 21:13:01 +0000252 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000253 }
254
Zimuzocaa435e2019-03-20 11:16:06 +0000255 private void requestCheck(String packageName) {
256 try {
257 Slog.d(TAG, "Requesting explicit health check for " + packageName);
258 mHealthCheckController.request(packageName);
259 } catch (RemoteException e) {
260 Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e);
261 }
262 }
263
264 private void cancelCheck(String packageName) {
265 try {
266 Slog.d(TAG, "Cancelling explicit health check for " + packageName);
267 mHealthCheckController.cancel(packageName);
268 } catch (RemoteException e) {
269 Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e);
270 }
271 }
272
273 private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
274 Consumer<String> action) {
275 Iterator<String> iterator = collection1.iterator();
276 while (iterator.hasNext()) {
277 String packageName = iterator.next();
278 if (!collection2.contains(packageName)) {
279 action.accept(packageName);
280 }
281 }
282 }
283
284 private void updateChecksInner(List<String> supportedPackages,
285 List<String> previousRequestedPackages) {
286 boolean shouldUpdateFile = false;
287
288 synchronized (mLock) {
289 Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages
290 + ". Requested packages: " + previousRequestedPackages);
291 Set<String> newRequestedPackages = new ArraySet<>();
292 Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
293 while (oit.hasNext()) {
294 ObserverInternal observer = oit.next();
295 Iterator<MonitoredPackage> pit =
296 observer.mPackages.values().iterator();
297 while (pit.hasNext()) {
298 MonitoredPackage monitoredPackage = pit.next();
299 String packageName = monitoredPackage.mName;
300 if (!monitoredPackage.mHasPassedHealthCheck) {
301 if (supportedPackages.contains(packageName)) {
302 newRequestedPackages.add(packageName);
303 } else {
304 shouldUpdateFile = true;
305 monitoredPackage.mHasPassedHealthCheck = true;
306 }
307 }
308 }
309 }
310 // TODO: Support ending the binding if newRequestedPackages is empty.
311 // Will have to re-bind when we #startObservingHealth.
312
313 // Cancel packages no longer requested
314 actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p));
315 // Request packages not yet requested
316 actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p));
317 }
318
319 if (shouldUpdateFile) {
320 saveToFileAsync();
321 }
322 }
323
324 private void updateHealthChecks() {
325 mShortTaskHandler.post(() -> {
326 try {
327 Slog.i(TAG, "Updating explicit health checks for all available packages");
328 mHealthCheckController.getSupportedPackages(supported -> {
329 try {
330 mHealthCheckController.getRequestedPackages(
331 requested -> updateChecksInner(supported, requested));
332 } catch (RemoteException e) {
333 Slog.e(TAG, "Failed to get requested health check packages", e);
334 }
335 });
336 } catch (RemoteException e) {
337 Slog.e(TAG, "Failed to get supported health check package", e);
338 }
339 });
340 }
341
Zimuzo6efba542018-11-29 12:47:58 +0000342 /**
343 * Unregisters {@code observer} from listening to package failure.
344 * Additionally, this stops observing any packages that may have previously been observed
345 * even from a previous boot.
346 */
347 public void unregisterHealthObserver(PackageHealthObserver observer) {
348 synchronized (mLock) {
349 mAllObservers.remove(observer.getName());
Zimuzo6efba542018-11-29 12:47:58 +0000350 }
Zimuzocfaed762019-01-03 21:13:01 +0000351 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000352 }
353
Zimuzo3eee4382019-01-08 20:42:39 +0000354 /**
355 * Returns packages observed by {@code observer}
356 *
357 * @return an empty set if {@code observer} has some packages observerd from a previous boot
358 * but has not registered itself in the current boot to receive notifications. Returns null
359 * if there are no active packages monitored from any boot.
360 */
361 @Nullable
362 public Set<String> getPackages(PackageHealthObserver observer) {
363 synchronized (mLock) {
364 for (int i = 0; i < mAllObservers.size(); i++) {
365 if (observer.getName().equals(mAllObservers.keyAt(i))) {
366 if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
367 return mAllObservers.valueAt(i).mPackages.keySet();
368 }
369 return Collections.emptySet();
370 }
371 }
372 }
373 return null;
374 }
375
Zimuzo6efba542018-11-29 12:47:58 +0000376 /**
377 * Called when a process fails either due to a crash or ANR.
378 *
Zimuzoe5009cd2019-01-23 18:11:58 +0000379 * <p>For each package contained in the process, one registered observer with the least user
380 * impact will be notified for mitigation.
Zimuzo6efba542018-11-29 12:47:58 +0000381 *
382 * <p>This method could be called frequently if there is a severe problem on the device.
383 */
Zimuzo972e1cd2019-01-28 16:30:01 +0000384 public void onPackageFailure(List<VersionedPackage> packages) {
Zimuzocaa435e2019-03-20 11:16:06 +0000385 mLongTaskHandler.post(() -> {
Zimuzoe5009cd2019-01-23 18:11:58 +0000386 synchronized (mLock) {
387 if (mAllObservers.isEmpty()) {
388 return;
389 }
Zimuzocfaed762019-01-03 21:13:01 +0000390
Zimuzo972e1cd2019-01-28 16:30:01 +0000391 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
Zimuzo71d931e2019-02-01 13:08:16 +0000392 VersionedPackage versionedPackage = packages.get(pIndex);
393 // Observer that will receive failure for versionedPackage
Zimuzoe5009cd2019-01-23 18:11:58 +0000394 PackageHealthObserver currentObserverToNotify = null;
395 int currentObserverImpact = Integer.MAX_VALUE;
396
397 // Find observer with least user impact
398 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
399 ObserverInternal observer = mAllObservers.valueAt(oIndex);
400 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
401 if (registeredObserver != null
Zimuzo71d931e2019-02-01 13:08:16 +0000402 && observer.onPackageFailure(versionedPackage.getPackageName())) {
403 int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000404 if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
405 && impact < currentObserverImpact) {
406 currentObserverToNotify = registeredObserver;
407 currentObserverImpact = impact;
408 }
409 }
410 }
411
412 // Execute action with least user impact
413 if (currentObserverToNotify != null) {
Zimuzo71d931e2019-02-01 13:08:16 +0000414 currentObserverToNotify.execute(versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000415 }
Zimuzo3eee4382019-01-08 20:42:39 +0000416 }
Zimuzocfaed762019-01-03 21:13:01 +0000417 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000418 });
Zimuzo6efba542018-11-29 12:47:58 +0000419 }
420
Zimuzo9284e742019-02-22 12:09:28 +0000421 // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
Zimuzo6efba542018-11-29 12:47:58 +0000422 // This currently adds about 7ms extra to shutdown thread
423 /** Writes the package information to file during shutdown. */
424 public void writeNow() {
425 if (!mAllObservers.isEmpty()) {
Zimuzocaa435e2019-03-20 11:16:06 +0000426 mLongTaskHandler.removeCallbacks(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000427 pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
428 saveToFile();
429 Slog.i(TAG, "Last write to update package durations");
430 }
431 }
432
Zimuzocaa435e2019-03-20 11:16:06 +0000433 // TODO(b/120598832): Set depending on DeviceConfig flag
434 /**
435 * Enables or disables explicit health checks.
436 * <p> If explicit health checks are enabled, the health check service is started.
437 * <p> If explicit health checks are disabled, pending explicit health check requests are
438 * passed and the health check service is stopped.
439 */
440 public void setExplicitHealthCheckEnabled(boolean enabled) {
441 synchronized (mLock) {
442 mIsHealthCheckEnabled = enabled;
443 mHealthCheckController.setEnabled(enabled);
444 }
445 }
446
Zimuzoe5009cd2019-01-23 18:11:58 +0000447 /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
448 @Retention(SOURCE)
449 @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
450 PackageHealthObserverImpact.USER_IMPACT_LOW,
451 PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
452 PackageHealthObserverImpact.USER_IMPACT_HIGH})
453 public @interface PackageHealthObserverImpact {
454 /** No action to take. */
455 int USER_IMPACT_NONE = 0;
456 /* Action has low user impact, user of a device will barely notice. */
457 int USER_IMPACT_LOW = 1;
458 /* Action has medium user impact, user of a device will likely notice. */
459 int USER_IMPACT_MEDIUM = 3;
460 /* Action has high user impact, a last resort, user of a device will be very frustrated. */
461 int USER_IMPACT_HIGH = 5;
462 }
463
Zimuzo6efba542018-11-29 12:47:58 +0000464 /** Register instances of this interface to receive notifications on package failure. */
465 public interface PackageHealthObserver {
466 /**
Zimuzo71d931e2019-02-01 13:08:16 +0000467 * Called when health check fails for the {@code versionedPackage}.
Zimuzoe5009cd2019-01-23 18:11:58 +0000468 *
469 * @return any one of {@link PackageHealthObserverImpact} to express the impact
470 * to the user on {@link #execute}
Zimuzo6efba542018-11-29 12:47:58 +0000471 */
Zimuzo71d931e2019-02-01 13:08:16 +0000472 @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000473
474 /**
475 * Executes mitigation for {@link #onHealthCheckFailed}.
476 *
477 * @return {@code true} if action was executed successfully, {@code false} otherwise
478 */
Zimuzo71d931e2019-02-01 13:08:16 +0000479 boolean execute(VersionedPackage versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000480
Zimuzo9284e742019-02-22 12:09:28 +0000481 // TODO(b/120598832): Ensure uniqueness?
Zimuzo6efba542018-11-29 12:47:58 +0000482 /**
483 * Identifier for the observer, should not change across device updates otherwise the
484 * watchdog may drop observing packages with the old name.
485 */
486 String getName();
487 }
488
Zimuzocaa435e2019-03-20 11:16:06 +0000489 /**
490 * Updates the observers monitoring {@code packageName} that explicit health check has passed.
491 *
492 * <p> This update is strictly for registered observers at the time of the call
493 * Observers that register after this signal will have no knowledge of prior signals and will
494 * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
495 *
496 * <p> {@code packageName} can still be considered failed if reported by
497 * {@link #onPackageFailure} before the package expires.
498 *
499 * <p> Triggered by components outside the system server when they are fully functional after an
500 * update.
501 */
502 private void onHealthCheckPassed(String packageName) {
503 Slog.i(TAG, "Health check passed for package: " + packageName);
504 boolean shouldUpdateFile = false;
505 synchronized (mLock) {
506 for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
507 ObserverInternal observer = mAllObservers.valueAt(observerIdx);
508 MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
509 if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
510 monitoredPackage.mHasPassedHealthCheck = true;
511 shouldUpdateFile = true;
512 }
513 }
514 }
515 if (shouldUpdateFile) {
516 saveToFileAsync();
517 }
518 }
519
Zimuzo6efba542018-11-29 12:47:58 +0000520 /** Reschedules handler to prune expired packages from observers. */
521 private void rescheduleCleanup() {
522 synchronized (mLock) {
523 long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
524 if (nextDurationToScheduleMs == Long.MAX_VALUE) {
525 Slog.i(TAG, "No monitored packages, ending package cleanup");
526 mDurationAtLastReschedule = 0;
527 mUptimeAtLastRescheduleMs = 0;
528 return;
529 }
530 long uptimeMs = SystemClock.uptimeMillis();
531 // O if mPackageCleanup not running
532 long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
533 ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000534 // Less than O if mPackageCleanup unexpectedly didn't run yet even though
535 // and we are past the last duration scheduled to run
Zimuzo6efba542018-11-29 12:47:58 +0000536 long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000537 if (mUptimeAtLastRescheduleMs == 0
538 || remainingDurationMs <= 0
539 || nextDurationToScheduleMs < remainingDurationMs) {
Zimuzo6efba542018-11-29 12:47:58 +0000540 // First schedule or an earlier reschedule
541 pruneObservers(elapsedDurationMs);
Zimuzocaa435e2019-03-20 11:16:06 +0000542 mShortTaskHandler.removeCallbacks(mPackageCleanup);
543 mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
Zimuzo6efba542018-11-29 12:47:58 +0000544 mDurationAtLastReschedule = nextDurationToScheduleMs;
545 mUptimeAtLastRescheduleMs = uptimeMs;
546 }
547 }
548 }
549
550 /**
551 * Returns the earliest time a package should expire.
552 * @returns Long#MAX_VALUE if there are no observed packages.
553 */
554 private long getEarliestPackageExpiryLocked() {
555 long shortestDurationMs = Long.MAX_VALUE;
Zimuzocfaed762019-01-03 21:13:01 +0000556 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
557 ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
558 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
559 long duration = packages.valueAt(pIndex).mDurationMs;
560 if (duration < shortestDurationMs) {
561 shortestDurationMs = duration;
Zimuzo6efba542018-11-29 12:47:58 +0000562 }
563 }
564 }
565 Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
Zimuzo3eee4382019-01-08 20:42:39 +0000566
Zimuzo6efba542018-11-29 12:47:58 +0000567 return shortestDurationMs;
568 }
569
570 /**
571 * Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
572 * Discards expired packages and discards observers without any packages.
573 */
574 private void pruneObservers(long elapsedMs) {
575 if (elapsedMs == 0) {
576 return;
577 }
578 synchronized (mLock) {
579 Slog.d(TAG, "Removing expired packages after " + elapsedMs + "ms");
580 Iterator<ObserverInternal> it = mAllObservers.values().iterator();
581 while (it.hasNext()) {
582 ObserverInternal observer = it.next();
Zimuzoef65fb82019-02-28 10:44:29 +0000583 List<MonitoredPackage> failedPackages =
584 observer.updateMonitoringDurations(elapsedMs);
585 if (!failedPackages.isEmpty()) {
Zimuzocaa435e2019-03-20 11:16:06 +0000586 onHealthCheckFailed(observer, failedPackages);
Zimuzoef65fb82019-02-28 10:44:29 +0000587 }
588 if (observer.mPackages.isEmpty()) {
Zimuzo6efba542018-11-29 12:47:58 +0000589 Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
590 it.remove();
591 }
592 }
593 }
Zimuzocaa435e2019-03-20 11:16:06 +0000594 updateHealthChecks();
Zimuzocfaed762019-01-03 21:13:01 +0000595 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000596 }
597
Zimuzocaa435e2019-03-20 11:16:06 +0000598 private void onHealthCheckFailed(ObserverInternal observer,
Zimuzoef65fb82019-02-28 10:44:29 +0000599 List<MonitoredPackage> failedPackages) {
Zimuzocaa435e2019-03-20 11:16:06 +0000600 mLongTaskHandler.post(() -> {
Zimuzoef65fb82019-02-28 10:44:29 +0000601 synchronized (mLock) {
602 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
603 if (registeredObserver != null) {
604 PackageManager pm = mContext.getPackageManager();
605 for (int i = 0; i < failedPackages.size(); i++) {
606 String packageName = failedPackages.get(i).mName;
607 long versionCode = 0;
Zimuzocaa435e2019-03-20 11:16:06 +0000608 Slog.i(TAG, "Explicit health check failed for package " + packageName);
Zimuzoef65fb82019-02-28 10:44:29 +0000609 try {
610 versionCode = pm.getPackageInfo(
611 packageName, 0 /* flags */).getLongVersionCode();
612 } catch (PackageManager.NameNotFoundException e) {
613 Slog.w(TAG, "Explicit health check failed but could not find package "
614 + packageName);
615 // TODO(b/120598832): Skip. We only continue to pass tests for now since
616 // the tests don't install any packages
617 }
618 registeredObserver.execute(new VersionedPackage(packageName, versionCode));
619 }
620 }
621 }
622 });
623 }
624
Zimuzo6efba542018-11-29 12:47:58 +0000625 /**
626 * Loads mAllObservers from file.
627 *
628 * <p>Note that this is <b>not</b> thread safe and should only called be called
629 * from the constructor.
630 */
631 private void loadFromFile() {
632 InputStream infile = null;
633 mAllObservers.clear();
634 try {
635 infile = mPolicyFile.openRead();
636 final XmlPullParser parser = Xml.newPullParser();
637 parser.setInput(infile, StandardCharsets.UTF_8.name());
638 XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
639 int outerDepth = parser.getDepth();
640 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
641 ObserverInternal observer = ObserverInternal.read(parser);
642 if (observer != null) {
643 mAllObservers.put(observer.mName, observer);
644 }
645 }
646 } catch (FileNotFoundException e) {
647 // Nothing to monitor
Zimuzocfaed762019-01-03 21:13:01 +0000648 } catch (IOException | NumberFormatException | XmlPullParserException e) {
Zimuzocaa435e2019-03-20 11:16:06 +0000649 Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
Zimuzocfaed762019-01-03 21:13:01 +0000650 mPolicyFile.delete();
Zimuzo6efba542018-11-29 12:47:58 +0000651 } finally {
652 IoUtils.closeQuietly(infile);
653 }
654 }
655
656 /**
Zimuzocfaed762019-01-03 21:13:01 +0000657 * Persists mAllObservers to file. Threshold information is ignored.
Zimuzo6efba542018-11-29 12:47:58 +0000658 */
659 private boolean saveToFile() {
Zimuzocfaed762019-01-03 21:13:01 +0000660 synchronized (mLock) {
661 FileOutputStream stream;
662 try {
663 stream = mPolicyFile.startWrite();
664 } catch (IOException e) {
665 Slog.w(TAG, "Cannot update monitored packages", e);
666 return false;
Zimuzo6efba542018-11-29 12:47:58 +0000667 }
Zimuzocfaed762019-01-03 21:13:01 +0000668
669 try {
670 XmlSerializer out = new FastXmlSerializer();
671 out.setOutput(stream, StandardCharsets.UTF_8.name());
672 out.startDocument(null, true);
673 out.startTag(null, TAG_PACKAGE_WATCHDOG);
674 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
675 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
676 mAllObservers.valueAt(oIndex).write(out);
677 }
678 out.endTag(null, TAG_PACKAGE_WATCHDOG);
679 out.endDocument();
680 mPolicyFile.finishWrite(stream);
681 return true;
682 } catch (IOException e) {
683 Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
684 mPolicyFile.failWrite(stream);
685 return false;
686 } finally {
687 IoUtils.closeQuietly(stream);
688 }
Zimuzo6efba542018-11-29 12:47:58 +0000689 }
690 }
691
Zimuzocfaed762019-01-03 21:13:01 +0000692 private void saveToFileAsync() {
Zimuzocaa435e2019-03-20 11:16:06 +0000693 mLongTaskHandler.removeCallbacks(this::saveToFile);
694 mLongTaskHandler.post(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000695 }
696
697 /**
698 * Represents an observer monitoring a set of packages along with the failure thresholds for
699 * each package.
700 */
701 static class ObserverInternal {
702 public final String mName;
Zimuzoef65fb82019-02-28 10:44:29 +0000703 //TODO(b/120598832): Add getter for mPackages
Zimuzo6efba542018-11-29 12:47:58 +0000704 public final ArrayMap<String, MonitoredPackage> mPackages;
Zimuzo3eee4382019-01-08 20:42:39 +0000705 @Nullable
706 public PackageHealthObserver mRegisteredObserver;
Zimuzo6efba542018-11-29 12:47:58 +0000707
708 ObserverInternal(String name, List<MonitoredPackage> packages) {
709 mName = name;
710 mPackages = new ArrayMap<>();
711 updatePackages(packages);
712 }
713
714 /**
715 * Writes important details to file. Doesn't persist any package failure thresholds.
716 *
717 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
718 * #saveToFile which runs on a single threaded handler.
719 */
720 public boolean write(XmlSerializer out) {
721 try {
722 out.startTag(null, TAG_OBSERVER);
723 out.attribute(null, ATTR_NAME, mName);
724 for (int i = 0; i < mPackages.size(); i++) {
725 MonitoredPackage p = mPackages.valueAt(i);
726 out.startTag(null, TAG_PACKAGE);
727 out.attribute(null, ATTR_NAME, p.mName);
728 out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
Zimuzo9284e742019-02-22 12:09:28 +0000729 out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
730 String.valueOf(p.mHasPassedHealthCheck));
Zimuzo6efba542018-11-29 12:47:58 +0000731 out.endTag(null, TAG_PACKAGE);
732 }
733 out.endTag(null, TAG_OBSERVER);
734 return true;
735 } catch (IOException e) {
736 Slog.w(TAG, "Cannot save observer", e);
737 return false;
738 }
739 }
740
741 public void updatePackages(List<MonitoredPackage> packages) {
742 synchronized (mName) {
Zimuzocfaed762019-01-03 21:13:01 +0000743 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
744 MonitoredPackage p = packages.get(pIndex);
Zimuzo6efba542018-11-29 12:47:58 +0000745 mPackages.put(p.mName, p);
746 }
747 }
748 }
749
750 /**
751 * Reduces the monitoring durations of all packages observed by this observer by
752 * {@code elapsedMs}. If any duration is less than 0, the package is removed from
753 * observation.
754 *
Zimuzoef65fb82019-02-28 10:44:29 +0000755 * @returns a {@link List} of packages that were removed from the observer without explicit
756 * health check passing, or an empty list if no package expired for which an explicit health
757 * check was still pending
Zimuzo6efba542018-11-29 12:47:58 +0000758 */
Zimuzoef65fb82019-02-28 10:44:29 +0000759 public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) {
760 List<MonitoredPackage> removedPackages = new ArrayList<>();
Zimuzo6efba542018-11-29 12:47:58 +0000761 synchronized (mName) {
762 Iterator<MonitoredPackage> it = mPackages.values().iterator();
763 while (it.hasNext()) {
764 MonitoredPackage p = it.next();
765 long newDuration = p.mDurationMs - elapsedMs;
766 if (newDuration > 0) {
767 p.mDurationMs = newDuration;
768 } else {
Zimuzoef65fb82019-02-28 10:44:29 +0000769 if (!p.mHasPassedHealthCheck) {
770 removedPackages.add(p);
771 }
Zimuzo6efba542018-11-29 12:47:58 +0000772 it.remove();
773 }
774 }
Zimuzoef65fb82019-02-28 10:44:29 +0000775 return removedPackages;
Zimuzo6efba542018-11-29 12:47:58 +0000776 }
777 }
778
779 /**
780 * Increments failure counts of {@code packageName}.
781 * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
782 */
783 public boolean onPackageFailure(String packageName) {
784 synchronized (mName) {
785 MonitoredPackage p = mPackages.get(packageName);
786 if (p != null) {
787 return p.onFailure();
788 }
789 return false;
790 }
791 }
792
793 /**
794 * Returns one ObserverInternal from the {@code parser} and advances its state.
795 *
796 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
797 * #loadFromFile which in turn is only called on construction of the
798 * singleton PackageWatchdog.
799 **/
800 public static ObserverInternal read(XmlPullParser parser) {
801 String observerName = null;
802 if (TAG_OBSERVER.equals(parser.getName())) {
803 observerName = parser.getAttributeValue(null, ATTR_NAME);
804 if (TextUtils.isEmpty(observerName)) {
Zimuzo9284e742019-02-22 12:09:28 +0000805 Slog.wtf(TAG, "Unable to read observer name");
Zimuzo6efba542018-11-29 12:47:58 +0000806 return null;
807 }
808 }
809 List<MonitoredPackage> packages = new ArrayList<>();
810 int innerDepth = parser.getDepth();
811 try {
812 while (XmlUtils.nextElementWithin(parser, innerDepth)) {
813 if (TAG_PACKAGE.equals(parser.getName())) {
Zimuzo9284e742019-02-22 12:09:28 +0000814 try {
815 String packageName = parser.getAttributeValue(null, ATTR_NAME);
816 long duration = Long.parseLong(
817 parser.getAttributeValue(null, ATTR_DURATION));
818 boolean hasPassedHealthCheck = Boolean.parseBoolean(
819 parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
820 if (!TextUtils.isEmpty(packageName)) {
821 packages.add(new MonitoredPackage(packageName, duration,
822 hasPassedHealthCheck));
823 }
824 } catch (NumberFormatException e) {
825 Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
826 continue;
Zimuzo6efba542018-11-29 12:47:58 +0000827 }
828 }
829 }
Zimuzo9284e742019-02-22 12:09:28 +0000830 } catch (XmlPullParserException | IOException e) {
831 Slog.wtf(TAG, "Unable to read observer " + observerName, e);
Zimuzo6efba542018-11-29 12:47:58 +0000832 return null;
833 }
834 if (packages.isEmpty()) {
835 return null;
836 }
837 return new ObserverInternal(observerName, packages);
838 }
839 }
840
841 /** Represents a package along with the time it should be monitored for. */
842 static class MonitoredPackage {
843 public final String mName;
Zimuzo9284e742019-02-22 12:09:28 +0000844 // Whether an explicit health check has passed
845 public boolean mHasPassedHealthCheck;
Zimuzo6efba542018-11-29 12:47:58 +0000846 // System uptime duration to monitor package
847 public long mDurationMs;
848 // System uptime of first package failure
849 private long mUptimeStartMs;
850 // Number of failures since mUptimeStartMs
851 private int mFailures;
852
Zimuzo9284e742019-02-22 12:09:28 +0000853 MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
Zimuzo6efba542018-11-29 12:47:58 +0000854 mName = name;
855 mDurationMs = durationMs;
Zimuzo9284e742019-02-22 12:09:28 +0000856 mHasPassedHealthCheck = hasPassedHealthCheck;
Zimuzo6efba542018-11-29 12:47:58 +0000857 }
858
859 /**
860 * Increment package failures or resets failure count depending on the last package failure.
861 *
862 * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
863 */
864 public synchronized boolean onFailure() {
865 final long now = SystemClock.uptimeMillis();
866 final long duration = now - mUptimeStartMs;
867 if (duration > TRIGGER_DURATION_MS) {
Zimuzo9284e742019-02-22 12:09:28 +0000868 // TODO(b/120598832): Reseting to 1 is not correct
Zimuzo6efba542018-11-29 12:47:58 +0000869 // because there may be more than 1 failure in the last trigger window from now
870 // This is the RescueParty impl, will leave for now
871 mFailures = 1;
872 mUptimeStartMs = now;
873 } else {
874 mFailures++;
875 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000876 boolean failed = mFailures >= TRIGGER_FAILURE_COUNT;
877 if (failed) {
878 mFailures = 0;
879 }
880 return failed;
Zimuzo6efba542018-11-29 12:47:58 +0000881 }
882 }
Zimuzo6efba542018-11-29 12:47:58 +0000883}