Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 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 | |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 19 | import static java.lang.annotation.RetentionPolicy.SOURCE; |
| 20 | |
| 21 | import android.annotation.IntDef; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 22 | import android.annotation.Nullable; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 23 | import android.content.Context; |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 24 | import android.content.pm.PackageManager; |
Zimuzo | 972e1cd | 2019-01-28 16:30:01 +0000 | [diff] [blame] | 25 | import android.content.pm.VersionedPackage; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 26 | import android.os.Environment; |
| 27 | import android.os.Handler; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 28 | import android.os.Looper; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 29 | import android.os.RemoteException; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 30 | import android.os.SystemClock; |
| 31 | import android.text.TextUtils; |
| 32 | import android.util.ArrayMap; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 33 | import android.util.ArraySet; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 34 | import android.util.AtomicFile; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 35 | import android.util.Slog; |
| 36 | import android.util.Xml; |
| 37 | |
| 38 | import com.android.internal.annotations.GuardedBy; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 39 | import com.android.internal.annotations.VisibleForTesting; |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 40 | import com.android.internal.os.BackgroundThread; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 41 | import com.android.internal.util.FastXmlSerializer; |
| 42 | import com.android.internal.util.XmlUtils; |
| 43 | |
| 44 | import libcore.io.IoUtils; |
| 45 | |
| 46 | import org.xmlpull.v1.XmlPullParser; |
| 47 | import org.xmlpull.v1.XmlPullParserException; |
| 48 | import org.xmlpull.v1.XmlSerializer; |
| 49 | |
| 50 | import java.io.File; |
| 51 | import java.io.FileNotFoundException; |
| 52 | import java.io.FileOutputStream; |
| 53 | import java.io.IOException; |
| 54 | import java.io.InputStream; |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 55 | import java.lang.annotation.Retention; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 56 | import java.nio.charset.StandardCharsets; |
| 57 | import java.util.ArrayList; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 58 | import java.util.Collection; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 59 | import java.util.Collections; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 60 | import java.util.Iterator; |
| 61 | import java.util.List; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 62 | import java.util.Set; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 63 | import java.util.function.Consumer; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 64 | |
| 65 | /** |
| 66 | * Monitors the health of packages on the system and notifies interested observers when packages |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 67 | * fail. On failure, the registered observer with the least user impacting mitigation will |
| 68 | * be notified. |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 69 | */ |
| 70 | public class PackageWatchdog { |
| 71 | private static final String TAG = "PackageWatchdog"; |
| 72 | // Duration to count package failures before it resets to 0 |
| 73 | private static final int TRIGGER_DURATION_MS = 60000; |
| 74 | // Number of package failures within the duration above before we notify observers |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 75 | static final int TRIGGER_FAILURE_COUNT = 5; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 76 | private static final int DB_VERSION = 1; |
| 77 | private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog"; |
| 78 | private static final String TAG_PACKAGE = "package"; |
| 79 | private static final String TAG_OBSERVER = "observer"; |
| 80 | private static final String ATTR_VERSION = "version"; |
| 81 | private static final String ATTR_NAME = "name"; |
| 82 | private static final String ATTR_DURATION = "duration"; |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 83 | private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check"; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 84 | |
| 85 | private static PackageWatchdog sPackageWatchdog; |
| 86 | |
| 87 | private final Object mLock = new Object(); |
| 88 | // System server context |
| 89 | private final Context mContext; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 90 | // Handler to run short running tasks |
| 91 | private final Handler mShortTaskHandler; |
| 92 | // Handler for processing IO and long running tasks |
| 93 | private final Handler mLongTaskHandler; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 94 | // Contains (observer-name -> observer-handle) that have ever been registered from |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 95 | // previous boots. Observers with all packages expired are periodically pruned. |
| 96 | // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 97 | @GuardedBy("mLock") |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 98 | private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>(); |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 99 | // File containing the XML data of monitored packages /data/system/package-watchdog.xml |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 100 | private final AtomicFile mPolicyFile; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 101 | // Runnable to prune monitored packages that have expired |
| 102 | private final Runnable mPackageCleanup; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 103 | private final ExplicitHealthCheckController mHealthCheckController; |
| 104 | // Flag to control whether explicit health checks are supported or not |
| 105 | @GuardedBy("mLock") |
| 106 | private boolean mIsHealthCheckEnabled = true; |
| 107 | @GuardedBy("mLock") |
| 108 | private boolean mIsPackagesReady; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 109 | // Last SystemClock#uptimeMillis a package clean up was executed. |
| 110 | // 0 if mPackageCleanup not running. |
| 111 | private long mUptimeAtLastRescheduleMs; |
| 112 | // Duration a package cleanup was last scheduled for. |
| 113 | // 0 if mPackageCleanup not running. |
| 114 | private long mDurationAtLastReschedule; |
| 115 | |
| 116 | private PackageWatchdog(Context context) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 117 | // Needs to be constructed inline |
| 118 | this(context, new AtomicFile( |
| 119 | new File(new File(Environment.getDataDirectory(), "system"), |
| 120 | "package-watchdog.xml")), |
| 121 | new Handler(Looper.myLooper()), BackgroundThread.getHandler(), |
| 122 | new ExplicitHealthCheckController(context)); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 123 | } |
| 124 | |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 125 | /** |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 126 | * Creates a PackageWatchdog that allows injecting dependencies. |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 127 | */ |
| 128 | @VisibleForTesting |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 129 | PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, |
| 130 | Handler longTaskHandler, ExplicitHealthCheckController controller) { |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 131 | mContext = context; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 132 | mPolicyFile = policyFile; |
| 133 | mShortTaskHandler = shortTaskHandler; |
| 134 | mLongTaskHandler = longTaskHandler; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 135 | mPackageCleanup = this::rescheduleCleanup; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 136 | mHealthCheckController = controller; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 137 | loadFromFile(); |
| 138 | } |
| 139 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 140 | /** Creates or gets singleton instance of PackageWatchdog. */ |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 141 | public static PackageWatchdog getInstance(Context context) { |
| 142 | synchronized (PackageWatchdog.class) { |
| 143 | if (sPackageWatchdog == null) { |
| 144 | sPackageWatchdog = new PackageWatchdog(context); |
| 145 | } |
| 146 | return sPackageWatchdog; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 147 | } |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 148 | } |
| 149 | |
| 150 | /** |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 151 | * Called during boot to notify when packages are ready on the device so we can start |
| 152 | * binding. |
| 153 | */ |
| 154 | public void onPackagesReady() { |
| 155 | synchronized (mLock) { |
| 156 | mIsPackagesReady = true; |
| 157 | mHealthCheckController.setCallbacks(this::updateHealthChecks, |
| 158 | packageName -> onHealthCheckPassed(packageName)); |
| 159 | // Controller is disabled at creation until here where we may enable it |
| 160 | mHealthCheckController.setEnabled(mIsHealthCheckEnabled); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /** |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 165 | * Registers {@code observer} to listen for package failures |
| 166 | * |
| 167 | * <p>Observers are expected to call this on boot. It does not specify any packages but |
| 168 | * it will resume observing any packages requested from a previous boot. |
| 169 | */ |
| 170 | public void registerHealthObserver(PackageHealthObserver observer) { |
| 171 | synchronized (mLock) { |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 172 | ObserverInternal internalObserver = mAllObservers.get(observer.getName()); |
| 173 | if (internalObserver != null) { |
| 174 | internalObserver.mRegisteredObserver = observer; |
| 175 | } |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 176 | if (mDurationAtLastReschedule == 0) { |
| 177 | // Nothing running, schedule |
| 178 | rescheduleCleanup(); |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Starts observing the health of the {@code packages} for {@code observer} and notifies |
| 185 | * {@code observer} of any package failures within the monitoring duration. |
| 186 | * |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 187 | * <p>If monitoring a package supporting explicit health check, at the end of the monitoring |
| 188 | * duration if {@link #onHealthCheckPassed} was never called, |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 189 | * {@link PackageHealthObserver#execute} will be called as if the package failed. |
| 190 | * |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 191 | * <p>If {@code observer} is already monitoring a package in {@code packageNames}, |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 192 | * the monitoring window of that package will be reset to {@code durationMs} and the health |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 193 | * check state will be reset to a default depending on if the package is contained in |
| 194 | * {@link mPackagesWithExplicitHealthCheckEnabled}. |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 195 | * |
| 196 | * @throws IllegalArgumentException if {@code packageNames} is empty |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 197 | * or {@code durationMs} is less than 1 |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 198 | */ |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 199 | public void startObservingHealth(PackageHealthObserver observer, List<String> packages, |
| 200 | long durationMs) { |
| 201 | if (packages.isEmpty() || durationMs < 1) { |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 202 | throw new IllegalArgumentException("Observation not started, no packages specified" |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 203 | + "or invalid duration"); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 204 | } |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 205 | if (!mIsPackagesReady) { |
| 206 | // TODO: Queue observation requests when packages are not ready |
| 207 | Slog.w(TAG, "Attempt to observe when packages not ready"); |
| 208 | return; |
| 209 | } |
| 210 | |
| 211 | try { |
| 212 | Slog.i(TAG, "Getting packages supporting explicit health check"); |
| 213 | mHealthCheckController.getSupportedPackages(supportedPackages -> |
| 214 | startObservingInner(observer, packages, durationMs, supportedPackages)); |
| 215 | } catch (RemoteException e) { |
| 216 | Slog.wtf(TAG, "Failed to fetch supported explicit health check packages"); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | private void startObservingInner(PackageHealthObserver observer, |
| 221 | List<String> packageNames, long durationMs, |
| 222 | List<String> healthCheckSupportedPackages) { |
| 223 | Slog.i(TAG, "Start observing packages " + packageNames |
| 224 | + ". Explicit health check supported packages " + healthCheckSupportedPackages); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 225 | List<MonitoredPackage> packages = new ArrayList<>(); |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 226 | for (int i = 0; i < packageNames.size(); i++) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 227 | String packageName = packageNames.get(i); |
| 228 | boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName); |
| 229 | // If we should enable explicit health check for a package, |
| 230 | // MonitoredPackage#mHasHealthCheckPassed will be false |
| 231 | // until PackageWatchdog#onHealthCheckPassed |
| 232 | packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck)); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 233 | } |
| 234 | synchronized (mLock) { |
| 235 | ObserverInternal oldObserver = mAllObservers.get(observer.getName()); |
| 236 | if (oldObserver == null) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 237 | Slog.d(TAG, observer.getName() + " started monitoring health " |
| 238 | + "of packages " + packageNames); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 239 | mAllObservers.put(observer.getName(), |
| 240 | new ObserverInternal(observer.getName(), packages)); |
| 241 | } else { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 242 | Slog.d(TAG, observer.getName() + " added the following " |
| 243 | + "packages to monitor " + packageNames); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 244 | oldObserver.updatePackages(packages); |
| 245 | } |
| 246 | } |
| 247 | registerHealthObserver(observer); |
| 248 | // Always reschedule because we may need to expire packages |
| 249 | // earlier than we are already scheduled for |
| 250 | rescheduleCleanup(); |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 251 | updateHealthChecks(); |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 252 | saveToFileAsync(); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 253 | } |
| 254 | |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 255 | private void requestCheck(String packageName) { |
| 256 | try { |
| 257 | Slog.d(TAG, "Requesting explicit health check for " + packageName); |
| 258 | mHealthCheckController.request(packageName); |
| 259 | } catch (RemoteException e) { |
| 260 | Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | private void cancelCheck(String packageName) { |
| 265 | try { |
| 266 | Slog.d(TAG, "Cancelling explicit health check for " + packageName); |
| 267 | mHealthCheckController.cancel(packageName); |
| 268 | } catch (RemoteException e) { |
| 269 | Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | private void actOnDifference(Collection<String> collection1, Collection<String> collection2, |
| 274 | Consumer<String> action) { |
| 275 | Iterator<String> iterator = collection1.iterator(); |
| 276 | while (iterator.hasNext()) { |
| 277 | String packageName = iterator.next(); |
| 278 | if (!collection2.contains(packageName)) { |
| 279 | action.accept(packageName); |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | private void updateChecksInner(List<String> supportedPackages, |
| 285 | List<String> previousRequestedPackages) { |
| 286 | boolean shouldUpdateFile = false; |
| 287 | |
| 288 | synchronized (mLock) { |
| 289 | Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages |
| 290 | + ". Requested packages: " + previousRequestedPackages); |
| 291 | Set<String> newRequestedPackages = new ArraySet<>(); |
| 292 | Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); |
| 293 | while (oit.hasNext()) { |
| 294 | ObserverInternal observer = oit.next(); |
| 295 | Iterator<MonitoredPackage> pit = |
| 296 | observer.mPackages.values().iterator(); |
| 297 | while (pit.hasNext()) { |
| 298 | MonitoredPackage monitoredPackage = pit.next(); |
| 299 | String packageName = monitoredPackage.mName; |
| 300 | if (!monitoredPackage.mHasPassedHealthCheck) { |
| 301 | if (supportedPackages.contains(packageName)) { |
| 302 | newRequestedPackages.add(packageName); |
| 303 | } else { |
| 304 | shouldUpdateFile = true; |
| 305 | monitoredPackage.mHasPassedHealthCheck = true; |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | // TODO: Support ending the binding if newRequestedPackages is empty. |
| 311 | // Will have to re-bind when we #startObservingHealth. |
| 312 | |
| 313 | // Cancel packages no longer requested |
| 314 | actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p)); |
| 315 | // Request packages not yet requested |
| 316 | actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p)); |
| 317 | } |
| 318 | |
| 319 | if (shouldUpdateFile) { |
| 320 | saveToFileAsync(); |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | private void updateHealthChecks() { |
| 325 | mShortTaskHandler.post(() -> { |
| 326 | try { |
| 327 | Slog.i(TAG, "Updating explicit health checks for all available packages"); |
| 328 | mHealthCheckController.getSupportedPackages(supported -> { |
| 329 | try { |
| 330 | mHealthCheckController.getRequestedPackages( |
| 331 | requested -> updateChecksInner(supported, requested)); |
| 332 | } catch (RemoteException e) { |
| 333 | Slog.e(TAG, "Failed to get requested health check packages", e); |
| 334 | } |
| 335 | }); |
| 336 | } catch (RemoteException e) { |
| 337 | Slog.e(TAG, "Failed to get supported health check package", e); |
| 338 | } |
| 339 | }); |
| 340 | } |
| 341 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 342 | /** |
| 343 | * Unregisters {@code observer} from listening to package failure. |
| 344 | * Additionally, this stops observing any packages that may have previously been observed |
| 345 | * even from a previous boot. |
| 346 | */ |
| 347 | public void unregisterHealthObserver(PackageHealthObserver observer) { |
| 348 | synchronized (mLock) { |
| 349 | mAllObservers.remove(observer.getName()); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 350 | } |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 351 | saveToFileAsync(); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 352 | } |
| 353 | |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 354 | /** |
| 355 | * Returns packages observed by {@code observer} |
| 356 | * |
| 357 | * @return an empty set if {@code observer} has some packages observerd from a previous boot |
| 358 | * but has not registered itself in the current boot to receive notifications. Returns null |
| 359 | * if there are no active packages monitored from any boot. |
| 360 | */ |
| 361 | @Nullable |
| 362 | public Set<String> getPackages(PackageHealthObserver observer) { |
| 363 | synchronized (mLock) { |
| 364 | for (int i = 0; i < mAllObservers.size(); i++) { |
| 365 | if (observer.getName().equals(mAllObservers.keyAt(i))) { |
| 366 | if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) { |
| 367 | return mAllObservers.valueAt(i).mPackages.keySet(); |
| 368 | } |
| 369 | return Collections.emptySet(); |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | return null; |
| 374 | } |
| 375 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 376 | /** |
| 377 | * Called when a process fails either due to a crash or ANR. |
| 378 | * |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 379 | * <p>For each package contained in the process, one registered observer with the least user |
| 380 | * impact will be notified for mitigation. |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 381 | * |
| 382 | * <p>This method could be called frequently if there is a severe problem on the device. |
| 383 | */ |
Zimuzo | 972e1cd | 2019-01-28 16:30:01 +0000 | [diff] [blame] | 384 | public void onPackageFailure(List<VersionedPackage> packages) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 385 | mLongTaskHandler.post(() -> { |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 386 | synchronized (mLock) { |
| 387 | if (mAllObservers.isEmpty()) { |
| 388 | return; |
| 389 | } |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 390 | |
Zimuzo | 972e1cd | 2019-01-28 16:30:01 +0000 | [diff] [blame] | 391 | for (int pIndex = 0; pIndex < packages.size(); pIndex++) { |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 392 | VersionedPackage versionedPackage = packages.get(pIndex); |
| 393 | // Observer that will receive failure for versionedPackage |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 394 | PackageHealthObserver currentObserverToNotify = null; |
| 395 | int currentObserverImpact = Integer.MAX_VALUE; |
| 396 | |
| 397 | // Find observer with least user impact |
| 398 | for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { |
| 399 | ObserverInternal observer = mAllObservers.valueAt(oIndex); |
| 400 | PackageHealthObserver registeredObserver = observer.mRegisteredObserver; |
| 401 | if (registeredObserver != null |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 402 | && observer.onPackageFailure(versionedPackage.getPackageName())) { |
| 403 | int impact = registeredObserver.onHealthCheckFailed(versionedPackage); |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 404 | if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE |
| 405 | && impact < currentObserverImpact) { |
| 406 | currentObserverToNotify = registeredObserver; |
| 407 | currentObserverImpact = impact; |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | // Execute action with least user impact |
| 413 | if (currentObserverToNotify != null) { |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 414 | currentObserverToNotify.execute(versionedPackage); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 415 | } |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 416 | } |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 417 | } |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 418 | }); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 419 | } |
| 420 | |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 421 | // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 422 | // This currently adds about 7ms extra to shutdown thread |
| 423 | /** Writes the package information to file during shutdown. */ |
| 424 | public void writeNow() { |
| 425 | if (!mAllObservers.isEmpty()) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 426 | mLongTaskHandler.removeCallbacks(this::saveToFile); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 427 | pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs); |
| 428 | saveToFile(); |
| 429 | Slog.i(TAG, "Last write to update package durations"); |
| 430 | } |
| 431 | } |
| 432 | |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 433 | // TODO(b/120598832): Set depending on DeviceConfig flag |
| 434 | /** |
| 435 | * Enables or disables explicit health checks. |
| 436 | * <p> If explicit health checks are enabled, the health check service is started. |
| 437 | * <p> If explicit health checks are disabled, pending explicit health check requests are |
| 438 | * passed and the health check service is stopped. |
| 439 | */ |
| 440 | public void setExplicitHealthCheckEnabled(boolean enabled) { |
| 441 | synchronized (mLock) { |
| 442 | mIsHealthCheckEnabled = enabled; |
| 443 | mHealthCheckController.setEnabled(enabled); |
| 444 | } |
| 445 | } |
| 446 | |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 447 | /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ |
| 448 | @Retention(SOURCE) |
| 449 | @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, |
| 450 | PackageHealthObserverImpact.USER_IMPACT_LOW, |
| 451 | PackageHealthObserverImpact.USER_IMPACT_MEDIUM, |
| 452 | PackageHealthObserverImpact.USER_IMPACT_HIGH}) |
| 453 | public @interface PackageHealthObserverImpact { |
| 454 | /** No action to take. */ |
| 455 | int USER_IMPACT_NONE = 0; |
| 456 | /* Action has low user impact, user of a device will barely notice. */ |
| 457 | int USER_IMPACT_LOW = 1; |
| 458 | /* Action has medium user impact, user of a device will likely notice. */ |
| 459 | int USER_IMPACT_MEDIUM = 3; |
| 460 | /* Action has high user impact, a last resort, user of a device will be very frustrated. */ |
| 461 | int USER_IMPACT_HIGH = 5; |
| 462 | } |
| 463 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 464 | /** Register instances of this interface to receive notifications on package failure. */ |
| 465 | public interface PackageHealthObserver { |
| 466 | /** |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 467 | * Called when health check fails for the {@code versionedPackage}. |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 468 | * |
| 469 | * @return any one of {@link PackageHealthObserverImpact} to express the impact |
| 470 | * to the user on {@link #execute} |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 471 | */ |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 472 | @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage); |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 473 | |
| 474 | /** |
| 475 | * Executes mitigation for {@link #onHealthCheckFailed}. |
| 476 | * |
| 477 | * @return {@code true} if action was executed successfully, {@code false} otherwise |
| 478 | */ |
Zimuzo | 71d931e | 2019-02-01 13:08:16 +0000 | [diff] [blame] | 479 | boolean execute(VersionedPackage versionedPackage); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 480 | |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 481 | // TODO(b/120598832): Ensure uniqueness? |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 482 | /** |
| 483 | * Identifier for the observer, should not change across device updates otherwise the |
| 484 | * watchdog may drop observing packages with the old name. |
| 485 | */ |
| 486 | String getName(); |
| 487 | } |
| 488 | |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 489 | /** |
| 490 | * Updates the observers monitoring {@code packageName} that explicit health check has passed. |
| 491 | * |
| 492 | * <p> This update is strictly for registered observers at the time of the call |
| 493 | * Observers that register after this signal will have no knowledge of prior signals and will |
| 494 | * effectively behave as if the explicit health check hasn't passed for {@code packageName}. |
| 495 | * |
| 496 | * <p> {@code packageName} can still be considered failed if reported by |
| 497 | * {@link #onPackageFailure} before the package expires. |
| 498 | * |
| 499 | * <p> Triggered by components outside the system server when they are fully functional after an |
| 500 | * update. |
| 501 | */ |
| 502 | private void onHealthCheckPassed(String packageName) { |
| 503 | Slog.i(TAG, "Health check passed for package: " + packageName); |
| 504 | boolean shouldUpdateFile = false; |
| 505 | synchronized (mLock) { |
| 506 | for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { |
| 507 | ObserverInternal observer = mAllObservers.valueAt(observerIdx); |
| 508 | MonitoredPackage monitoredPackage = observer.mPackages.get(packageName); |
| 509 | if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) { |
| 510 | monitoredPackage.mHasPassedHealthCheck = true; |
| 511 | shouldUpdateFile = true; |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | if (shouldUpdateFile) { |
| 516 | saveToFileAsync(); |
| 517 | } |
| 518 | } |
| 519 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 520 | /** Reschedules handler to prune expired packages from observers. */ |
| 521 | private void rescheduleCleanup() { |
| 522 | synchronized (mLock) { |
| 523 | long nextDurationToScheduleMs = getEarliestPackageExpiryLocked(); |
| 524 | if (nextDurationToScheduleMs == Long.MAX_VALUE) { |
| 525 | Slog.i(TAG, "No monitored packages, ending package cleanup"); |
| 526 | mDurationAtLastReschedule = 0; |
| 527 | mUptimeAtLastRescheduleMs = 0; |
| 528 | return; |
| 529 | } |
| 530 | long uptimeMs = SystemClock.uptimeMillis(); |
| 531 | // O if mPackageCleanup not running |
| 532 | long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0 |
| 533 | ? 0 : uptimeMs - mUptimeAtLastRescheduleMs; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 534 | // Less than O if mPackageCleanup unexpectedly didn't run yet even though |
| 535 | // and we are past the last duration scheduled to run |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 536 | long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 537 | if (mUptimeAtLastRescheduleMs == 0 |
| 538 | || remainingDurationMs <= 0 |
| 539 | || nextDurationToScheduleMs < remainingDurationMs) { |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 540 | // First schedule or an earlier reschedule |
| 541 | pruneObservers(elapsedDurationMs); |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 542 | mShortTaskHandler.removeCallbacks(mPackageCleanup); |
| 543 | mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 544 | mDurationAtLastReschedule = nextDurationToScheduleMs; |
| 545 | mUptimeAtLastRescheduleMs = uptimeMs; |
| 546 | } |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | /** |
| 551 | * Returns the earliest time a package should expire. |
| 552 | * @returns Long#MAX_VALUE if there are no observed packages. |
| 553 | */ |
| 554 | private long getEarliestPackageExpiryLocked() { |
| 555 | long shortestDurationMs = Long.MAX_VALUE; |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 556 | for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { |
| 557 | ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages; |
| 558 | for (int pIndex = 0; pIndex < packages.size(); pIndex++) { |
| 559 | long duration = packages.valueAt(pIndex).mDurationMs; |
| 560 | if (duration < shortestDurationMs) { |
| 561 | shortestDurationMs = duration; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 562 | } |
| 563 | } |
| 564 | } |
| 565 | Slog.v(TAG, "Earliest package time is " + shortestDurationMs); |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 566 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 567 | return shortestDurationMs; |
| 568 | } |
| 569 | |
| 570 | /** |
| 571 | * Removes {@code elapsedMs} milliseconds from all durations on monitored packages. |
| 572 | * Discards expired packages and discards observers without any packages. |
| 573 | */ |
| 574 | private void pruneObservers(long elapsedMs) { |
| 575 | if (elapsedMs == 0) { |
| 576 | return; |
| 577 | } |
| 578 | synchronized (mLock) { |
| 579 | Slog.d(TAG, "Removing expired packages after " + elapsedMs + "ms"); |
| 580 | Iterator<ObserverInternal> it = mAllObservers.values().iterator(); |
| 581 | while (it.hasNext()) { |
| 582 | ObserverInternal observer = it.next(); |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 583 | List<MonitoredPackage> failedPackages = |
| 584 | observer.updateMonitoringDurations(elapsedMs); |
| 585 | if (!failedPackages.isEmpty()) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 586 | onHealthCheckFailed(observer, failedPackages); |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 587 | } |
| 588 | if (observer.mPackages.isEmpty()) { |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 589 | Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired"); |
| 590 | it.remove(); |
| 591 | } |
| 592 | } |
| 593 | } |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 594 | updateHealthChecks(); |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 595 | saveToFileAsync(); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 596 | } |
| 597 | |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 598 | private void onHealthCheckFailed(ObserverInternal observer, |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 599 | List<MonitoredPackage> failedPackages) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 600 | mLongTaskHandler.post(() -> { |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 601 | synchronized (mLock) { |
| 602 | PackageHealthObserver registeredObserver = observer.mRegisteredObserver; |
| 603 | if (registeredObserver != null) { |
| 604 | PackageManager pm = mContext.getPackageManager(); |
| 605 | for (int i = 0; i < failedPackages.size(); i++) { |
| 606 | String packageName = failedPackages.get(i).mName; |
| 607 | long versionCode = 0; |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 608 | Slog.i(TAG, "Explicit health check failed for package " + packageName); |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 609 | try { |
| 610 | versionCode = pm.getPackageInfo( |
| 611 | packageName, 0 /* flags */).getLongVersionCode(); |
| 612 | } catch (PackageManager.NameNotFoundException e) { |
| 613 | Slog.w(TAG, "Explicit health check failed but could not find package " |
| 614 | + packageName); |
| 615 | // TODO(b/120598832): Skip. We only continue to pass tests for now since |
| 616 | // the tests don't install any packages |
| 617 | } |
| 618 | registeredObserver.execute(new VersionedPackage(packageName, versionCode)); |
| 619 | } |
| 620 | } |
| 621 | } |
| 622 | }); |
| 623 | } |
| 624 | |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 625 | /** |
| 626 | * Loads mAllObservers from file. |
| 627 | * |
| 628 | * <p>Note that this is <b>not</b> thread safe and should only called be called |
| 629 | * from the constructor. |
| 630 | */ |
| 631 | private void loadFromFile() { |
| 632 | InputStream infile = null; |
| 633 | mAllObservers.clear(); |
| 634 | try { |
| 635 | infile = mPolicyFile.openRead(); |
| 636 | final XmlPullParser parser = Xml.newPullParser(); |
| 637 | parser.setInput(infile, StandardCharsets.UTF_8.name()); |
| 638 | XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG); |
| 639 | int outerDepth = parser.getDepth(); |
| 640 | while (XmlUtils.nextElementWithin(parser, outerDepth)) { |
| 641 | ObserverInternal observer = ObserverInternal.read(parser); |
| 642 | if (observer != null) { |
| 643 | mAllObservers.put(observer.mName, observer); |
| 644 | } |
| 645 | } |
| 646 | } catch (FileNotFoundException e) { |
| 647 | // Nothing to monitor |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 648 | } catch (IOException | NumberFormatException | XmlPullParserException e) { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 649 | Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e); |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 650 | mPolicyFile.delete(); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 651 | } finally { |
| 652 | IoUtils.closeQuietly(infile); |
| 653 | } |
| 654 | } |
| 655 | |
| 656 | /** |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 657 | * Persists mAllObservers to file. Threshold information is ignored. |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 658 | */ |
| 659 | private boolean saveToFile() { |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 660 | synchronized (mLock) { |
| 661 | FileOutputStream stream; |
| 662 | try { |
| 663 | stream = mPolicyFile.startWrite(); |
| 664 | } catch (IOException e) { |
| 665 | Slog.w(TAG, "Cannot update monitored packages", e); |
| 666 | return false; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 667 | } |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 668 | |
| 669 | try { |
| 670 | XmlSerializer out = new FastXmlSerializer(); |
| 671 | out.setOutput(stream, StandardCharsets.UTF_8.name()); |
| 672 | out.startDocument(null, true); |
| 673 | out.startTag(null, TAG_PACKAGE_WATCHDOG); |
| 674 | out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION)); |
| 675 | for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { |
| 676 | mAllObservers.valueAt(oIndex).write(out); |
| 677 | } |
| 678 | out.endTag(null, TAG_PACKAGE_WATCHDOG); |
| 679 | out.endDocument(); |
| 680 | mPolicyFile.finishWrite(stream); |
| 681 | return true; |
| 682 | } catch (IOException e) { |
| 683 | Slog.w(TAG, "Failed to save monitored packages, restoring backup", e); |
| 684 | mPolicyFile.failWrite(stream); |
| 685 | return false; |
| 686 | } finally { |
| 687 | IoUtils.closeQuietly(stream); |
| 688 | } |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 689 | } |
| 690 | } |
| 691 | |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 692 | private void saveToFileAsync() { |
Zimuzo | caa435e | 2019-03-20 11:16:06 +0000 | [diff] [blame] | 693 | mLongTaskHandler.removeCallbacks(this::saveToFile); |
| 694 | mLongTaskHandler.post(this::saveToFile); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 695 | } |
| 696 | |
| 697 | /** |
| 698 | * Represents an observer monitoring a set of packages along with the failure thresholds for |
| 699 | * each package. |
| 700 | */ |
| 701 | static class ObserverInternal { |
| 702 | public final String mName; |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 703 | //TODO(b/120598832): Add getter for mPackages |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 704 | public final ArrayMap<String, MonitoredPackage> mPackages; |
Zimuzo | 3eee438 | 2019-01-08 20:42:39 +0000 | [diff] [blame] | 705 | @Nullable |
| 706 | public PackageHealthObserver mRegisteredObserver; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 707 | |
| 708 | ObserverInternal(String name, List<MonitoredPackage> packages) { |
| 709 | mName = name; |
| 710 | mPackages = new ArrayMap<>(); |
| 711 | updatePackages(packages); |
| 712 | } |
| 713 | |
| 714 | /** |
| 715 | * Writes important details to file. Doesn't persist any package failure thresholds. |
| 716 | * |
| 717 | * <p>Note that this method is <b>not</b> thread safe. It should only be called from |
| 718 | * #saveToFile which runs on a single threaded handler. |
| 719 | */ |
| 720 | public boolean write(XmlSerializer out) { |
| 721 | try { |
| 722 | out.startTag(null, TAG_OBSERVER); |
| 723 | out.attribute(null, ATTR_NAME, mName); |
| 724 | for (int i = 0; i < mPackages.size(); i++) { |
| 725 | MonitoredPackage p = mPackages.valueAt(i); |
| 726 | out.startTag(null, TAG_PACKAGE); |
| 727 | out.attribute(null, ATTR_NAME, p.mName); |
| 728 | out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs)); |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 729 | out.attribute(null, ATTR_PASSED_HEALTH_CHECK, |
| 730 | String.valueOf(p.mHasPassedHealthCheck)); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 731 | out.endTag(null, TAG_PACKAGE); |
| 732 | } |
| 733 | out.endTag(null, TAG_OBSERVER); |
| 734 | return true; |
| 735 | } catch (IOException e) { |
| 736 | Slog.w(TAG, "Cannot save observer", e); |
| 737 | return false; |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | public void updatePackages(List<MonitoredPackage> packages) { |
| 742 | synchronized (mName) { |
Zimuzo | cfaed76 | 2019-01-03 21:13:01 +0000 | [diff] [blame] | 743 | for (int pIndex = 0; pIndex < packages.size(); pIndex++) { |
| 744 | MonitoredPackage p = packages.get(pIndex); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 745 | mPackages.put(p.mName, p); |
| 746 | } |
| 747 | } |
| 748 | } |
| 749 | |
| 750 | /** |
| 751 | * Reduces the monitoring durations of all packages observed by this observer by |
| 752 | * {@code elapsedMs}. If any duration is less than 0, the package is removed from |
| 753 | * observation. |
| 754 | * |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 755 | * @returns a {@link List} of packages that were removed from the observer without explicit |
| 756 | * health check passing, or an empty list if no package expired for which an explicit health |
| 757 | * check was still pending |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 758 | */ |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 759 | public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) { |
| 760 | List<MonitoredPackage> removedPackages = new ArrayList<>(); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 761 | synchronized (mName) { |
| 762 | Iterator<MonitoredPackage> it = mPackages.values().iterator(); |
| 763 | while (it.hasNext()) { |
| 764 | MonitoredPackage p = it.next(); |
| 765 | long newDuration = p.mDurationMs - elapsedMs; |
| 766 | if (newDuration > 0) { |
| 767 | p.mDurationMs = newDuration; |
| 768 | } else { |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 769 | if (!p.mHasPassedHealthCheck) { |
| 770 | removedPackages.add(p); |
| 771 | } |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 772 | it.remove(); |
| 773 | } |
| 774 | } |
Zimuzo | ef65fb8 | 2019-02-28 10:44:29 +0000 | [diff] [blame] | 775 | return removedPackages; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 776 | } |
| 777 | } |
| 778 | |
| 779 | /** |
| 780 | * Increments failure counts of {@code packageName}. |
| 781 | * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise |
| 782 | */ |
| 783 | public boolean onPackageFailure(String packageName) { |
| 784 | synchronized (mName) { |
| 785 | MonitoredPackage p = mPackages.get(packageName); |
| 786 | if (p != null) { |
| 787 | return p.onFailure(); |
| 788 | } |
| 789 | return false; |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | /** |
| 794 | * Returns one ObserverInternal from the {@code parser} and advances its state. |
| 795 | * |
| 796 | * <p>Note that this method is <b>not</b> thread safe. It should only be called from |
| 797 | * #loadFromFile which in turn is only called on construction of the |
| 798 | * singleton PackageWatchdog. |
| 799 | **/ |
| 800 | public static ObserverInternal read(XmlPullParser parser) { |
| 801 | String observerName = null; |
| 802 | if (TAG_OBSERVER.equals(parser.getName())) { |
| 803 | observerName = parser.getAttributeValue(null, ATTR_NAME); |
| 804 | if (TextUtils.isEmpty(observerName)) { |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 805 | Slog.wtf(TAG, "Unable to read observer name"); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 806 | return null; |
| 807 | } |
| 808 | } |
| 809 | List<MonitoredPackage> packages = new ArrayList<>(); |
| 810 | int innerDepth = parser.getDepth(); |
| 811 | try { |
| 812 | while (XmlUtils.nextElementWithin(parser, innerDepth)) { |
| 813 | if (TAG_PACKAGE.equals(parser.getName())) { |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 814 | try { |
| 815 | String packageName = parser.getAttributeValue(null, ATTR_NAME); |
| 816 | long duration = Long.parseLong( |
| 817 | parser.getAttributeValue(null, ATTR_DURATION)); |
| 818 | boolean hasPassedHealthCheck = Boolean.parseBoolean( |
| 819 | parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK)); |
| 820 | if (!TextUtils.isEmpty(packageName)) { |
| 821 | packages.add(new MonitoredPackage(packageName, duration, |
| 822 | hasPassedHealthCheck)); |
| 823 | } |
| 824 | } catch (NumberFormatException e) { |
| 825 | Slog.wtf(TAG, "Skipping package for observer " + observerName, e); |
| 826 | continue; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 827 | } |
| 828 | } |
| 829 | } |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 830 | } catch (XmlPullParserException | IOException e) { |
| 831 | Slog.wtf(TAG, "Unable to read observer " + observerName, e); |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 832 | return null; |
| 833 | } |
| 834 | if (packages.isEmpty()) { |
| 835 | return null; |
| 836 | } |
| 837 | return new ObserverInternal(observerName, packages); |
| 838 | } |
| 839 | } |
| 840 | |
| 841 | /** Represents a package along with the time it should be monitored for. */ |
| 842 | static class MonitoredPackage { |
| 843 | public final String mName; |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 844 | // Whether an explicit health check has passed |
| 845 | public boolean mHasPassedHealthCheck; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 846 | // System uptime duration to monitor package |
| 847 | public long mDurationMs; |
| 848 | // System uptime of first package failure |
| 849 | private long mUptimeStartMs; |
| 850 | // Number of failures since mUptimeStartMs |
| 851 | private int mFailures; |
| 852 | |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 853 | MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) { |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 854 | mName = name; |
| 855 | mDurationMs = durationMs; |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 856 | mHasPassedHealthCheck = hasPassedHealthCheck; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 857 | } |
| 858 | |
| 859 | /** |
| 860 | * Increment package failures or resets failure count depending on the last package failure. |
| 861 | * |
| 862 | * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise |
| 863 | */ |
| 864 | public synchronized boolean onFailure() { |
| 865 | final long now = SystemClock.uptimeMillis(); |
| 866 | final long duration = now - mUptimeStartMs; |
| 867 | if (duration > TRIGGER_DURATION_MS) { |
Zimuzo | 9284e74 | 2019-02-22 12:09:28 +0000 | [diff] [blame] | 868 | // TODO(b/120598832): Reseting to 1 is not correct |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 869 | // because there may be more than 1 failure in the last trigger window from now |
| 870 | // This is the RescueParty impl, will leave for now |
| 871 | mFailures = 1; |
| 872 | mUptimeStartMs = now; |
| 873 | } else { |
| 874 | mFailures++; |
| 875 | } |
Zimuzo | e5009cd | 2019-01-23 18:11:58 +0000 | [diff] [blame] | 876 | boolean failed = mFailures >= TRIGGER_FAILURE_COUNT; |
| 877 | if (failed) { |
| 878 | mFailures = 0; |
| 879 | } |
| 880 | return failed; |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 881 | } |
| 882 | } |
Zimuzo | 6efba54 | 2018-11-29 12:47:58 +0000 | [diff] [blame] | 883 | } |