blob: ec40971c38ee2081ba03971cf3afbe7fcb55be73 [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;
Zimuzo972e1cd2019-01-28 16:30:01 +000024import android.content.pm.VersionedPackage;
Zimuzo6efba542018-11-29 12:47:58 +000025import android.os.Environment;
26import android.os.Handler;
Zimuzo6efba542018-11-29 12:47:58 +000027import android.os.Looper;
Zimuzo6efba542018-11-29 12:47:58 +000028import android.os.SystemClock;
29import android.text.TextUtils;
30import android.util.ArrayMap;
31import android.util.AtomicFile;
32import android.util.Log;
33import android.util.Slog;
34import android.util.Xml;
35
36import com.android.internal.annotations.GuardedBy;
Zimuzo3eee4382019-01-08 20:42:39 +000037import com.android.internal.annotations.VisibleForTesting;
Zimuzocfaed762019-01-03 21:13:01 +000038import com.android.internal.os.BackgroundThread;
Zimuzo6efba542018-11-29 12:47:58 +000039import com.android.internal.util.FastXmlSerializer;
40import com.android.internal.util.XmlUtils;
41
42import libcore.io.IoUtils;
43
44import org.xmlpull.v1.XmlPullParser;
45import org.xmlpull.v1.XmlPullParserException;
46import org.xmlpull.v1.XmlSerializer;
47
48import java.io.File;
49import java.io.FileNotFoundException;
50import java.io.FileOutputStream;
51import java.io.IOException;
52import java.io.InputStream;
Zimuzoe5009cd2019-01-23 18:11:58 +000053import java.lang.annotation.Retention;
Zimuzo6efba542018-11-29 12:47:58 +000054import java.nio.charset.StandardCharsets;
55import java.util.ArrayList;
Zimuzo3eee4382019-01-08 20:42:39 +000056import java.util.Collections;
Zimuzo6efba542018-11-29 12:47:58 +000057import java.util.Iterator;
58import java.util.List;
Zimuzo3eee4382019-01-08 20:42:39 +000059import java.util.Set;
Zimuzo6efba542018-11-29 12:47:58 +000060
61/**
62 * Monitors the health of packages on the system and notifies interested observers when packages
Zimuzoe5009cd2019-01-23 18:11:58 +000063 * fail. On failure, the registered observer with the least user impacting mitigation will
64 * be notified.
Zimuzo6efba542018-11-29 12:47:58 +000065 */
66public class PackageWatchdog {
67 private static final String TAG = "PackageWatchdog";
68 // Duration to count package failures before it resets to 0
69 private static final int TRIGGER_DURATION_MS = 60000;
70 // Number of package failures within the duration above before we notify observers
Zimuzo3eee4382019-01-08 20:42:39 +000071 static final int TRIGGER_FAILURE_COUNT = 5;
Zimuzo6efba542018-11-29 12:47:58 +000072 private static final int DB_VERSION = 1;
73 private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
74 private static final String TAG_PACKAGE = "package";
75 private static final String TAG_OBSERVER = "observer";
76 private static final String ATTR_VERSION = "version";
77 private static final String ATTR_NAME = "name";
78 private static final String ATTR_DURATION = "duration";
Zimuzo6efba542018-11-29 12:47:58 +000079
80 private static PackageWatchdog sPackageWatchdog;
81
82 private final Object mLock = new Object();
83 // System server context
84 private final Context mContext;
85 // Handler to run package cleanup runnables
86 private final Handler mTimerHandler;
Zimuzoe5009cd2019-01-23 18:11:58 +000087 // Handler for processing IO and observer actions
88 private final Handler mWorkerHandler;
Zimuzo3eee4382019-01-08 20:42:39 +000089 // Contains (observer-name -> observer-handle) that have ever been registered from
Zimuzocfaed762019-01-03 21:13:01 +000090 // previous boots. Observers with all packages expired are periodically pruned.
91 // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
Zimuzo6efba542018-11-29 12:47:58 +000092 @GuardedBy("mLock")
Zimuzo3eee4382019-01-08 20:42:39 +000093 private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
Zimuzocfaed762019-01-03 21:13:01 +000094 // File containing the XML data of monitored packages /data/system/package-watchdog.xml
Zimuzo3eee4382019-01-08 20:42:39 +000095 private final AtomicFile mPolicyFile;
Zimuzo6efba542018-11-29 12:47:58 +000096 // Runnable to prune monitored packages that have expired
97 private final Runnable mPackageCleanup;
98 // Last SystemClock#uptimeMillis a package clean up was executed.
99 // 0 if mPackageCleanup not running.
100 private long mUptimeAtLastRescheduleMs;
101 // Duration a package cleanup was last scheduled for.
102 // 0 if mPackageCleanup not running.
103 private long mDurationAtLastReschedule;
104
Zimuzo3eee4382019-01-08 20:42:39 +0000105 // TODO(zezeozue): Remove redundant context param
Zimuzo6efba542018-11-29 12:47:58 +0000106 private PackageWatchdog(Context context) {
107 mContext = context;
Zimuzo3eee4382019-01-08 20:42:39 +0000108 mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
109 "package-watchdog.xml"));
Zimuzo6efba542018-11-29 12:47:58 +0000110 mTimerHandler = new Handler(Looper.myLooper());
Zimuzoe5009cd2019-01-23 18:11:58 +0000111 mWorkerHandler = BackgroundThread.getHandler();
Zimuzo6efba542018-11-29 12:47:58 +0000112 mPackageCleanup = this::rescheduleCleanup;
113 loadFromFile();
114 }
115
Zimuzo3eee4382019-01-08 20:42:39 +0000116 /**
117 * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
118 * and creates package-watchdog.xml in an apps data directory.
119 */
120 @VisibleForTesting
121 PackageWatchdog(Context context, Looper looper) {
122 mContext = context;
123 mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
124 mTimerHandler = new Handler(looper);
Zimuzoe5009cd2019-01-23 18:11:58 +0000125 mWorkerHandler = mTimerHandler;
Zimuzo3eee4382019-01-08 20:42:39 +0000126 mPackageCleanup = this::rescheduleCleanup;
127 loadFromFile();
128 }
129
130
Zimuzo6efba542018-11-29 12:47:58 +0000131 /** Creates or gets singleton instance of PackageWatchdog. */
Zimuzocfaed762019-01-03 21:13:01 +0000132 public static PackageWatchdog getInstance(Context context) {
133 synchronized (PackageWatchdog.class) {
134 if (sPackageWatchdog == null) {
135 sPackageWatchdog = new PackageWatchdog(context);
136 }
137 return sPackageWatchdog;
Zimuzo6efba542018-11-29 12:47:58 +0000138 }
Zimuzo6efba542018-11-29 12:47:58 +0000139 }
140
141 /**
142 * Registers {@code observer} to listen for package failures
143 *
144 * <p>Observers are expected to call this on boot. It does not specify any packages but
145 * it will resume observing any packages requested from a previous boot.
146 */
147 public void registerHealthObserver(PackageHealthObserver observer) {
148 synchronized (mLock) {
Zimuzo3eee4382019-01-08 20:42:39 +0000149 ObserverInternal internalObserver = mAllObservers.get(observer.getName());
150 if (internalObserver != null) {
151 internalObserver.mRegisteredObserver = observer;
152 }
Zimuzo6efba542018-11-29 12:47:58 +0000153 if (mDurationAtLastReschedule == 0) {
154 // Nothing running, schedule
155 rescheduleCleanup();
156 }
157 }
158 }
159
160 /**
161 * Starts observing the health of the {@code packages} for {@code observer} and notifies
162 * {@code observer} of any package failures within the monitoring duration.
163 *
164 * <p>If {@code observer} is already monitoring a package in {@code packageNames},
Zimuzocfaed762019-01-03 21:13:01 +0000165 * the monitoring window of that package will be reset to {@code durationMs}.
Zimuzo6efba542018-11-29 12:47:58 +0000166 *
167 * @throws IllegalArgumentException if {@code packageNames} is empty
Zimuzocfaed762019-01-03 21:13:01 +0000168 * or {@code durationMs} is less than 1
Zimuzo6efba542018-11-29 12:47:58 +0000169 */
170 public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
Zimuzo3eee4382019-01-08 20:42:39 +0000171 long durationMs) {
Zimuzocfaed762019-01-03 21:13:01 +0000172 if (packageNames.isEmpty() || durationMs < 1) {
Zimuzo6efba542018-11-29 12:47:58 +0000173 throw new IllegalArgumentException("Observation not started, no packages specified"
Zimuzocfaed762019-01-03 21:13:01 +0000174 + "or invalid duration");
Zimuzo6efba542018-11-29 12:47:58 +0000175 }
Zimuzo6efba542018-11-29 12:47:58 +0000176 List<MonitoredPackage> packages = new ArrayList<>();
Zimuzocfaed762019-01-03 21:13:01 +0000177 for (int i = 0; i < packageNames.size(); i++) {
178 packages.add(new MonitoredPackage(packageNames.get(i), durationMs));
Zimuzo6efba542018-11-29 12:47:58 +0000179 }
180 synchronized (mLock) {
181 ObserverInternal oldObserver = mAllObservers.get(observer.getName());
182 if (oldObserver == null) {
183 Slog.d(TAG, observer.getName() + " started monitoring health of packages "
184 + packageNames);
185 mAllObservers.put(observer.getName(),
186 new ObserverInternal(observer.getName(), packages));
187 } else {
188 Slog.d(TAG, observer.getName() + " added the following packages to monitor "
189 + packageNames);
190 oldObserver.updatePackages(packages);
191 }
192 }
193 registerHealthObserver(observer);
194 // Always reschedule because we may need to expire packages
195 // earlier than we are already scheduled for
196 rescheduleCleanup();
Zimuzocfaed762019-01-03 21:13:01 +0000197 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000198 }
199
200 /**
201 * Unregisters {@code observer} from listening to package failure.
202 * Additionally, this stops observing any packages that may have previously been observed
203 * even from a previous boot.
204 */
205 public void unregisterHealthObserver(PackageHealthObserver observer) {
206 synchronized (mLock) {
207 mAllObservers.remove(observer.getName());
Zimuzo6efba542018-11-29 12:47:58 +0000208 }
Zimuzocfaed762019-01-03 21:13:01 +0000209 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000210 }
211
Zimuzo3eee4382019-01-08 20:42:39 +0000212 /**
213 * Returns packages observed by {@code observer}
214 *
215 * @return an empty set if {@code observer} has some packages observerd from a previous boot
216 * but has not registered itself in the current boot to receive notifications. Returns null
217 * if there are no active packages monitored from any boot.
218 */
219 @Nullable
220 public Set<String> getPackages(PackageHealthObserver observer) {
221 synchronized (mLock) {
222 for (int i = 0; i < mAllObservers.size(); i++) {
223 if (observer.getName().equals(mAllObservers.keyAt(i))) {
224 if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
225 return mAllObservers.valueAt(i).mPackages.keySet();
226 }
227 return Collections.emptySet();
228 }
229 }
230 }
231 return null;
232 }
233
Zimuzo6efba542018-11-29 12:47:58 +0000234 /**
235 * Called when a process fails either due to a crash or ANR.
236 *
Zimuzoe5009cd2019-01-23 18:11:58 +0000237 * <p>For each package contained in the process, one registered observer with the least user
238 * impact will be notified for mitigation.
Zimuzo6efba542018-11-29 12:47:58 +0000239 *
240 * <p>This method could be called frequently if there is a severe problem on the device.
241 */
Zimuzo972e1cd2019-01-28 16:30:01 +0000242 public void onPackageFailure(List<VersionedPackage> packages) {
Zimuzoe5009cd2019-01-23 18:11:58 +0000243 mWorkerHandler.post(() -> {
244 synchronized (mLock) {
245 if (mAllObservers.isEmpty()) {
246 return;
247 }
Zimuzocfaed762019-01-03 21:13:01 +0000248
Zimuzo972e1cd2019-01-28 16:30:01 +0000249 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
Zimuzo71d931e2019-02-01 13:08:16 +0000250 VersionedPackage versionedPackage = packages.get(pIndex);
251 // Observer that will receive failure for versionedPackage
Zimuzoe5009cd2019-01-23 18:11:58 +0000252 PackageHealthObserver currentObserverToNotify = null;
253 int currentObserverImpact = Integer.MAX_VALUE;
254
255 // Find observer with least user impact
256 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
257 ObserverInternal observer = mAllObservers.valueAt(oIndex);
258 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
259 if (registeredObserver != null
Zimuzo71d931e2019-02-01 13:08:16 +0000260 && observer.onPackageFailure(versionedPackage.getPackageName())) {
261 int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000262 if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
263 && impact < currentObserverImpact) {
264 currentObserverToNotify = registeredObserver;
265 currentObserverImpact = impact;
266 }
267 }
268 }
269
270 // Execute action with least user impact
271 if (currentObserverToNotify != null) {
Zimuzo71d931e2019-02-01 13:08:16 +0000272 currentObserverToNotify.execute(versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000273 }
Zimuzo3eee4382019-01-08 20:42:39 +0000274 }
Zimuzocfaed762019-01-03 21:13:01 +0000275 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000276 });
Zimuzo6efba542018-11-29 12:47:58 +0000277 }
278
279 // TODO(zezeozue): Optimize write? Maybe only write a separate smaller file?
280 // This currently adds about 7ms extra to shutdown thread
281 /** Writes the package information to file during shutdown. */
282 public void writeNow() {
283 if (!mAllObservers.isEmpty()) {
Zimuzoe5009cd2019-01-23 18:11:58 +0000284 mWorkerHandler.removeCallbacks(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000285 pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
286 saveToFile();
287 Slog.i(TAG, "Last write to update package durations");
288 }
289 }
290
Zimuzoe5009cd2019-01-23 18:11:58 +0000291 /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
292 @Retention(SOURCE)
293 @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
294 PackageHealthObserverImpact.USER_IMPACT_LOW,
295 PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
296 PackageHealthObserverImpact.USER_IMPACT_HIGH})
297 public @interface PackageHealthObserverImpact {
298 /** No action to take. */
299 int USER_IMPACT_NONE = 0;
300 /* Action has low user impact, user of a device will barely notice. */
301 int USER_IMPACT_LOW = 1;
302 /* Action has medium user impact, user of a device will likely notice. */
303 int USER_IMPACT_MEDIUM = 3;
304 /* Action has high user impact, a last resort, user of a device will be very frustrated. */
305 int USER_IMPACT_HIGH = 5;
306 }
307
Zimuzo6efba542018-11-29 12:47:58 +0000308 /** Register instances of this interface to receive notifications on package failure. */
309 public interface PackageHealthObserver {
310 /**
Zimuzo71d931e2019-02-01 13:08:16 +0000311 * Called when health check fails for the {@code versionedPackage}.
Zimuzoe5009cd2019-01-23 18:11:58 +0000312 *
313 * @return any one of {@link PackageHealthObserverImpact} to express the impact
314 * to the user on {@link #execute}
Zimuzo6efba542018-11-29 12:47:58 +0000315 */
Zimuzo71d931e2019-02-01 13:08:16 +0000316 @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
Zimuzoe5009cd2019-01-23 18:11:58 +0000317
318 /**
319 * Executes mitigation for {@link #onHealthCheckFailed}.
320 *
321 * @return {@code true} if action was executed successfully, {@code false} otherwise
322 */
Zimuzo71d931e2019-02-01 13:08:16 +0000323 boolean execute(VersionedPackage versionedPackage);
Zimuzo6efba542018-11-29 12:47:58 +0000324
325 // TODO(zezeozue): Ensure uniqueness?
326 /**
327 * Identifier for the observer, should not change across device updates otherwise the
328 * watchdog may drop observing packages with the old name.
329 */
330 String getName();
331 }
332
333 /** Reschedules handler to prune expired packages from observers. */
334 private void rescheduleCleanup() {
335 synchronized (mLock) {
336 long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
337 if (nextDurationToScheduleMs == Long.MAX_VALUE) {
338 Slog.i(TAG, "No monitored packages, ending package cleanup");
339 mDurationAtLastReschedule = 0;
340 mUptimeAtLastRescheduleMs = 0;
341 return;
342 }
343 long uptimeMs = SystemClock.uptimeMillis();
344 // O if mPackageCleanup not running
345 long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
346 ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000347 // Less than O if mPackageCleanup unexpectedly didn't run yet even though
348 // and we are past the last duration scheduled to run
Zimuzo6efba542018-11-29 12:47:58 +0000349 long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
Zimuzo3eee4382019-01-08 20:42:39 +0000350 if (mUptimeAtLastRescheduleMs == 0
351 || remainingDurationMs <= 0
352 || nextDurationToScheduleMs < remainingDurationMs) {
Zimuzo6efba542018-11-29 12:47:58 +0000353 // First schedule or an earlier reschedule
354 pruneObservers(elapsedDurationMs);
355 mTimerHandler.removeCallbacks(mPackageCleanup);
356 mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
357 mDurationAtLastReschedule = nextDurationToScheduleMs;
358 mUptimeAtLastRescheduleMs = uptimeMs;
359 }
360 }
361 }
362
363 /**
364 * Returns the earliest time a package should expire.
365 * @returns Long#MAX_VALUE if there are no observed packages.
366 */
367 private long getEarliestPackageExpiryLocked() {
368 long shortestDurationMs = Long.MAX_VALUE;
Zimuzocfaed762019-01-03 21:13:01 +0000369 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
370 ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
371 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
372 long duration = packages.valueAt(pIndex).mDurationMs;
373 if (duration < shortestDurationMs) {
374 shortestDurationMs = duration;
Zimuzo6efba542018-11-29 12:47:58 +0000375 }
376 }
377 }
378 Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
Zimuzo3eee4382019-01-08 20:42:39 +0000379
Zimuzo6efba542018-11-29 12:47:58 +0000380 return shortestDurationMs;
381 }
382
383 /**
384 * Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
385 * Discards expired packages and discards observers without any packages.
386 */
387 private void pruneObservers(long elapsedMs) {
388 if (elapsedMs == 0) {
389 return;
390 }
391 synchronized (mLock) {
392 Slog.d(TAG, "Removing expired packages after " + elapsedMs + "ms");
393 Iterator<ObserverInternal> it = mAllObservers.values().iterator();
394 while (it.hasNext()) {
395 ObserverInternal observer = it.next();
396 if (!observer.updateMonitoringDurations(elapsedMs)) {
397 Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
398 it.remove();
399 }
400 }
401 }
Zimuzocfaed762019-01-03 21:13:01 +0000402 saveToFileAsync();
Zimuzo6efba542018-11-29 12:47:58 +0000403 }
404
405 /**
406 * Loads mAllObservers from file.
407 *
408 * <p>Note that this is <b>not</b> thread safe and should only called be called
409 * from the constructor.
410 */
411 private void loadFromFile() {
412 InputStream infile = null;
413 mAllObservers.clear();
414 try {
415 infile = mPolicyFile.openRead();
416 final XmlPullParser parser = Xml.newPullParser();
417 parser.setInput(infile, StandardCharsets.UTF_8.name());
418 XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
419 int outerDepth = parser.getDepth();
420 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
421 ObserverInternal observer = ObserverInternal.read(parser);
422 if (observer != null) {
423 mAllObservers.put(observer.mName, observer);
424 }
425 }
426 } catch (FileNotFoundException e) {
427 // Nothing to monitor
Zimuzocfaed762019-01-03 21:13:01 +0000428 } catch (IOException | NumberFormatException | XmlPullParserException e) {
429 Log.wtf(TAG, "Unable to read monitored packages, deleting file", e);
430 mPolicyFile.delete();
Zimuzo6efba542018-11-29 12:47:58 +0000431 } finally {
432 IoUtils.closeQuietly(infile);
433 }
434 }
435
436 /**
Zimuzocfaed762019-01-03 21:13:01 +0000437 * Persists mAllObservers to file. Threshold information is ignored.
Zimuzo6efba542018-11-29 12:47:58 +0000438 */
439 private boolean saveToFile() {
Zimuzocfaed762019-01-03 21:13:01 +0000440 synchronized (mLock) {
441 FileOutputStream stream;
442 try {
443 stream = mPolicyFile.startWrite();
444 } catch (IOException e) {
445 Slog.w(TAG, "Cannot update monitored packages", e);
446 return false;
Zimuzo6efba542018-11-29 12:47:58 +0000447 }
Zimuzocfaed762019-01-03 21:13:01 +0000448
449 try {
450 XmlSerializer out = new FastXmlSerializer();
451 out.setOutput(stream, StandardCharsets.UTF_8.name());
452 out.startDocument(null, true);
453 out.startTag(null, TAG_PACKAGE_WATCHDOG);
454 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
455 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
456 mAllObservers.valueAt(oIndex).write(out);
457 }
458 out.endTag(null, TAG_PACKAGE_WATCHDOG);
459 out.endDocument();
460 mPolicyFile.finishWrite(stream);
461 return true;
462 } catch (IOException e) {
463 Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
464 mPolicyFile.failWrite(stream);
465 return false;
466 } finally {
467 IoUtils.closeQuietly(stream);
468 }
Zimuzo6efba542018-11-29 12:47:58 +0000469 }
470 }
471
Zimuzocfaed762019-01-03 21:13:01 +0000472 private void saveToFileAsync() {
Zimuzoe5009cd2019-01-23 18:11:58 +0000473 mWorkerHandler.removeCallbacks(this::saveToFile);
474 mWorkerHandler.post(this::saveToFile);
Zimuzo6efba542018-11-29 12:47:58 +0000475 }
476
477 /**
478 * Represents an observer monitoring a set of packages along with the failure thresholds for
479 * each package.
480 */
481 static class ObserverInternal {
482 public final String mName;
483 public final ArrayMap<String, MonitoredPackage> mPackages;
Zimuzo3eee4382019-01-08 20:42:39 +0000484 @Nullable
485 public PackageHealthObserver mRegisteredObserver;
Zimuzo6efba542018-11-29 12:47:58 +0000486
487 ObserverInternal(String name, List<MonitoredPackage> packages) {
488 mName = name;
489 mPackages = new ArrayMap<>();
490 updatePackages(packages);
491 }
492
493 /**
494 * Writes important details to file. Doesn't persist any package failure thresholds.
495 *
496 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
497 * #saveToFile which runs on a single threaded handler.
498 */
499 public boolean write(XmlSerializer out) {
500 try {
501 out.startTag(null, TAG_OBSERVER);
502 out.attribute(null, ATTR_NAME, mName);
503 for (int i = 0; i < mPackages.size(); i++) {
504 MonitoredPackage p = mPackages.valueAt(i);
505 out.startTag(null, TAG_PACKAGE);
506 out.attribute(null, ATTR_NAME, p.mName);
507 out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
508 out.endTag(null, TAG_PACKAGE);
509 }
510 out.endTag(null, TAG_OBSERVER);
511 return true;
512 } catch (IOException e) {
513 Slog.w(TAG, "Cannot save observer", e);
514 return false;
515 }
516 }
517
518 public void updatePackages(List<MonitoredPackage> packages) {
519 synchronized (mName) {
Zimuzocfaed762019-01-03 21:13:01 +0000520 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
521 MonitoredPackage p = packages.get(pIndex);
Zimuzo6efba542018-11-29 12:47:58 +0000522 mPackages.put(p.mName, p);
523 }
524 }
525 }
526
527 /**
528 * Reduces the monitoring durations of all packages observed by this observer by
529 * {@code elapsedMs}. If any duration is less than 0, the package is removed from
530 * observation.
531 *
532 * @returns {@code true} if there are still packages to be observed, {@code false} otherwise
533 */
534 public boolean updateMonitoringDurations(long elapsedMs) {
535 List<MonitoredPackage> packages = new ArrayList<>();
536 synchronized (mName) {
537 Iterator<MonitoredPackage> it = mPackages.values().iterator();
538 while (it.hasNext()) {
539 MonitoredPackage p = it.next();
540 long newDuration = p.mDurationMs - elapsedMs;
541 if (newDuration > 0) {
542 p.mDurationMs = newDuration;
543 } else {
544 it.remove();
545 }
546 }
547 return !mPackages.isEmpty();
548 }
549 }
550
551 /**
552 * Increments failure counts of {@code packageName}.
553 * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
554 */
555 public boolean onPackageFailure(String packageName) {
556 synchronized (mName) {
557 MonitoredPackage p = mPackages.get(packageName);
558 if (p != null) {
559 return p.onFailure();
560 }
561 return false;
562 }
563 }
564
565 /**
566 * Returns one ObserverInternal from the {@code parser} and advances its state.
567 *
568 * <p>Note that this method is <b>not</b> thread safe. It should only be called from
569 * #loadFromFile which in turn is only called on construction of the
570 * singleton PackageWatchdog.
571 **/
572 public static ObserverInternal read(XmlPullParser parser) {
573 String observerName = null;
574 if (TAG_OBSERVER.equals(parser.getName())) {
575 observerName = parser.getAttributeValue(null, ATTR_NAME);
576 if (TextUtils.isEmpty(observerName)) {
577 return null;
578 }
579 }
580 List<MonitoredPackage> packages = new ArrayList<>();
581 int innerDepth = parser.getDepth();
582 try {
583 while (XmlUtils.nextElementWithin(parser, innerDepth)) {
584 if (TAG_PACKAGE.equals(parser.getName())) {
585 String packageName = parser.getAttributeValue(null, ATTR_NAME);
586 long duration = Long.parseLong(
587 parser.getAttributeValue(null, ATTR_DURATION));
588 if (!TextUtils.isEmpty(packageName)) {
589 packages.add(new MonitoredPackage(packageName, duration));
590 }
591 }
592 }
593 } catch (IOException e) {
594 return null;
595 } catch (XmlPullParserException e) {
596 return null;
597 }
598 if (packages.isEmpty()) {
599 return null;
600 }
601 return new ObserverInternal(observerName, packages);
602 }
603 }
604
605 /** Represents a package along with the time it should be monitored for. */
606 static class MonitoredPackage {
607 public final String mName;
608 // System uptime duration to monitor package
609 public long mDurationMs;
610 // System uptime of first package failure
611 private long mUptimeStartMs;
612 // Number of failures since mUptimeStartMs
613 private int mFailures;
614
615 MonitoredPackage(String name, long durationMs) {
616 mName = name;
617 mDurationMs = durationMs;
618 }
619
620 /**
621 * Increment package failures or resets failure count depending on the last package failure.
622 *
623 * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
624 */
625 public synchronized boolean onFailure() {
626 final long now = SystemClock.uptimeMillis();
627 final long duration = now - mUptimeStartMs;
628 if (duration > TRIGGER_DURATION_MS) {
629 // TODO(zezeozue): Reseting to 1 is not correct
630 // because there may be more than 1 failure in the last trigger window from now
631 // This is the RescueParty impl, will leave for now
632 mFailures = 1;
633 mUptimeStartMs = now;
634 } else {
635 mFailures++;
636 }
Zimuzoe5009cd2019-01-23 18:11:58 +0000637 boolean failed = mFailures >= TRIGGER_FAILURE_COUNT;
638 if (failed) {
639 mFailures = 0;
640 }
641 return failed;
Zimuzo6efba542018-11-29 12:47:58 +0000642 }
643 }
Zimuzo6efba542018-11-29 12:47:58 +0000644}