1 /*
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 
17 package com.android.server;
18 
19 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
20 
21 import static java.lang.annotation.RetentionPolicy.SOURCE;
22 
23 import android.annotation.IntDef;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.content.pm.VersionedPackage;
28 import android.net.ConnectivityModuleConnector;
29 import android.os.Environment;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.SystemClock;
33 import android.provider.DeviceConfig;
34 import android.text.TextUtils;
35 import android.util.ArrayMap;
36 import android.util.ArraySet;
37 import android.util.AtomicFile;
38 import android.util.Slog;
39 import android.util.Xml;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.os.BackgroundThread;
44 import com.android.internal.util.FastXmlSerializer;
45 import com.android.internal.util.XmlUtils;
46 
47 import libcore.io.IoUtils;
48 
49 import org.xmlpull.v1.XmlPullParser;
50 import org.xmlpull.v1.XmlPullParserException;
51 import org.xmlpull.v1.XmlSerializer;
52 
53 import java.io.File;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.nio.charset.StandardCharsets;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.concurrent.TimeUnit;
68 
69 /**
70  * Monitors the health of packages on the system and notifies interested observers when packages
71  * fail. On failure, the registered observer with the least user impacting mitigation will
72  * be notified.
73  */
74 public class PackageWatchdog {
75     private static final String TAG = "PackageWatchdog";
76 
77     static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS =
78             "watchdog_trigger_failure_duration_millis";
79     static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
80             "watchdog_trigger_failure_count";
81     static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
82             "watchdog_explicit_health_check_enabled";
83 
84     public static final int FAILURE_REASON_UNKNOWN = 0;
85     public static final int FAILURE_REASON_NATIVE_CRASH = 1;
86     public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
87     public static final int FAILURE_REASON_APP_CRASH = 3;
88     public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
89 
90     @IntDef(prefix = { "FAILURE_REASON_" }, value = {
91             FAILURE_REASON_UNKNOWN,
92             FAILURE_REASON_NATIVE_CRASH,
93             FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
94             FAILURE_REASON_APP_CRASH,
95             FAILURE_REASON_APP_NOT_RESPONDING
96     })
97     @Retention(RetentionPolicy.SOURCE)
98     public @interface FailureReasons {}
99 
100     // Duration to count package failures before it resets to 0
101     private static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
102             (int) TimeUnit.MINUTES.toMillis(1);
103     // Number of package failures within the duration above before we notify observers
104     private static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
105     // Whether explicit health checks are enabled or not
106     private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
107 
108     private static final int DB_VERSION = 1;
109     private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
110     private static final String TAG_PACKAGE = "package";
111     private static final String TAG_OBSERVER = "observer";
112     private static final String ATTR_VERSION = "version";
113     private static final String ATTR_NAME = "name";
114     private static final String ATTR_DURATION = "duration";
115     private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
116     private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
117 
118     @GuardedBy("PackageWatchdog.class")
119     private static PackageWatchdog sPackageWatchdog;
120 
121     private final Object mLock = new Object();
122     // System server context
123     private final Context mContext;
124     // Handler to run short running tasks
125     private final Handler mShortTaskHandler;
126     // Handler for processing IO and long running tasks
127     private final Handler mLongTaskHandler;
128     // Contains (observer-name -> observer-handle) that have ever been registered from
129     // previous boots. Observers with all packages expired are periodically pruned.
130     // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
131     @GuardedBy("mLock")
132     private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
133     // File containing the XML data of monitored packages /data/system/package-watchdog.xml
134     private final AtomicFile mPolicyFile;
135     private final ExplicitHealthCheckController mHealthCheckController;
136     private final ConnectivityModuleConnector mConnectivityModuleConnector;
137     @GuardedBy("mLock")
138     private boolean mIsPackagesReady;
139     // Flag to control whether explicit health checks are supported or not
140     @GuardedBy("mLock")
141     private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
142     @GuardedBy("mLock")
143     private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
144     @GuardedBy("mLock")
145     private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
146     // SystemClock#uptimeMillis when we last executed #syncState
147     // 0 if no prune is scheduled.
148     @GuardedBy("mLock")
149     private long mUptimeAtLastStateSync;
150 
PackageWatchdog(Context context)151     private PackageWatchdog(Context context) {
152         // Needs to be constructed inline
153         this(context, new AtomicFile(
154                         new File(new File(Environment.getDataDirectory(), "system"),
155                                 "package-watchdog.xml")),
156                 new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
157                 new ExplicitHealthCheckController(context),
158                 ConnectivityModuleConnector.getInstance());
159     }
160 
161     /**
162      * Creates a PackageWatchdog that allows injecting dependencies.
163      */
164     @VisibleForTesting
PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, Handler longTaskHandler, ExplicitHealthCheckController controller, ConnectivityModuleConnector connectivityModuleConnector)165     PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
166             Handler longTaskHandler, ExplicitHealthCheckController controller,
167             ConnectivityModuleConnector connectivityModuleConnector) {
168         mContext = context;
169         mPolicyFile = policyFile;
170         mShortTaskHandler = shortTaskHandler;
171         mLongTaskHandler = longTaskHandler;
172         mHealthCheckController = controller;
173         mConnectivityModuleConnector = connectivityModuleConnector;
174         loadFromFile();
175     }
176 
177     /** Creates or gets singleton instance of PackageWatchdog. */
getInstance(Context context)178     public static PackageWatchdog getInstance(Context context) {
179         synchronized (PackageWatchdog.class) {
180             if (sPackageWatchdog == null) {
181                 sPackageWatchdog = new PackageWatchdog(context);
182             }
183             return sPackageWatchdog;
184         }
185     }
186 
187     /**
188      * Called during boot to notify when packages are ready on the device so we can start
189      * binding.
190      */
onPackagesReady()191     public void onPackagesReady() {
192         synchronized (mLock) {
193             mIsPackagesReady = true;
194             mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
195                     packages -> onSupportedPackages(packages),
196                     () -> syncRequestsAsync());
197             setPropertyChangedListenerLocked();
198             updateConfigs();
199             registerConnectivityModuleHealthListener();
200         }
201     }
202 
203     /**
204      * Registers {@code observer} to listen for package failures
205      *
206      * <p>Observers are expected to call this on boot. It does not specify any packages but
207      * it will resume observing any packages requested from a previous boot.
208      */
registerHealthObserver(PackageHealthObserver observer)209     public void registerHealthObserver(PackageHealthObserver observer) {
210         synchronized (mLock) {
211             ObserverInternal internalObserver = mAllObservers.get(observer.getName());
212             if (internalObserver != null) {
213                 internalObserver.mRegisteredObserver = observer;
214             }
215         }
216     }
217 
218     /**
219      * Starts observing the health of the {@code packages} for {@code observer} and notifies
220      * {@code observer} of any package failures within the monitoring duration.
221      *
222      * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
223      * duration if {@link #onHealthCheckPassed} was never called,
224      * {@link PackageHealthObserver#execute} will be called as if the package failed.
225      *
226      * <p>If {@code observer} is already monitoring a package in {@code packageNames},
227      * the monitoring window of that package will be reset to {@code durationMs} and the health
228      * check state will be reset to a default depending on if the package is contained in
229      * {@link mPackagesWithExplicitHealthCheckEnabled}.
230      *
231      * @throws IllegalArgumentException if {@code packageNames} is empty
232      * or {@code durationMs} is less than 1
233      */
startObservingHealth(PackageHealthObserver observer, List<String> packageNames, long durationMs)234     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
235             long durationMs) {
236         if (packageNames.isEmpty()) {
237             Slog.wtf(TAG, "No packages to observe, " + observer.getName());
238             return;
239         }
240         if (durationMs < 1) {
241             // TODO: Instead of failing, monitor for default? 48hrs?
242             throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer "
243                     + observer.getName() + ". Not observing packages " + packageNames);
244         }
245 
246         List<MonitoredPackage> packages = new ArrayList<>();
247         for (int i = 0; i < packageNames.size(); i++) {
248             // Health checks not available yet so health check state will start INACTIVE
249             packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
250         }
251 
252         // Sync before we add the new packages to the observers. This will #pruneObservers,
253         // causing any elapsed time to be deducted from all existing packages before we add new
254         // packages. This maintains the invariant that the elapsed time for ALL (new and existing)
255         // packages is the same.
256         syncState("observing new packages");
257 
258         synchronized (mLock) {
259             ObserverInternal oldObserver = mAllObservers.get(observer.getName());
260             if (oldObserver == null) {
261                 Slog.d(TAG, observer.getName() + " started monitoring health "
262                         + "of packages " + packageNames);
263                 mAllObservers.put(observer.getName(),
264                         new ObserverInternal(observer.getName(), packages));
265             } else {
266                 Slog.d(TAG, observer.getName() + " added the following "
267                         + "packages to monitor " + packageNames);
268                 oldObserver.updatePackagesLocked(packages);
269             }
270         }
271 
272         // Register observer in case not already registered
273         registerHealthObserver(observer);
274 
275         // Sync after we add the new packages to the observers. We may have received packges
276         // requiring an earlier schedule than we are currently scheduled for.
277         syncState("updated observers");
278     }
279 
280     /**
281      * Unregisters {@code observer} from listening to package failure.
282      * Additionally, this stops observing any packages that may have previously been observed
283      * even from a previous boot.
284      */
unregisterHealthObserver(PackageHealthObserver observer)285     public void unregisterHealthObserver(PackageHealthObserver observer) {
286         synchronized (mLock) {
287             mAllObservers.remove(observer.getName());
288         }
289         syncState("unregistering observer: " + observer.getName());
290     }
291 
292     /**
293      * Returns packages observed by {@code observer}
294      *
295      * @return an empty set if {@code observer} has some packages observerd from a previous boot
296      * but has not registered itself in the current boot to receive notifications. Returns null
297      * if there are no active packages monitored from any boot.
298      */
299     @Nullable
getPackages(PackageHealthObserver observer)300     public Set<String> getPackages(PackageHealthObserver observer) {
301         synchronized (mLock) {
302             for (int i = 0; i < mAllObservers.size(); i++) {
303                 if (observer.getName().equals(mAllObservers.keyAt(i))) {
304                     if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
305                         return mAllObservers.valueAt(i).mPackages.keySet();
306                     }
307                     return Collections.emptySet();
308                 }
309             }
310         }
311         return null;
312     }
313 
314     /**
315      * Called when a process fails due to a crash, ANR or explicit health check.
316      *
317      * <p>For each package contained in the process, one registered observer with the least user
318      * impact will be notified for mitigation.
319      *
320      * <p>This method could be called frequently if there is a severe problem on the device.
321      */
onPackageFailure(List<VersionedPackage> packages, @FailureReasons int failureReason)322     public void onPackageFailure(List<VersionedPackage> packages,
323             @FailureReasons int failureReason) {
324         mLongTaskHandler.post(() -> {
325             synchronized (mLock) {
326                 if (mAllObservers.isEmpty()) {
327                     return;
328                 }
329 
330                 for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
331                     VersionedPackage versionedPackage = packages.get(pIndex);
332                     // Observer that will receive failure for versionedPackage
333                     PackageHealthObserver currentObserverToNotify = null;
334                     int currentObserverImpact = Integer.MAX_VALUE;
335 
336                     // Find observer with least user impact
337                     for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
338                         ObserverInternal observer = mAllObservers.valueAt(oIndex);
339                         PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
340                         if (registeredObserver != null
341                                 && observer.onPackageFailureLocked(
342                                         versionedPackage.getPackageName())) {
343                             int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
344                             if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
345                                     && impact < currentObserverImpact) {
346                                 currentObserverToNotify = registeredObserver;
347                                 currentObserverImpact = impact;
348                             }
349                         }
350                     }
351 
352                     // Execute action with least user impact
353                     if (currentObserverToNotify != null) {
354                         currentObserverToNotify.execute(versionedPackage, failureReason);
355                     }
356                 }
357             }
358         });
359     }
360 
361     // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
362     // avoid holding lock?
363     // This currently adds about 7ms extra to shutdown thread
364     /** Writes the package information to file during shutdown. */
writeNow()365     public void writeNow() {
366         synchronized (mLock) {
367             // Must only run synchronous tasks as this runs on the ShutdownThread and no other
368             // thread is guaranteed to run during shutdown.
369             if (!mAllObservers.isEmpty()) {
370                 mLongTaskHandler.removeCallbacks(this::saveToFileAsync);
371                 pruneObserversLocked();
372                 saveToFile();
373                 Slog.i(TAG, "Last write to update package durations");
374             }
375         }
376     }
377 
378     /**
379      * Enables or disables explicit health checks.
380      * <p> If explicit health checks are enabled, the health check service is started.
381      * <p> If explicit health checks are disabled, pending explicit health check requests are
382      * passed and the health check service is stopped.
383      */
setExplicitHealthCheckEnabled(boolean enabled)384     private void setExplicitHealthCheckEnabled(boolean enabled) {
385         synchronized (mLock) {
386             mIsHealthCheckEnabled = enabled;
387             mHealthCheckController.setEnabled(enabled);
388             // Prune to update internal state whenever health check is enabled/disabled
389             syncState("health check state " + (enabled ? "enabled" : "disabled"));
390         }
391     }
392 
393     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
394     @Retention(SOURCE)
395     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
396                      PackageHealthObserverImpact.USER_IMPACT_LOW,
397                      PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
398                      PackageHealthObserverImpact.USER_IMPACT_HIGH})
399     public @interface PackageHealthObserverImpact {
400         /** No action to take. */
401         int USER_IMPACT_NONE = 0;
402         /* Action has low user impact, user of a device will barely notice. */
403         int USER_IMPACT_LOW = 1;
404         /* Action has medium user impact, user of a device will likely notice. */
405         int USER_IMPACT_MEDIUM = 3;
406         /* Action has high user impact, a last resort, user of a device will be very frustrated. */
407         int USER_IMPACT_HIGH = 5;
408     }
409 
410     /** Register instances of this interface to receive notifications on package failure. */
411     public interface PackageHealthObserver {
412         /**
413          * Called when health check fails for the {@code versionedPackage}.
414          *
415          * @return any one of {@link PackageHealthObserverImpact} to express the impact
416          * to the user on {@link #execute}
417          */
onHealthCheckFailed(VersionedPackage versionedPackage)418         @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
419 
420         /**
421          * Executes mitigation for {@link #onHealthCheckFailed}.
422          *
423          * @return {@code true} if action was executed successfully, {@code false} otherwise
424          */
execute(VersionedPackage versionedPackage, @FailureReasons int failureReason)425         boolean execute(VersionedPackage versionedPackage, @FailureReasons int failureReason);
426 
427         // TODO(b/120598832): Ensure uniqueness?
428         /**
429          * Identifier for the observer, should not change across device updates otherwise the
430          * watchdog may drop observing packages with the old name.
431          */
getName()432         String getName();
433     }
434 
getTriggerFailureCount()435     long getTriggerFailureCount() {
436         synchronized (mLock) {
437             return mTriggerFailureCount;
438         }
439     }
440 
441     /**
442      * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
443      */
syncRequestsAsync()444     private void syncRequestsAsync() {
445         mShortTaskHandler.removeCallbacks(this::syncRequests);
446         mShortTaskHandler.post(this::syncRequests);
447     }
448 
449     /**
450      * Syncs health check requests with the {@link ExplicitHealthCheckController}.
451      * Calls to this must be serialized.
452      *
453      * @see #syncRequestsAsync
454      */
syncRequests()455     private void syncRequests() {
456         Set<String> packages = null;
457         synchronized (mLock) {
458             if (mIsPackagesReady) {
459                 packages = getPackagesPendingHealthChecksLocked();
460             } // else, we will sync requests when packages become ready
461         }
462 
463         // Call outside lock to avoid holding lock when calling into the controller.
464         if (packages != null) {
465             Slog.i(TAG, "Syncing health check requests for packages: " + packages);
466             mHealthCheckController.syncRequests(packages);
467         }
468     }
469 
470     /**
471      * Updates the observers monitoring {@code packageName} that explicit health check has passed.
472      *
473      * <p> This update is strictly for registered observers at the time of the call
474      * Observers that register after this signal will have no knowledge of prior signals and will
475      * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
476      *
477      * <p> {@code packageName} can still be considered failed if reported by
478      * {@link #onPackageFailureLocked} before the package expires.
479      *
480      * <p> Triggered by components outside the system server when they are fully functional after an
481      * update.
482      */
onHealthCheckPassed(String packageName)483     private void onHealthCheckPassed(String packageName) {
484         Slog.i(TAG, "Health check passed for package: " + packageName);
485         boolean isStateChanged = false;
486 
487         synchronized (mLock) {
488             for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
489                 ObserverInternal observer = mAllObservers.valueAt(observerIdx);
490                 MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
491 
492                 if (monitoredPackage != null) {
493                     int oldState = monitoredPackage.getHealthCheckStateLocked();
494                     int newState = monitoredPackage.tryPassHealthCheckLocked();
495                     isStateChanged |= oldState != newState;
496                 }
497             }
498         }
499 
500         if (isStateChanged) {
501             syncState("health check passed for " + packageName);
502         }
503     }
504 
onSupportedPackages(List<PackageConfig> supportedPackages)505     private void onSupportedPackages(List<PackageConfig> supportedPackages) {
506         boolean isStateChanged = false;
507 
508         Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
509         Iterator<PackageConfig> it = supportedPackages.iterator();
510         while (it.hasNext()) {
511             PackageConfig info = it.next();
512             supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
513         }
514 
515         synchronized (mLock) {
516             Slog.d(TAG, "Received supported packages " + supportedPackages);
517             Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
518             while (oit.hasNext()) {
519                 Iterator<MonitoredPackage> pit = oit.next().mPackages.values().iterator();
520                 while (pit.hasNext()) {
521                     MonitoredPackage monitoredPackage = pit.next();
522                     String packageName = monitoredPackage.getName();
523                     int oldState = monitoredPackage.getHealthCheckStateLocked();
524                     int newState;
525 
526                     if (supportedPackageTimeouts.containsKey(packageName)) {
527                         // Supported packages become ACTIVE if currently INACTIVE
528                         newState = monitoredPackage.setHealthCheckActiveLocked(
529                                 supportedPackageTimeouts.get(packageName));
530                     } else {
531                         // Unsupported packages are marked as PASSED unless already FAILED
532                         newState = monitoredPackage.tryPassHealthCheckLocked();
533                     }
534                     isStateChanged |= oldState != newState;
535                 }
536             }
537         }
538 
539         if (isStateChanged) {
540             syncState("updated health check supported packages " + supportedPackages);
541         }
542     }
543 
544     @GuardedBy("mLock")
getPackagesPendingHealthChecksLocked()545     private Set<String> getPackagesPendingHealthChecksLocked() {
546         Slog.d(TAG, "Getting all observed packages pending health checks");
547         Set<String> packages = new ArraySet<>();
548         Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
549         while (oit.hasNext()) {
550             ObserverInternal observer = oit.next();
551             Iterator<MonitoredPackage> pit =
552                     observer.mPackages.values().iterator();
553             while (pit.hasNext()) {
554                 MonitoredPackage monitoredPackage = pit.next();
555                 String packageName = monitoredPackage.getName();
556                 if (monitoredPackage.isPendingHealthChecksLocked()) {
557                     packages.add(packageName);
558                 }
559             }
560         }
561         return packages;
562     }
563 
564     /**
565      * Syncs the state of the observers.
566      *
567      * <p> Prunes all observers, saves new state to disk, syncs health check requests with the
568      * health check service and schedules the next state sync.
569      */
syncState(String reason)570     private void syncState(String reason) {
571         synchronized (mLock) {
572             Slog.i(TAG, "Syncing state, reason: " + reason);
573             pruneObserversLocked();
574 
575             saveToFileAsync();
576             syncRequestsAsync();
577 
578             // Done syncing state, schedule the next state sync
579             scheduleNextSyncStateLocked();
580         }
581     }
582 
syncStateWithScheduledReason()583     private void syncStateWithScheduledReason() {
584         syncState("scheduled");
585     }
586 
587     @GuardedBy("mLock")
scheduleNextSyncStateLocked()588     private void scheduleNextSyncStateLocked() {
589         long durationMs = getNextStateSyncMillisLocked();
590         mShortTaskHandler.removeCallbacks(this::syncStateWithScheduledReason);
591         if (durationMs == Long.MAX_VALUE) {
592             Slog.i(TAG, "Cancelling state sync, nothing to sync");
593             mUptimeAtLastStateSync = 0;
594         } else {
595             Slog.i(TAG, "Scheduling next state sync in " + durationMs + "ms");
596             mUptimeAtLastStateSync = SystemClock.uptimeMillis();
597             mShortTaskHandler.postDelayed(this::syncStateWithScheduledReason, durationMs);
598         }
599     }
600 
601     /**
602      * Returns the next duration in millis to sync the watchdog state.
603      *
604      * @returns Long#MAX_VALUE if there are no observed packages.
605      */
606     @GuardedBy("mLock")
getNextStateSyncMillisLocked()607     private long getNextStateSyncMillisLocked() {
608         long shortestDurationMs = Long.MAX_VALUE;
609         for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
610             ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
611             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
612                 MonitoredPackage mp = packages.valueAt(pIndex);
613                 long duration = mp.getShortestScheduleDurationMsLocked();
614                 if (duration < shortestDurationMs) {
615                     shortestDurationMs = duration;
616                 }
617             }
618         }
619         return shortestDurationMs;
620     }
621 
622     /**
623      * Removes {@code elapsedMs} milliseconds from all durations on monitored packages
624      * and updates other internal state.
625      */
626     @GuardedBy("mLock")
pruneObserversLocked()627     private void pruneObserversLocked() {
628         long elapsedMs = mUptimeAtLastStateSync == 0
629                 ? 0 : SystemClock.uptimeMillis() - mUptimeAtLastStateSync;
630         if (elapsedMs <= 0) {
631             Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
632             return;
633         }
634 
635         Slog.i(TAG, "Removing " + elapsedMs + "ms from all packages on all observers");
636         Iterator<ObserverInternal> it = mAllObservers.values().iterator();
637         while (it.hasNext()) {
638             ObserverInternal observer = it.next();
639             Set<MonitoredPackage> failedPackages =
640                     observer.prunePackagesLocked(elapsedMs);
641             if (!failedPackages.isEmpty()) {
642                 onHealthCheckFailed(observer, failedPackages);
643             }
644             if (observer.mPackages.isEmpty()) {
645                 Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
646                 it.remove();
647             }
648         }
649     }
650 
onHealthCheckFailed(ObserverInternal observer, Set<MonitoredPackage> failedPackages)651     private void onHealthCheckFailed(ObserverInternal observer,
652             Set<MonitoredPackage> failedPackages) {
653         mLongTaskHandler.post(() -> {
654             synchronized (mLock) {
655                 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
656                 if (registeredObserver != null) {
657                     Iterator<MonitoredPackage> it = failedPackages.iterator();
658                     while (it.hasNext()) {
659                         String failedPackage = it.next().getName();
660                         Slog.i(TAG, "Explicit health check failed for package " + failedPackage);
661                         VersionedPackage versionedPkg = getVersionedPackage(failedPackage);
662                         if (versionedPkg == null) {
663                             Slog.w(TAG, "Explicit health check failed but could not find package "
664                                     + failedPackage);
665                             // TODO(b/120598832): Skip. We only continue to pass tests for now since
666                             // the tests don't install any packages
667                             versionedPkg = new VersionedPackage(failedPackage, 0L);
668                         }
669                         registeredObserver.execute(versionedPkg,
670                                 PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
671                     }
672                 }
673             }
674         });
675     }
676 
677     @Nullable
getVersionedPackage(String packageName)678     private VersionedPackage getVersionedPackage(String packageName) {
679         final PackageManager pm = mContext.getPackageManager();
680         if (pm == null) {
681             return null;
682         }
683         try {
684             final long versionCode = pm.getPackageInfo(
685                     packageName, 0 /* flags */).getLongVersionCode();
686             return new VersionedPackage(packageName, versionCode);
687         } catch (PackageManager.NameNotFoundException e) {
688             return null;
689         }
690     }
691 
692     /**
693      * Loads mAllObservers from file.
694      *
695      * <p>Note that this is <b>not</b> thread safe and should only called be called
696      * from the constructor.
697      */
loadFromFile()698     private void loadFromFile() {
699         InputStream infile = null;
700         mAllObservers.clear();
701         try {
702             infile = mPolicyFile.openRead();
703             final XmlPullParser parser = Xml.newPullParser();
704             parser.setInput(infile, StandardCharsets.UTF_8.name());
705             XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
706             int outerDepth = parser.getDepth();
707             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
708                 ObserverInternal observer = ObserverInternal.read(parser, this);
709                 if (observer != null) {
710                     mAllObservers.put(observer.mName, observer);
711                 }
712             }
713         } catch (FileNotFoundException e) {
714             // Nothing to monitor
715         } catch (IOException | NumberFormatException | XmlPullParserException e) {
716             Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
717             mPolicyFile.delete();
718         } finally {
719             IoUtils.closeQuietly(infile);
720         }
721     }
722 
723     /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
setPropertyChangedListenerLocked()724     private void setPropertyChangedListenerLocked() {
725         DeviceConfig.addOnPropertiesChangedListener(
726                 DeviceConfig.NAMESPACE_ROLLBACK,
727                 mContext.getMainExecutor(),
728                 (properties) -> {
729                     if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
730                         return;
731                     }
732                     updateConfigs();
733                 });
734     }
735 
736     /**
737      * Health check is enabled or disabled after reading the flags
738      * from DeviceConfig.
739      */
updateConfigs()740     private void updateConfigs() {
741         synchronized (mLock) {
742             mTriggerFailureCount = DeviceConfig.getInt(
743                     DeviceConfig.NAMESPACE_ROLLBACK,
744                     PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
745                     DEFAULT_TRIGGER_FAILURE_COUNT);
746             if (mTriggerFailureCount <= 0) {
747                 mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
748             }
749 
750             mTriggerFailureDurationMs = DeviceConfig.getInt(
751                     DeviceConfig.NAMESPACE_ROLLBACK,
752                     PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
753                     DEFAULT_TRIGGER_FAILURE_DURATION_MS);
754             if (mTriggerFailureDurationMs <= 0) {
755                 mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_COUNT;
756             }
757 
758             setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
759                     DeviceConfig.NAMESPACE_ROLLBACK,
760                     PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
761                     DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED));
762         }
763     }
764 
registerConnectivityModuleHealthListener()765     private void registerConnectivityModuleHealthListener() {
766         // TODO: have an internal method to trigger a rollback by reporting high severity errors,
767         // and rely on ActivityManager to inform the watchdog of severe network stack crashes
768         // instead of having this listener in parallel.
769         mConnectivityModuleConnector.registerHealthListener(
770                 packageName -> {
771                     final VersionedPackage pkg = getVersionedPackage(packageName);
772                     if (pkg == null) {
773                         Slog.wtf(TAG, "NetworkStack failed but could not find its package");
774                         return;
775                     }
776                     // This is a severe failure and recovery should be attempted immediately.
777                     // TODO: have a better way to handle such failures.
778                     final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
779                     final long failureCount = getTriggerFailureCount();
780                     for (int i = 0; i < failureCount; i++) {
781                         onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
782                     }
783                 });
784     }
785 
786     /**
787      * Persists mAllObservers to file. Threshold information is ignored.
788      */
saveToFile()789     private boolean saveToFile() {
790         Slog.i(TAG, "Saving observer state to file");
791         synchronized (mLock) {
792             FileOutputStream stream;
793             try {
794                 stream = mPolicyFile.startWrite();
795             } catch (IOException e) {
796                 Slog.w(TAG, "Cannot update monitored packages", e);
797                 return false;
798             }
799 
800             try {
801                 XmlSerializer out = new FastXmlSerializer();
802                 out.setOutput(stream, StandardCharsets.UTF_8.name());
803                 out.startDocument(null, true);
804                 out.startTag(null, TAG_PACKAGE_WATCHDOG);
805                 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
806                 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
807                     mAllObservers.valueAt(oIndex).writeLocked(out);
808                 }
809                 out.endTag(null, TAG_PACKAGE_WATCHDOG);
810                 out.endDocument();
811                 mPolicyFile.finishWrite(stream);
812                 return true;
813             } catch (IOException e) {
814                 Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
815                 mPolicyFile.failWrite(stream);
816                 return false;
817             } finally {
818                 IoUtils.closeQuietly(stream);
819             }
820         }
821     }
822 
saveToFileAsync()823     private void saveToFileAsync() {
824         if (!mLongTaskHandler.hasCallbacks(this::saveToFile)) {
825             mLongTaskHandler.post(this::saveToFile);
826         }
827     }
828 
829     /**
830      * Represents an observer monitoring a set of packages along with the failure thresholds for
831      * each package.
832      *
833      * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
834      * instances of this class.
835      */
836     //TODO(b/120598832): Remove 'm' from non-private fields
837     private static class ObserverInternal {
838         public final String mName;
839         //TODO(b/120598832): Add getter for mPackages
840         @GuardedBy("mLock")
841         public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
842         @Nullable
843         @GuardedBy("mLock")
844         public PackageHealthObserver mRegisteredObserver;
845 
ObserverInternal(String name, List<MonitoredPackage> packages)846         ObserverInternal(String name, List<MonitoredPackage> packages) {
847             mName = name;
848             updatePackagesLocked(packages);
849         }
850 
851         /**
852          * Writes important {@link MonitoredPackage} details for this observer to file.
853          * Does not persist any package failure thresholds.
854          */
855         @GuardedBy("mLock")
writeLocked(XmlSerializer out)856         public boolean writeLocked(XmlSerializer out) {
857             try {
858                 out.startTag(null, TAG_OBSERVER);
859                 out.attribute(null, ATTR_NAME, mName);
860                 for (int i = 0; i < mPackages.size(); i++) {
861                     MonitoredPackage p = mPackages.valueAt(i);
862                     p.writeLocked(out);
863                 }
864                 out.endTag(null, TAG_OBSERVER);
865                 return true;
866             } catch (IOException e) {
867                 Slog.w(TAG, "Cannot save observer", e);
868                 return false;
869             }
870         }
871 
872         @GuardedBy("mLock")
updatePackagesLocked(List<MonitoredPackage> packages)873         public void updatePackagesLocked(List<MonitoredPackage> packages) {
874             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
875                 MonitoredPackage p = packages.get(pIndex);
876                 mPackages.put(p.mName, p);
877             }
878         }
879 
880         /**
881          * Reduces the monitoring durations of all packages observed by this observer by
882          * {@code elapsedMs}. If any duration is less than 0, the package is removed from
883          * observation. If any health check duration is less than 0, the health check result
884          * is evaluated.
885          *
886          * @return a {@link Set} of packages that were removed from the observer without explicit
887          * health check passing, or an empty list if no package expired for which an explicit health
888          * check was still pending
889          */
890         @GuardedBy("mLock")
prunePackagesLocked(long elapsedMs)891         private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
892             Set<MonitoredPackage> failedPackages = new ArraySet<>();
893             Iterator<MonitoredPackage> it = mPackages.values().iterator();
894             while (it.hasNext()) {
895                 MonitoredPackage p = it.next();
896                 int oldState = p.getHealthCheckStateLocked();
897                 int newState = p.handleElapsedTimeLocked(elapsedMs);
898                 if (oldState != MonitoredPackage.STATE_FAILED
899                         && newState == MonitoredPackage.STATE_FAILED) {
900                     Slog.i(TAG, "Package " + p.mName + " failed health check");
901                     failedPackages.add(p);
902                 }
903                 if (p.isExpiredLocked()) {
904                     it.remove();
905                 }
906             }
907             return failedPackages;
908         }
909 
910         /**
911          * Increments failure counts of {@code packageName}.
912          * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
913          */
914         @GuardedBy("mLock")
onPackageFailureLocked(String packageName)915         public boolean onPackageFailureLocked(String packageName) {
916             MonitoredPackage p = mPackages.get(packageName);
917             if (p != null) {
918                 return p.onFailureLocked();
919             }
920             return false;
921         }
922 
923         /**
924          * Returns one ObserverInternal from the {@code parser} and advances its state.
925          *
926          * <p>Note that this method is <b>not</b> thread safe. It should only be called from
927          * #loadFromFile which in turn is only called on construction of the
928          * singleton PackageWatchdog.
929          **/
read(XmlPullParser parser, PackageWatchdog watchdog)930         public static ObserverInternal read(XmlPullParser parser, PackageWatchdog watchdog) {
931             String observerName = null;
932             if (TAG_OBSERVER.equals(parser.getName())) {
933                 observerName = parser.getAttributeValue(null, ATTR_NAME);
934                 if (TextUtils.isEmpty(observerName)) {
935                     Slog.wtf(TAG, "Unable to read observer name");
936                     return null;
937                 }
938             }
939             List<MonitoredPackage> packages = new ArrayList<>();
940             int innerDepth = parser.getDepth();
941             try {
942                 while (XmlUtils.nextElementWithin(parser, innerDepth)) {
943                     if (TAG_PACKAGE.equals(parser.getName())) {
944                         try {
945                             String packageName = parser.getAttributeValue(null, ATTR_NAME);
946                             long duration = Long.parseLong(
947                                     parser.getAttributeValue(null, ATTR_DURATION));
948                             long healthCheckDuration = Long.parseLong(
949                                     parser.getAttributeValue(null,
950                                             ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
951                             boolean hasPassedHealthCheck = Boolean.parseBoolean(
952                                     parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
953                             if (!TextUtils.isEmpty(packageName)) {
954                                 packages.add(watchdog.new MonitoredPackage(packageName, duration,
955                                         healthCheckDuration, hasPassedHealthCheck));
956                             }
957                         } catch (NumberFormatException e) {
958                             Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
959                             continue;
960                         }
961                     }
962                 }
963             } catch (XmlPullParserException | IOException e) {
964                 Slog.wtf(TAG, "Unable to read observer " + observerName, e);
965                 return null;
966             }
967             if (packages.isEmpty()) {
968                 return null;
969             }
970             return new ObserverInternal(observerName, packages);
971         }
972     }
973 
974     /**
975      * Represents a package and its health check state along with the time
976      * it should be monitored for.
977      *
978      * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
979      * instances of this class.
980      */
981     class MonitoredPackage {
982         // Health check states
983         // TODO(b/120598832): Prefix with HEALTH_CHECK
984         // mName has not passed health check but has requested a health check
985         public static final int STATE_ACTIVE = 0;
986         // mName has not passed health check and has not requested a health check
987         public static final int STATE_INACTIVE = 1;
988         // mName has passed health check
989         public static final int STATE_PASSED = 2;
990         // mName has failed health check
991         public static final int STATE_FAILED = 3;
992 
993         //TODO(b/120598832): VersionedPackage?
994         private final String mName;
995         // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
996         // methods that could change the health check state: handleElapsedTimeLocked and
997         // tryPassHealthCheckLocked
998         private int mHealthCheckState = STATE_INACTIVE;
999         // Whether an explicit health check has passed.
1000         // This value in addition with mHealthCheckDurationMs determines the health check state
1001         // of the package, see #getHealthCheckStateLocked
1002         @GuardedBy("mLock")
1003         private boolean mHasPassedHealthCheck;
1004         // System uptime duration to monitor package.
1005         @GuardedBy("mLock")
1006         private long mDurationMs;
1007         // System uptime duration to check the result of an explicit health check
1008         // Initially, MAX_VALUE until we get a value from the health check service
1009         // and request health checks.
1010         // This value in addition with mHasPassedHealthCheck determines the health check state
1011         // of the package, see #getHealthCheckStateLocked
1012         @GuardedBy("mLock")
1013         private long mHealthCheckDurationMs = Long.MAX_VALUE;
1014         // System uptime of first package failure
1015         @GuardedBy("mLock")
1016         private long mUptimeStartMs;
1017         // Number of failures since mUptimeStartMs
1018         @GuardedBy("mLock")
1019         private int mFailures;
1020 
MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck)1021         MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
1022             this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
1023         }
1024 
MonitoredPackage(String name, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck)1025         MonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
1026                 boolean hasPassedHealthCheck) {
1027             mName = name;
1028             mDurationMs = durationMs;
1029             mHealthCheckDurationMs = healthCheckDurationMs;
1030             mHasPassedHealthCheck = hasPassedHealthCheck;
1031             updateHealthCheckStateLocked();
1032         }
1033 
1034         /** Writes the salient fields to disk using {@code out}. */
1035         @GuardedBy("mLock")
writeLocked(XmlSerializer out)1036         public void writeLocked(XmlSerializer out) throws IOException {
1037             out.startTag(null, TAG_PACKAGE);
1038             out.attribute(null, ATTR_NAME, mName);
1039             out.attribute(null, ATTR_DURATION, String.valueOf(mDurationMs));
1040             out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
1041                     String.valueOf(mHealthCheckDurationMs));
1042             out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
1043                     String.valueOf(mHasPassedHealthCheck));
1044             out.endTag(null, TAG_PACKAGE);
1045         }
1046 
1047         /**
1048          * Increment package failures or resets failure count depending on the last package failure.
1049          *
1050          * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
1051          */
1052         @GuardedBy("mLock")
onFailureLocked()1053         public boolean onFailureLocked() {
1054             final long now = SystemClock.uptimeMillis();
1055             final long duration = now - mUptimeStartMs;
1056             if (duration > mTriggerFailureDurationMs) {
1057                 // TODO(b/120598832): Reseting to 1 is not correct
1058                 // because there may be more than 1 failure in the last trigger window from now
1059                 // This is the RescueParty impl, will leave for now
1060                 mFailures = 1;
1061                 mUptimeStartMs = now;
1062             } else {
1063                 mFailures++;
1064             }
1065             boolean failed = mFailures >= mTriggerFailureCount;
1066             if (failed) {
1067                 mFailures = 0;
1068             }
1069             return failed;
1070         }
1071 
1072         /**
1073          * Sets the initial health check duration.
1074          *
1075          * @return the new health check state
1076          */
1077         @GuardedBy("mLock")
setHealthCheckActiveLocked(long initialHealthCheckDurationMs)1078         public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
1079             if (initialHealthCheckDurationMs <= 0) {
1080                 Slog.wtf(TAG, "Cannot set non-positive health check duration "
1081                         + initialHealthCheckDurationMs + "ms for package " + mName
1082                         + ". Using total duration " + mDurationMs + "ms instead");
1083                 initialHealthCheckDurationMs = mDurationMs;
1084             }
1085             if (mHealthCheckState == STATE_INACTIVE) {
1086                 // Transitions to ACTIVE
1087                 mHealthCheckDurationMs = initialHealthCheckDurationMs;
1088             }
1089             return updateHealthCheckStateLocked();
1090         }
1091 
1092         /**
1093          * Updates the monitoring durations of the package.
1094          *
1095          * @return the new health check state
1096          */
1097         @GuardedBy("mLock")
handleElapsedTimeLocked(long elapsedMs)1098         public int handleElapsedTimeLocked(long elapsedMs) {
1099             if (elapsedMs <= 0) {
1100                 Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + mName);
1101                 return mHealthCheckState;
1102             }
1103             // Transitions to FAILED if now <= 0 and health check not passed
1104             mDurationMs -= elapsedMs;
1105             if (mHealthCheckState == STATE_ACTIVE) {
1106                 // We only update health check durations if we have #setHealthCheckActiveLocked
1107                 // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
1108                 // Transitions to FAILED if now <= 0 and health check not passed
1109                 mHealthCheckDurationMs -= elapsedMs;
1110             }
1111             return updateHealthCheckStateLocked();
1112         }
1113 
1114         /**
1115          * Marks the health check as passed and transitions to {@link #STATE_PASSED}
1116          * if not yet {@link #STATE_FAILED}.
1117          *
1118          * @return the new health check state
1119          */
1120         @GuardedBy("mLock")
tryPassHealthCheckLocked()1121         public int tryPassHealthCheckLocked() {
1122             if (mHealthCheckState != STATE_FAILED) {
1123                 // FAILED is a final state so only pass if we haven't failed
1124                 // Transition to PASSED
1125                 mHasPassedHealthCheck = true;
1126             }
1127             return updateHealthCheckStateLocked();
1128         }
1129 
1130         /** Returns the monitored package name. */
getName()1131         private String getName() {
1132             return mName;
1133         }
1134 
1135         //TODO(b/120598832): IntDef
1136         /**
1137          * Returns the current health check state, any of {@link #STATE_ACTIVE},
1138          * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
1139          */
1140         @GuardedBy("mLock")
getHealthCheckStateLocked()1141         public int getHealthCheckStateLocked() {
1142             return mHealthCheckState;
1143         }
1144 
1145         /**
1146          * Returns the shortest duration before the package should be scheduled for a prune.
1147          *
1148          * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
1149          */
1150         @GuardedBy("mLock")
getShortestScheduleDurationMsLocked()1151         public long getShortestScheduleDurationMsLocked() {
1152             // Consider health check duration only if #isPendingHealthChecksLocked is true
1153             return Math.min(toPositive(mDurationMs),
1154                     isPendingHealthChecksLocked()
1155                     ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE);
1156         }
1157 
1158         /**
1159          * Returns {@code true} if the total duration left to monitor the package is less than or
1160          * equal to 0 {@code false} otherwise.
1161          */
1162         @GuardedBy("mLock")
isExpiredLocked()1163         public boolean isExpiredLocked() {
1164             return mDurationMs <= 0;
1165         }
1166 
1167         /**
1168          * Returns {@code true} if the package, {@link #getName} is expecting health check results
1169          * {@code false} otherwise.
1170          */
1171         @GuardedBy("mLock")
isPendingHealthChecksLocked()1172         public boolean isPendingHealthChecksLocked() {
1173             return mHealthCheckState == STATE_ACTIVE || mHealthCheckState == STATE_INACTIVE;
1174         }
1175 
1176         /**
1177          * Updates the health check state based on {@link #mHasPassedHealthCheck}
1178          * and {@link #mHealthCheckDurationMs}.
1179          *
1180          * @return the new health check state
1181          */
1182         @GuardedBy("mLock")
updateHealthCheckStateLocked()1183         private int updateHealthCheckStateLocked() {
1184             int oldState = mHealthCheckState;
1185             if (mHasPassedHealthCheck) {
1186                 // Set final state first to avoid ambiguity
1187                 mHealthCheckState = STATE_PASSED;
1188             } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
1189                 // Set final state first to avoid ambiguity
1190                 mHealthCheckState = STATE_FAILED;
1191             } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
1192                 mHealthCheckState = STATE_INACTIVE;
1193             } else {
1194                 mHealthCheckState = STATE_ACTIVE;
1195             }
1196             Slog.i(TAG, "Updated health check state for package " + mName + ": "
1197                     + toString(oldState) + " -> " + toString(mHealthCheckState));
1198             return mHealthCheckState;
1199         }
1200 
1201         /** Returns a {@link String} representation of the current health check state. */
toString(int state)1202         private String toString(int state) {
1203             switch (state) {
1204                 case STATE_ACTIVE:
1205                     return "ACTIVE";
1206                 case STATE_INACTIVE:
1207                     return "INACTIVE";
1208                 case STATE_PASSED:
1209                     return "PASSED";
1210                 case STATE_FAILED:
1211                     return "FAILED";
1212                 default:
1213                     return "UNKNOWN";
1214             }
1215         }
1216 
1217         /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */
toPositive(long value)1218         private long toPositive(long value) {
1219             return value > 0 ? value : Long.MAX_VALUE;
1220         }
1221     }
1222 }
1223