blob: 660109cf611407ba46e427ed024fc7a9047efe58 [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;
Zimuzo6efba542018-11-29 12:47:58 +000029import android.os.SystemClock;
30import android.text.TextUtils;
31import android.util.ArrayMap;
32import android.util.AtomicFile;
33import android.util.Log;
34import android.util.Slog;
35import android.util.Xml;
36
37import com.android.internal.annotations.GuardedBy;
Zimuzo3eee4382019-01-08 20:42:39 +000038import com.android.internal.annotations.VisibleForTesting;
Zimuzocfaed762019-01-03 21:13:01 +000039import com.android.internal.os.BackgroundThread;
Zimuzo6efba542018-11-29 12:47:58 +000040import com.android.internal.util.FastXmlSerializer;
41import com.android.internal.util.XmlUtils;
42
43import libcore.io.IoUtils;
44
45import org.xmlpull.v1.XmlPullParser;
46import org.xmlpull.v1.XmlPullParserException;
47import org.xmlpull.v1.XmlSerializer;
48
49import java.io.File;
50import java.io.FileNotFoundException;
51import java.io.FileOutputStream;
52import java.io.IOException;
53import java.io.InputStream;
Zimuzoe5009cd2019-01-23 18:11:58 +000054import java.lang.annotation.Retention;
Zimuzo6efba542018-11-29 12:47:58 +000055import java.nio.charset.StandardCharsets;
56import java.util.ArrayList;
Zimuzo3eee4382019-01-08 20:42:39 +000057import java.util.Collections;
Zimuzo6efba542018-11-29 12:47:58 +000058import java.util.Iterator;
59import java.util.List;
Zimuzo3eee4382019-01-08 20:42:39 +000060import java.util.Set;
Zimuzo6efba542018-11-29 12:47:58 +000061
62/**
63 * Monitors the health of packages on the system and notifies interested observers when packages
Zimuzoe5009cd2019-01-23 18:11:58 +000064 * fail. On failure, the registered observer with the least user impacting mitigation will
65 * be notified.
Zimuzo6efba542018-11-29 12:47:58 +000066 */
67public class PackageWatchdog {
68 private static final String TAG = "PackageWatchdog";
69 // Duration to count package failures before it resets to 0
70 private static final int TRIGGER_DURATION_MS = 60000;
71 // Number of package failures within the duration above before we notify observers
Zimuzo3eee4382019-01-08 20:42:39 +000072 static final int TRIGGER_FAILURE_COUNT = 5;
Zimuzo6efba542018-11-29 12:47:58 +000073 private static final int DB_VERSION = 1;
74 private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
75 private static final String TAG_PACKAGE = "package";
76 private static final String TAG_OBSERVER = "observer";
77 private static final String ATTR_VERSION = "version";
78 private static final String ATTR_NAME = "name";
79 private static final String ATTR_DURATION = "duration";
Zimuzo9284e742019-02-22 12:09:28 +000080 private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
Zimuzo6efba542018-11-29 12:47:58 +000081
82 private static PackageWatchdog sPackageWatchdog;
83
84 private final Object mLock = new Object();
85 // System server context
86 private final Context mContext;
87 // Handler to run package cleanup runnables
88 private final Handler mTimerHandler;
Zimuzoe5009cd2019-01-23 18:11:58 +000089 // Handler for processing IO and observer actions
90 private final Handler mWorkerHandler;
Zimuzo3eee4382019-01-08 20:42:39 +000091 // Contains (observer-name -> observer-handle) that have ever been registered from
Zimuzocfaed762019-01-03 21:13:01 +000092 // previous boots. Observers with all packages expired are periodically pruned.
93 // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
Zimuzo6efba542018-11-29 12:47:58 +000094 @GuardedBy("mLock")
Zimuzo3eee4382019-01-08 20:42:39 +000095 private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
Zimuzocfaed762019-01-03 21:13:01 +000096 // File containing the XML data of monitored packages /data/system/package-watchdog.xml
Zimuzo3eee4382019-01-08 20:42:39 +000097 private final AtomicFile mPolicyFile;
Zimuzo6efba542018-11-29 12:47:58 +000098 // Runnable to prune monitored packages that have expired
99 private final Runnable mPackageCleanup;
100 // Last SystemClock#uptimeMillis a package clean up was executed.
101 // 0 if mPackageCleanup not running.
102 private long mUptimeAtLastRescheduleMs;
103 // Duration a package cleanup was last scheduled for.
104 // 0 if mPackageCleanup not running.
105 private long mDurationAtLastReschedule;
106
Zimuzo9284e742019-02-22 12:09:28 +0000107 // TODO(b/120598832): Remove redundant context param
Zimuzo6efba542018-11-29 12:47:58 +0000108 private PackageWatchdog(Context context) {
109 mContext = context;
Zimuzo3eee4382019-01-08 20:42:39 +0000110 mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
111 "package-watchdog.xml"));
Zimuzo6efba542018-11-29 12:47:58 +0000112 mTimerHandler = new Handler(Looper.myLooper());
Zimuzoe5009cd2019-01-23 18:11:58 +0000113 mWorkerHandler = BackgroundThread.getHandler();
Zimuzo6efba542018-11-29 12:47:58 +0000114 mPackageCleanup = this::rescheduleCleanup;
115 loadFromFile();
116 }
117
Zimuzo3eee4382019-01-08 20:42:39 +0000118 /**
119 * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
120 * and creates package-watchdog.xml in an apps data directory.
121 */
122 @VisibleForTesting
123 PackageWatchdog(Context context, Looper looper) {
124 mContext = context;
125 mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
126 mTimerHandler = new Handler(looper);
Zimuzoe5009cd2019-01-23 18:11:58 +0000127 mWorkerHandler = mTimerHandler;
Zimuzo3eee4382019-01-08 20:42:39 +0000128 mPackageCleanup = this::rescheduleCleanup;
129 loadFromFile();
130 }
131
132
Zimuzo6efba542018-11-29 12:47:58 +0000133 /** Creates or gets singleton instance of PackageWatchdog. */
Zimuzocfaed762019-01-03 21:13:01 +0000134 public static PackageWatchdog getInstance(Context context) {
135 synchronized (PackageWatchdog.class) {
136 if (sPackageWatchdog == null) {
137 sPackageWatchdog = new PackageWatchdog(context);
138 }
139 return sPackageWatchdog;
Zimuzo6efba542018-11-29 12:47:58 +0000140 }
Zimuzo6efba542018-11-29 12:47:58 +0000141 }
142
143 /**
144 * Registers {@code observer} to listen for package failures
145 *
146 * <p>Observers are expected to call this on boot. It does not specify any packages but
147 * it will resume observing any packages requested from a previous boot.
148 */
149 public void registerHealthObserver(PackageHealthObserver observer) {
150 synchronized (mLock) {
Zimuzo3eee4382019-01-08 20:42:39 +0000151 ObserverInternal internalObserver = mAllObservers.get(observer.getName());
152 if (internalObserver != null) {
153 internalObserver.mRegisteredObserver = observer;
154 }
Zimuzo6efba542018-11-29 12:47:58 +0000155 if (mDurationAtLastReschedule == 0) {
156 // Nothing running, schedule
157 rescheduleCleanup();
158 }
159 }
160 }
161
162 /**
163 * Starts observing the health of the {@code packages} for {@code observer} and notifies
164 * {@code observer} of any package failures within the monitoring duration.
165 *
Zimuzo9284e742019-02-22 12:09:28 +0000166 * <p>If monitoring a package with {@code withExplicitHealthCheck}, at the end of the monitoring
167 * duration if {@link #onExplicitHealthCheckPassed} was never called,
168 * {@link PackageHealthObserver#execute} will be called as if the package failed.
169 *
Zimuzo6efba542018-11-29 12:47:58 +0000170 * <p>If {@code observer} is already monitoring a package in {@code packageNames},
Zimuzo9284e742019-02-22 12:09:28 +0000171 * the monitoring window of that package will be reset to {@code durationMs} and the health
172 * check state will be reset to a default depending on {@code withExplictHealthCheck}.
Zimuzo6efba542018-11-29 12:47:58 +0000173 *
174 * @throws IllegalArgumentException if {@code packageNames} is empty
Zimuzocfaed762019-01-03 21:13:01 +0000175 * or {@code durationMs} is less than 1
Zimuzo6efba542018-11-29 12:47:58 +0000176 */
177 public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
Zimuzo9284e742019-02-22 12:09:28 +0000178 long durationMs, boolean withExplicitHealthCheck) {
Zimuzocfaed762019-01-03 21:13:01 +0000179 if (packageNames.isEmpty() || durationMs < 1) {
Zimuzo6efba542018-11-29 12:47:58 +0000180 throw new IllegalArgumentException("Observation not started, no packages specified"
Zimuzocfaed762019-01-03 21:13:01 +0000181 + "or invalid duration");
Zimuzo6efba542018-11-29 12:47:58 +0000182 }
Zimuzo6efba542018-11-29 12:47:58 +0000183 List<MonitoredPackage> packages = new ArrayList<>();
Zimuzocfaed762019-01-03 21:13:01 +0000184 for (int i = 0; i < packageNames.size(); i++) {
Zimuzo9284e742019-02-22 12:09:28 +0000185 // When observing packages withExplicitHealthCheck,
186 // MonitoredPackage#mHasExplicitHealthCheckPassed will be false initially.
187 packages.add(new MonitoredPackage(packageNames.get(i), durationMs,
188 !withExplicitHealthCheck));
Zimuzo6efba542018-11-29 12:47:58 +0000189 }
190 synchronized (mLock) {
191 ObserverInternal oldObserver = mAllObservers.get(observer.getName());
192 if (oldObserver == null) {
193 Slog.d(TAG, observer.getName() + " started monitoring health of packages "
194 + packageNames);
195 mAllObservers.put(observer.getName(),
196 new ObserverInternal(observer.getName(), packages));
197 } else {
198 Slog.d(TAG, observer.getName() + " added the following packages to monitor "
199 + packageNames);
200 oldObserver.updatePackages(packages);
201 }
202 }
203 registerHealthObserver(observer);
204 // Always reschedule because we may need to expire packages
205 // earlier than we are already scheduled for
206 rescheduleCleanup();
Zimuzocfaed762019-01-03 21:13:01 +0000207 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000208 }
209
210 /**
211 * Unregisters {@code observer} from listening to package failure.
212 * Additionally, this stops observing any packages that may have previously been observed
213 * even from a previous boot.
214 */
215 public void unregisterHealthObserver(PackageHealthObserver observer) {
216 synchronized (mLock) {
217 mAllObservers.remove(observer.getName());
Zimuzo6efba542018-11-29 12:47:58 +0000218 }
Zimuzocfaed762019-01-03 21:13:01 +0000219 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000220 }
221
Zimuzo3eee4382019-01-08 20:42:39 +0000222 /**
223 * Returns packages observed by {@code observer}
224 *
225 * @return an empty set if {@code observer} has some packages observerd from a previous boot
226 * but has not registered itself in the current boot to receive notifications. Returns null
227 * if there are no active packages monitored from any boot.
228 */
229 @Nullable
230 public Set<String> getPackages(PackageHealthObserver observer) {
231 synchronized (mLock) {
232 for (int i = 0; i < mAllObservers.size(); i++) {
233 if (observer.getName().equals(mAllObservers.keyAt(i))) {
234 if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
235 return mAllObservers.valueAt(i).mPackages.keySet();
236 }
237 return Collections.emptySet();
238 }
239 }
240 }
241 return null;
242 }
243
Zimuzo6efba542018-11-29 12:47:58 +0000244 /**
245 * Called when a process fails either due to a crash or ANR.
246 *
Zimuzoe5009cd2019-01-23 18:11:58 +0000247 * <p>For each package contained in the process, one registered observer with the least user
248 * impact will be notified for mitigation.
Zimuzo6efba542018-11-29 12:47:58 +0000249 *
250 * <p>This method could be called frequently if there is a severe problem on the device.
251 */
Zimuzo972e1cd2019-01-28 16:30:01 +0000252 public void onPackageFailure(List<VersionedPackage> packages) {
Zimuzoe5009cd2019-01-23 18:11:58 +0000253 mWorkerHandler.post(() -> {
254 synchronized (mLock) {
255 if (mAllObservers.isEmpty()) {
256 return;
257 }
Zimuzocfaed762019-01-03 21:13:01 +0000258
Zimuzo972e1cd2019-01-28 16:30:01 +0000259 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
Zimuzo71d931e2019-02-01 13:08:16 +0000260 VersionedPackage versionedPackage = packages.get(pIndex);
261 // Observer that will receive failure for versionedPackage
Zimuzoe5009cd2019-01-23 18:11:58 +0000262 PackageHealthObserver currentObserverToNotify = null;
263 int currentObserverImpact = Integer.MAX_VALUE;
264
265 // Find observer with least user impact
266 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
267 ObserverInternal observer = mAllObservers.valueAt(oIndex);
268 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
269 if (registeredObserver != null
Zimuzo71d931e2019-02-01 13:08:16 +0000270 && observer.onPackageFailure(versionedPackage.getPackageName())) {
271 int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000272 if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
273 && impact < currentObserverImpact) {
274 currentObserverToNotify = registeredObserver;
275 currentObserverImpact = impact;
276 }
277 }
278 }
279
280 // Execute action with least user impact
281 if (currentObserverToNotify != null) {
Zimuzo71d931e2019-02-01 13:08:16 +0000282 currentObserverToNotify.execute(versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000283 }
Zimuzo3eee4382019-01-08 20:42:39 +0000284 }
Zimuzocfaed762019-01-03 21:13:01 +0000285 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000286 });
Zimuzo6efba542018-11-29 12:47:58 +0000287 }
288
Zimuzo9284e742019-02-22 12:09:28 +0000289 /**
290 * Updates the observers monitoring {@code packageName} that explicit health check has passed.
291 *
292 * <p> This update is strictly for registered observers at the time of the call
293 * Observers that register after this signal will have no knowledge of prior signals and will
294 * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
295 *
296 * <p> {@code packageName} can still be considered failed if reported by
297 * {@link #onPackageFailure} before the package expires.
298 *
299 * <p> Triggered by components outside the system server when they are fully functional after an
300 * update.
301 */
302 public void onExplicitHealthCheckPassed(String packageName) {
303 Slog.i(TAG, "Health check passed for package: " + packageName);
304 boolean shouldUpdateFile = false;
305 synchronized (mLock) {
306 for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
307 ObserverInternal observer = mAllObservers.valueAt(observerIdx);
308 MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
309 if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
310 monitoredPackage.mHasPassedHealthCheck = true;
311 shouldUpdateFile = true;
312 }
313 }
314 }
315 if (shouldUpdateFile) {
316 saveToFileAsync();
317 }
318 }
319
320 // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
Zimuzo6efba542018-11-29 12:47:58 +0000321 // This currently adds about 7ms extra to shutdown thread
322 /** Writes the package information to file during shutdown. */
323 public void writeNow() {
324 if (!mAllObservers.isEmpty()) {
Zimuzoe5009cd2019-01-23 18:11:58 +0000325 mWorkerHandler.removeCallbacks(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000326 pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
327 saveToFile();
328 Slog.i(TAG, "Last write to update package durations");
329 }
330 }
331
Zimuzoe5009cd2019-01-23 18:11:58 +0000332 /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
333 @Retention(SOURCE)
334 @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
335 PackageHealthObserverImpact.USER_IMPACT_LOW,
336 PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
337 PackageHealthObserverImpact.USER_IMPACT_HIGH})
338 public @interface PackageHealthObserverImpact {
339 /** No action to take. */
340 int USER_IMPACT_NONE = 0;
341 /* Action has low user impact, user of a device will barely notice. */
342 int USER_IMPACT_LOW = 1;
343 /* Action has medium user impact, user of a device will likely notice. */
344 int USER_IMPACT_MEDIUM = 3;
345 /* Action has high user impact, a last resort, user of a device will be very frustrated. */
346 int USER_IMPACT_HIGH = 5;
347 }
348
Zimuzo6efba542018-11-29 12:47:58 +0000349 /** Register instances of this interface to receive notifications on package failure. */
350 public interface PackageHealthObserver {
351 /**
Zimuzo71d931e2019-02-01 13:08:16 +0000352 * Called when health check fails for the {@code versionedPackage}.
Zimuzoe5009cd2019-01-23 18:11:58 +0000353 *
354 * @return any one of {@link PackageHealthObserverImpact} to express the impact
355 * to the user on {@link #execute}
Zimuzo6efba542018-11-29 12:47:58 +0000356 */
Zimuzo71d931e2019-02-01 13:08:16 +0000357 @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000358
359 /**
360 * Executes mitigation for {@link #onHealthCheckFailed}.
361 *
362 * @return {@code true} if action was executed successfully, {@code false} otherwise
363 */
Zimuzo71d931e2019-02-01 13:08:16 +0000364 boolean execute(VersionedPackage versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000365
Zimuzo9284e742019-02-22 12:09:28 +0000366 // TODO(b/120598832): Ensure uniqueness?
Zimuzo6efba542018-11-29 12:47:58 +0000367 /**
368 * Identifier for the observer, should not change across device updates otherwise the
369 * watchdog may drop observing packages with the old name.
370 */
371 String getName();
372 }
373
374 /** Reschedules handler to prune expired packages from observers. */
375 private void rescheduleCleanup() {
376 synchronized (mLock) {
377 long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
378 if (nextDurationToScheduleMs == Long.MAX_VALUE) {
379 Slog.i(TAG, "No monitored packages, ending package cleanup");
380 mDurationAtLastReschedule = 0;
381 mUptimeAtLastRescheduleMs = 0;
382 return;
383 }
384 long uptimeMs = SystemClock.uptimeMillis();
385 // O if mPackageCleanup not running
386 long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
387 ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000388 // Less than O if mPackageCleanup unexpectedly didn't run yet even though
389 // and we are past the last duration scheduled to run
Zimuzo6efba542018-11-29 12:47:58 +0000390 long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000391 if (mUptimeAtLastRescheduleMs == 0
392 || remainingDurationMs <= 0
393 || nextDurationToScheduleMs < remainingDurationMs) {
Zimuzo6efba542018-11-29 12:47:58 +0000394 // First schedule or an earlier reschedule
395 pruneObservers(elapsedDurationMs);
396 mTimerHandler.removeCallbacks(mPackageCleanup);
397 mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
398 mDurationAtLastReschedule = nextDurationToScheduleMs;
399 mUptimeAtLastRescheduleMs = uptimeMs;
400 }
401 }
402 }
403
404 /**
405 * Returns the earliest time a package should expire.
406 * @returns Long#MAX_VALUE if there are no observed packages.
407 */
408 private long getEarliestPackageExpiryLocked() {
409 long shortestDurationMs = Long.MAX_VALUE;
Zimuzocfaed762019-01-03 21:13:01 +0000410 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
411 ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
412 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
413 long duration = packages.valueAt(pIndex).mDurationMs;
414 if (duration < shortestDurationMs) {
415 shortestDurationMs = duration;
Zimuzo6efba542018-11-29 12:47:58 +0000416 }
417 }
418 }
419 Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
Zimuzo3eee4382019-01-08 20:42:39 +0000420
Zimuzo6efba542018-11-29 12:47:58 +0000421 return shortestDurationMs;
422 }
423
424 /**
425 * Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
426 * Discards expired packages and discards observers without any packages.
427 */
428 private void pruneObservers(long elapsedMs) {
429 if (elapsedMs == 0) {
430 return;
431 }
432 synchronized (mLock) {
433 Slog.d(TAG, "Removing expired packages after " + elapsedMs + "ms");
434 Iterator<ObserverInternal> it = mAllObservers.values().iterator();
435 while (it.hasNext()) {
436 ObserverInternal observer = it.next();
Zimuzoef65fb82019-02-28 10:44:29 +0000437 List<MonitoredPackage> failedPackages =
438 observer.updateMonitoringDurations(elapsedMs);
439 if (!failedPackages.isEmpty()) {
440 onExplicitHealthCheckFailed(observer, failedPackages);
441 }
442 if (observer.mPackages.isEmpty()) {
Zimuzo6efba542018-11-29 12:47:58 +0000443 Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
444 it.remove();
445 }
446 }
447 }
Zimuzocfaed762019-01-03 21:13:01 +0000448 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000449 }
450
Zimuzoef65fb82019-02-28 10:44:29 +0000451 private void onExplicitHealthCheckFailed(ObserverInternal observer,
452 List<MonitoredPackage> failedPackages) {
453 mWorkerHandler.post(() -> {
454 synchronized (mLock) {
455 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
456 if (registeredObserver != null) {
457 PackageManager pm = mContext.getPackageManager();
458 for (int i = 0; i < failedPackages.size(); i++) {
459 String packageName = failedPackages.get(i).mName;
460 long versionCode = 0;
461 try {
462 versionCode = pm.getPackageInfo(
463 packageName, 0 /* flags */).getLongVersionCode();
464 } catch (PackageManager.NameNotFoundException e) {
465 Slog.w(TAG, "Explicit health check failed but could not find package "
466 + packageName);
467 // TODO(b/120598832): Skip. We only continue to pass tests for now since
468 // the tests don't install any packages
469 }
470 registeredObserver.execute(new VersionedPackage(packageName, versionCode));
471 }
472 }
473 }
474 });
475 }
476
Zimuzo6efba542018-11-29 12:47:58 +0000477 /**
478 * Loads mAllObservers from file.
479 *
480 * <p>Note that this is <b>not</b> thread safe and should only called be called
481 * from the constructor.
482 */
483 private void loadFromFile() {
484 InputStream infile = null;
485 mAllObservers.clear();
486 try {
487 infile = mPolicyFile.openRead();
488 final XmlPullParser parser = Xml.newPullParser();
489 parser.setInput(infile, StandardCharsets.UTF_8.name());
490 XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
491 int outerDepth = parser.getDepth();
492 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
493 ObserverInternal observer = ObserverInternal.read(parser);
494 if (observer != null) {
495 mAllObservers.put(observer.mName, observer);
496 }
497 }
498 } catch (FileNotFoundException e) {
499 // Nothing to monitor
Zimuzocfaed762019-01-03 21:13:01 +0000500 } catch (IOException | NumberFormatException | XmlPullParserException e) {
501 Log.wtf(TAG, "Unable to read monitored packages, deleting file", e);
502 mPolicyFile.delete();
Zimuzo6efba542018-11-29 12:47:58 +0000503 } finally {
504 IoUtils.closeQuietly(infile);
505 }
506 }
507
508 /**
Zimuzocfaed762019-01-03 21:13:01 +0000509 * Persists mAllObservers to file. Threshold information is ignored.
Zimuzo6efba542018-11-29 12:47:58 +0000510 */
511 private boolean saveToFile() {
Zimuzocfaed762019-01-03 21:13:01 +0000512 synchronized (mLock) {
513 FileOutputStream stream;
514 try {
515 stream = mPolicyFile.startWrite();
516 } catch (IOException e) {
517 Slog.w(TAG, "Cannot update monitored packages", e);
518 return false;
Zimuzo6efba542018-11-29 12:47:58 +0000519 }
Zimuzocfaed762019-01-03 21:13:01 +0000520
521 try {
522 XmlSerializer out = new FastXmlSerializer();
523 out.setOutput(stream, StandardCharsets.UTF_8.name());
524 out.startDocument(null, true);
525 out.startTag(null, TAG_PACKAGE_WATCHDOG);
526 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
527 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
528 mAllObservers.valueAt(oIndex).write(out);
529 }
530 out.endTag(null, TAG_PACKAGE_WATCHDOG);
531 out.endDocument();
532 mPolicyFile.finishWrite(stream);
533 return true;
534 } catch (IOException e) {
535 Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
536 mPolicyFile.failWrite(stream);
537 return false;
538 } finally {
539 IoUtils.closeQuietly(stream);
540 }
Zimuzo6efba542018-11-29 12:47:58 +0000541 }
542 }
543
Zimuzocfaed762019-01-03 21:13:01 +0000544 private void saveToFileAsync() {
Zimuzoe5009cd2019-01-23 18:11:58 +0000545 mWorkerHandler.removeCallbacks(this::saveToFile);
546 mWorkerHandler.post(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000547 }
548
549 /**
550 * Represents an observer monitoring a set of packages along with the failure thresholds for
551 * each package.
552 */
553 static class ObserverInternal {
554 public final String mName;
Zimuzoef65fb82019-02-28 10:44:29 +0000555 //TODO(b/120598832): Add getter for mPackages
Zimuzo6efba542018-11-29 12:47:58 +0000556 public final ArrayMap<String, MonitoredPackage> mPackages;
Zimuzo3eee4382019-01-08 20:42:39 +0000557 @Nullable
558 public PackageHealthObserver mRegisteredObserver;
Zimuzo6efba542018-11-29 12:47:58 +0000559
560 ObserverInternal(String name, List<MonitoredPackage> packages) {
561 mName = name;
562 mPackages = new ArrayMap<>();
563 updatePackages(packages);
564 }
565
566 /**
567 * Writes important details to file. Doesn't persist any package failure thresholds.
568 *
569 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
570 * #saveToFile which runs on a single threaded handler.
571 */
572 public boolean write(XmlSerializer out) {
573 try {
574 out.startTag(null, TAG_OBSERVER);
575 out.attribute(null, ATTR_NAME, mName);
576 for (int i = 0; i < mPackages.size(); i++) {
577 MonitoredPackage p = mPackages.valueAt(i);
578 out.startTag(null, TAG_PACKAGE);
579 out.attribute(null, ATTR_NAME, p.mName);
580 out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
Zimuzo9284e742019-02-22 12:09:28 +0000581 out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
582 String.valueOf(p.mHasPassedHealthCheck));
Zimuzo6efba542018-11-29 12:47:58 +0000583 out.endTag(null, TAG_PACKAGE);
584 }
585 out.endTag(null, TAG_OBSERVER);
586 return true;
587 } catch (IOException e) {
588 Slog.w(TAG, "Cannot save observer", e);
589 return false;
590 }
591 }
592
593 public void updatePackages(List<MonitoredPackage> packages) {
594 synchronized (mName) {
Zimuzocfaed762019-01-03 21:13:01 +0000595 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
596 MonitoredPackage p = packages.get(pIndex);
Zimuzo6efba542018-11-29 12:47:58 +0000597 mPackages.put(p.mName, p);
598 }
599 }
600 }
601
602 /**
603 * Reduces the monitoring durations of all packages observed by this observer by
604 * {@code elapsedMs}. If any duration is less than 0, the package is removed from
605 * observation.
606 *
Zimuzoef65fb82019-02-28 10:44:29 +0000607 * @returns a {@link List} of packages that were removed from the observer without explicit
608 * health check passing, or an empty list if no package expired for which an explicit health
609 * check was still pending
Zimuzo6efba542018-11-29 12:47:58 +0000610 */
Zimuzoef65fb82019-02-28 10:44:29 +0000611 public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) {
612 List<MonitoredPackage> removedPackages = new ArrayList<>();
Zimuzo6efba542018-11-29 12:47:58 +0000613 synchronized (mName) {
614 Iterator<MonitoredPackage> it = mPackages.values().iterator();
615 while (it.hasNext()) {
616 MonitoredPackage p = it.next();
617 long newDuration = p.mDurationMs - elapsedMs;
618 if (newDuration > 0) {
619 p.mDurationMs = newDuration;
620 } else {
Zimuzoef65fb82019-02-28 10:44:29 +0000621 if (!p.mHasPassedHealthCheck) {
622 removedPackages.add(p);
623 }
Zimuzo6efba542018-11-29 12:47:58 +0000624 it.remove();
625 }
626 }
Zimuzoef65fb82019-02-28 10:44:29 +0000627 return removedPackages;
Zimuzo6efba542018-11-29 12:47:58 +0000628 }
629 }
630
631 /**
632 * Increments failure counts of {@code packageName}.
633 * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
634 */
635 public boolean onPackageFailure(String packageName) {
636 synchronized (mName) {
637 MonitoredPackage p = mPackages.get(packageName);
638 if (p != null) {
639 return p.onFailure();
640 }
641 return false;
642 }
643 }
644
645 /**
646 * Returns one ObserverInternal from the {@code parser} and advances its state.
647 *
648 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
649 * #loadFromFile which in turn is only called on construction of the
650 * singleton PackageWatchdog.
651 **/
652 public static ObserverInternal read(XmlPullParser parser) {
653 String observerName = null;
654 if (TAG_OBSERVER.equals(parser.getName())) {
655 observerName = parser.getAttributeValue(null, ATTR_NAME);
656 if (TextUtils.isEmpty(observerName)) {
Zimuzo9284e742019-02-22 12:09:28 +0000657 Slog.wtf(TAG, "Unable to read observer name");
Zimuzo6efba542018-11-29 12:47:58 +0000658 return null;
659 }
660 }
661 List<MonitoredPackage> packages = new ArrayList<>();
662 int innerDepth = parser.getDepth();
663 try {
664 while (XmlUtils.nextElementWithin(parser, innerDepth)) {
665 if (TAG_PACKAGE.equals(parser.getName())) {
Zimuzo9284e742019-02-22 12:09:28 +0000666 try {
667 String packageName = parser.getAttributeValue(null, ATTR_NAME);
668 long duration = Long.parseLong(
669 parser.getAttributeValue(null, ATTR_DURATION));
670 boolean hasPassedHealthCheck = Boolean.parseBoolean(
671 parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
672 if (!TextUtils.isEmpty(packageName)) {
673 packages.add(new MonitoredPackage(packageName, duration,
674 hasPassedHealthCheck));
675 }
676 } catch (NumberFormatException e) {
677 Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
678 continue;
Zimuzo6efba542018-11-29 12:47:58 +0000679 }
680 }
681 }
Zimuzo9284e742019-02-22 12:09:28 +0000682 } catch (XmlPullParserException | IOException e) {
683 Slog.wtf(TAG, "Unable to read observer " + observerName, e);
Zimuzo6efba542018-11-29 12:47:58 +0000684 return null;
685 }
686 if (packages.isEmpty()) {
687 return null;
688 }
689 return new ObserverInternal(observerName, packages);
690 }
691 }
692
693 /** Represents a package along with the time it should be monitored for. */
694 static class MonitoredPackage {
695 public final String mName;
Zimuzo9284e742019-02-22 12:09:28 +0000696 // Whether an explicit health check has passed
697 public boolean mHasPassedHealthCheck;
Zimuzo6efba542018-11-29 12:47:58 +0000698 // System uptime duration to monitor package
699 public long mDurationMs;
700 // System uptime of first package failure
701 private long mUptimeStartMs;
702 // Number of failures since mUptimeStartMs
703 private int mFailures;
704
Zimuzo9284e742019-02-22 12:09:28 +0000705 MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
Zimuzo6efba542018-11-29 12:47:58 +0000706 mName = name;
707 mDurationMs = durationMs;
Zimuzo9284e742019-02-22 12:09:28 +0000708 mHasPassedHealthCheck = hasPassedHealthCheck;
Zimuzo6efba542018-11-29 12:47:58 +0000709 }
710
711 /**
712 * Increment package failures or resets failure count depending on the last package failure.
713 *
714 * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
715 */
716 public synchronized boolean onFailure() {
717 final long now = SystemClock.uptimeMillis();
718 final long duration = now - mUptimeStartMs;
719 if (duration > TRIGGER_DURATION_MS) {
Zimuzo9284e742019-02-22 12:09:28 +0000720 // TODO(b/120598832): Reseting to 1 is not correct
Zimuzo6efba542018-11-29 12:47:58 +0000721 // because there may be more than 1 failure in the last trigger window from now
722 // This is the RescueParty impl, will leave for now
723 mFailures = 1;
724 mUptimeStartMs = now;
725 } else {
726 mFailures++;
727 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000728 boolean failed = mFailures >= TRIGGER_FAILURE_COUNT;
729 if (failed) {
730 mFailures = 0;
731 }
732 return failed;
Zimuzo6efba542018-11-29 12:47:58 +0000733 }
734 }
Zimuzo6efba542018-11-29 12:47:58 +0000735}