Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 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.timezone; |
| 18 | |
| 19 | import com.android.internal.annotations.VisibleForTesting; |
| 20 | |
| 21 | import android.app.timezone.RulesUpdaterContract; |
| 22 | import android.content.Context; |
| 23 | import android.content.pm.PackageManager; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 24 | import android.os.Environment; |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 25 | import android.os.FileUtils; |
| 26 | import android.os.SystemClock; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 27 | import android.provider.TimeZoneRulesDataContract; |
| 28 | import android.util.Slog; |
| 29 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 30 | import java.io.File; |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 31 | import java.io.IOException; |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 32 | import java.io.PrintWriter; |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 33 | import java.time.Clock; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 34 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 35 | /** |
| 36 | * Monitors the installed applications associated with time zone updates. If the app packages are |
| 37 | * updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted |
| 38 | * broadcast intent is used to trigger the time zone updater app. |
| 39 | * |
| 40 | * <p>The "update triggering" behavior of this component can be disabled via device configuration. |
| 41 | * |
| 42 | * <p>The package tracker listens for package updates of the time zone "updater app" and "data app". |
| 43 | * It also listens for "reliability" triggers. Reliability triggers are there to ensure that the |
| 44 | * package tracker handles failures reliably and are "idle maintenance" events or something similar. |
| 45 | * Reliability triggers can cause a time zone update check to take place if the current state is |
| 46 | * unclear. For example, it can be unclear after boot or after a failure. If there are repeated |
| 47 | * failures reliability updates are halted until the next boot. |
| 48 | * |
| 49 | * <p>This component keeps persistent track of the most recent app packages checked to avoid |
| 50 | * unnecessary expense from broadcasting intents (which will cause other app processes to spawn). |
| 51 | * The current status is also stored to detect whether the most recently-generated check is |
| 52 | * complete successfully. For example, if the device was interrupted while doing a check and never |
| 53 | * acknowledged a check then a check will be retried the next time a "reliability trigger" event |
| 54 | * happens. |
| 55 | */ |
| 56 | // Also made non-final so it can be mocked. |
| 57 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 58 | public class PackageTracker { |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 59 | private static final String TAG = "timezone.PackageTracker"; |
| 60 | |
| 61 | private final PackageManagerHelper mPackageManagerHelper; |
| 62 | private final IntentHelper mIntentHelper; |
| 63 | private final ConfigHelper mConfigHelper; |
| 64 | private final PackageStatusStorage mPackageStatusStorage; |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 65 | private final Clock mElapsedRealtimeClock; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 66 | |
| 67 | // False if tracking is disabled. |
| 68 | private boolean mTrackingEnabled; |
| 69 | |
| 70 | // These fields may be null if package tracking is disabled. |
| 71 | private String mUpdateAppPackageName; |
| 72 | private String mDataAppPackageName; |
| 73 | |
| 74 | // The time a triggered check is allowed to take before it is considered overdue. |
| 75 | private int mCheckTimeAllowedMillis; |
| 76 | // The number of failed checks in a row before reliability checks should stop happening. |
| 77 | private long mFailedCheckRetryCount; |
| 78 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 79 | /* |
| 80 | * The minimum delay between a successive reliability triggers / other operations. Should to be |
| 81 | * larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package |
| 82 | * update checks. |
| 83 | */ |
| 84 | private int mDelayBeforeReliabilityCheckMillis; |
| 85 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 86 | // Reliability check state: If a check was triggered but not acknowledged within |
| 87 | // mCheckTimeAllowedMillis then another one can be triggered. |
| 88 | private Long mLastTriggerTimestamp = null; |
| 89 | |
| 90 | // Reliability check state: Whether any checks have been triggered at all. |
| 91 | private boolean mCheckTriggered; |
| 92 | |
| 93 | // Reliability check state: A count of how many failures have occurred consecutively. |
| 94 | private int mCheckFailureCount; |
| 95 | |
| 96 | /** Creates the {@link PackageTracker} for normal use. */ |
| 97 | static PackageTracker create(Context context) { |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 98 | Clock elapsedRealtimeClock = SystemClock.elapsedRealtimeClock(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 99 | PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context); |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 100 | File storageDir = FileUtils.createDir(Environment.getDataSystemDirectory(), "timezone"); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 101 | return new PackageTracker( |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 102 | elapsedRealtimeClock /* elapsedRealtimeClock */, |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 103 | helperImpl /* configHelper */, |
| 104 | helperImpl /* packageManagerHelper */, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 105 | new PackageStatusStorage(storageDir), |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 106 | new IntentHelperImpl(context)); |
| 107 | } |
| 108 | |
| 109 | // A constructor that can be used by tests to supply mocked / faked dependencies. |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 110 | PackageTracker(Clock elapsedRealtimeClock, ConfigHelper configHelper, |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 111 | PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage, |
| 112 | IntentHelper intentHelper) { |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 113 | mElapsedRealtimeClock = elapsedRealtimeClock; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 114 | mConfigHelper = configHelper; |
| 115 | mPackageManagerHelper = packageManagerHelper; |
| 116 | mPackageStatusStorage = packageStatusStorage; |
| 117 | mIntentHelper = intentHelper; |
| 118 | } |
| 119 | |
| 120 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 121 | protected synchronized boolean start() { |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 122 | mTrackingEnabled = mConfigHelper.isTrackingEnabled(); |
| 123 | if (!mTrackingEnabled) { |
| 124 | Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled."); |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 125 | return false; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 126 | } |
| 127 | |
| 128 | mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName(); |
| 129 | mDataAppPackageName = mConfigHelper.getDataAppPackageName(); |
| 130 | mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis(); |
| 131 | mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount(); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 132 | mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 133 | |
| 134 | // Validate the device configuration including the application packages. |
| 135 | // The manifest entries in the apps themselves are not validated until use as they can |
| 136 | // change and we don't want to prevent the system server starting due to a bad application. |
| 137 | throwIfDeviceSettingsOrAppsAreBad(); |
| 138 | |
| 139 | // Explicitly start in a reliability state where reliability triggering will do something. |
| 140 | mCheckTriggered = false; |
| 141 | mCheckFailureCount = 0; |
| 142 | |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 143 | // Initialize the storage, as needed. |
| 144 | try { |
| 145 | mPackageStatusStorage.initialize(); |
| 146 | } catch (IOException e) { |
| 147 | Slog.w(TAG, "PackageTracker storage could not be initialized.", e); |
| 148 | return false; |
| 149 | } |
| 150 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 151 | // Initialize the intent helper. |
| 152 | mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this); |
| 153 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 154 | // Schedule a reliability trigger so we will have at least one after boot. This will allow |
| 155 | // us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok |
| 156 | // to delay for a while before doing this even if idle. |
| 157 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 158 | |
| 159 | Slog.i(TAG, "Time zone updater / data package tracking enabled"); |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 160 | return true; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Performs checks that confirm the system image has correctly configured package |
| 165 | * tracking configuration. Only called if package tracking is enabled. Throws an exception if |
| 166 | * the device is configured badly which will prevent the device booting. |
| 167 | */ |
| 168 | private void throwIfDeviceSettingsOrAppsAreBad() { |
| 169 | // None of the checks below can be based on application manifest settings, otherwise a bad |
| 170 | // update could leave the device in an unbootable state. See validateDataAppManifest() and |
| 171 | // validateUpdaterAppManifest() for softer errors. |
| 172 | |
| 173 | throwRuntimeExceptionIfNullOrEmpty( |
| 174 | mUpdateAppPackageName, "Update app package name missing."); |
| 175 | throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing."); |
| 176 | if (mFailedCheckRetryCount < 1) { |
| 177 | throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null); |
| 178 | } |
| 179 | if (mCheckTimeAllowedMillis < 1000) { |
| 180 | throw logAndThrowRuntimeException( |
| 181 | "mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null); |
| 182 | } |
| 183 | |
| 184 | // Validate the updater application package. |
Neil Fuller | 3d7a414 | 2017-07-12 17:18:19 +0100 | [diff] [blame] | 185 | try { |
| 186 | if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) { |
| 187 | throw logAndThrowRuntimeException( |
| 188 | "Update app " + mUpdateAppPackageName + " must be a priv-app.", null); |
| 189 | } |
| 190 | } catch (PackageManager.NameNotFoundException e) { |
| 191 | throw logAndThrowRuntimeException("Could not determine update app package details for " |
| 192 | + mUpdateAppPackageName, e); |
| 193 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 194 | Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid."); |
| 195 | |
| 196 | // Validate the data application package. |
Neil Fuller | 3d7a414 | 2017-07-12 17:18:19 +0100 | [diff] [blame] | 197 | try { |
| 198 | if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) { |
| 199 | throw logAndThrowRuntimeException( |
| 200 | "Data app " + mDataAppPackageName + " must be a priv-app.", null); |
| 201 | } |
| 202 | } catch (PackageManager.NameNotFoundException e) { |
| 203 | throw logAndThrowRuntimeException("Could not determine data app package details for " |
| 204 | + mDataAppPackageName, e); |
| 205 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 206 | Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid."); |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Inspects the current in-memory state, installed packages and storage state to determine if an |
| 211 | * update check is needed and then trigger if it is. |
| 212 | * |
| 213 | * @param packageChanged true if this method was called because a known packaged definitely |
| 214 | * changed, false if the cause is a reliability trigger |
| 215 | */ |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 216 | public synchronized void triggerUpdateIfNeeded(boolean packageChanged) { |
| 217 | if (!mTrackingEnabled) { |
| 218 | throw new IllegalStateException("Unexpected call. Tracking is disabled."); |
| 219 | } |
| 220 | |
| 221 | // Validate the applications' current manifest entries: make sure they are configured as |
| 222 | // they should be. These are not fatal and just means that no update is triggered: we don't |
| 223 | // want to take down the system server if an OEM or Google have pushed a bad update to |
| 224 | // an application. |
| 225 | boolean updaterAppManifestValid = validateUpdaterAppManifest(); |
| 226 | boolean dataAppManifestValid = validateDataAppManifest(); |
| 227 | if (!updaterAppManifestValid || !dataAppManifestValid) { |
| 228 | Slog.e(TAG, "No update triggered due to invalid application manifest entries." |
| 229 | + " updaterApp=" + updaterAppManifestValid |
| 230 | + ", dataApp=" + dataAppManifestValid); |
| 231 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 232 | // There's no point in doing any reliability triggers if the current packages are bad. |
| 233 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 234 | return; |
| 235 | } |
| 236 | |
| 237 | if (!packageChanged) { |
| 238 | // This call was made because the device is doing a "reliability" check. |
| 239 | // 4 possible cases: |
| 240 | // 1) No check has previously triggered since restart. We want to trigger in this case. |
| 241 | // 2) A check has previously triggered and it is in progress. We want to trigger if |
| 242 | // the response is overdue. |
| 243 | // 3) A check has previously triggered and it failed. We want to trigger, but only if |
| 244 | // we're not in a persistent failure state. |
| 245 | // 4) A check has previously triggered and it succeeded. |
| 246 | // We don't want to trigger, and want to stop future triggers. |
| 247 | |
| 248 | if (!mCheckTriggered) { |
| 249 | // Case 1. |
| 250 | Slog.d(TAG, "triggerUpdateIfNeeded: First reliability trigger."); |
| 251 | } else if (isCheckInProgress()) { |
| 252 | // Case 2. |
| 253 | if (!isCheckResponseOverdue()) { |
| 254 | // A check is in progress but hasn't been given time to succeed. |
| 255 | Slog.d(TAG, |
| 256 | "triggerUpdateIfNeeded: checkComplete call is not yet overdue." |
| 257 | + " Not triggering."); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 258 | // Don't do any work now but we do schedule a future reliability trigger. |
| 259 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 260 | return; |
| 261 | } |
| 262 | } else if (mCheckFailureCount > mFailedCheckRetryCount) { |
| 263 | // Case 3. If the system is in some kind of persistent failure state we don't want |
| 264 | // to keep checking, so just stop. |
| 265 | Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures" |
| 266 | + " exceeded. Stopping reliability triggers until next reboot or package" |
| 267 | + " update."); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 268 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 269 | return; |
| 270 | } else if (mCheckFailureCount == 0) { |
| 271 | // Case 4. |
| 272 | Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was" |
| 273 | + " successful."); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 274 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 275 | return; |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | // Read the currently installed data / updater package versions. |
| 280 | PackageVersions currentInstalledVersions = lookupInstalledPackageVersions(); |
| 281 | if (currentInstalledVersions == null) { |
| 282 | // This should not happen if the device is configured in a valid way. |
| 283 | Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null"); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 284 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 285 | return; |
| 286 | } |
| 287 | |
| 288 | // Establish the current state using package manager and stored state. Determine if we have |
| 289 | // already successfully checked the installed versions. |
| 290 | PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus(); |
| 291 | if (packageStatus == null) { |
| 292 | // This can imply corrupt, uninitialized storage state (e.g. first check ever on a |
| 293 | // device) or after some kind of reset. |
| 294 | Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed."); |
| 295 | } else if (!packageStatus.mVersions.equals(currentInstalledVersions)) { |
| 296 | // The stored package version information differs from the installed version. |
| 297 | // Trigger the check in all cases. |
| 298 | Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions=" |
| 299 | + packageStatus.mVersions + ", do not match current package versions=" |
| 300 | + currentInstalledVersions + ". Triggering check."); |
| 301 | } else { |
| 302 | Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently" |
| 303 | + " installed versions, currentInstalledVersions=" + currentInstalledVersions |
| 304 | + ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus); |
| 305 | if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) { |
| 306 | // The last check succeeded and nothing has changed. Do nothing and disable |
| 307 | // reliability checks. |
| 308 | Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger."); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 309 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 310 | return; |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | // Generate a token to send to the updater app. |
| 315 | CheckToken checkToken = |
| 316 | mPackageStatusStorage.generateCheckToken(currentInstalledVersions); |
| 317 | if (checkToken == null) { |
| 318 | Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token." |
| 319 | + " Not sending check request."); |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 320 | // Trigger again later: perhaps we'll have better luck. |
| 321 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 322 | return; |
| 323 | } |
| 324 | |
| 325 | // Trigger the update check. |
| 326 | mIntentHelper.sendTriggerUpdateCheck(checkToken); |
| 327 | mCheckTriggered = true; |
| 328 | |
| 329 | // Update the reliability check state in case the update fails. |
| 330 | setCheckInProgress(); |
| 331 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 332 | // Schedule a reliability trigger in case the update check doesn't succeed and there is no |
| 333 | // response at all. It will be cancelled if the check is successful in recordCheckResult. |
| 334 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 335 | } |
| 336 | |
| 337 | /** |
| 338 | * Used to record the result of a check. Can be called even if active package tracking is |
| 339 | * disabled. |
| 340 | */ |
| 341 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| 342 | protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) { |
| 343 | Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success); |
| 344 | |
| 345 | // If package tracking is disabled it means no record-keeping is required. However, we do |
| 346 | // want to clear out any stored state to make it clear that the current state is unknown and |
| 347 | // should tracking become enabled again (perhaps through an OTA) we'd need to perform an |
| 348 | // update check. |
| 349 | if (!mTrackingEnabled) { |
| 350 | // This means an updater has spontaneously modified time zone data without having been |
| 351 | // triggered. This can happen if the OEM is handling their own updates, but we don't |
| 352 | // need to do any tracking in this case. |
| 353 | |
| 354 | if (checkToken == null) { |
| 355 | // This is the expected case if tracking is disabled but an OEM is handling time |
| 356 | // zone installs using their own mechanism. |
| 357 | Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been" |
| 358 | + " provided. Resetting tracking state."); |
| 359 | } else { |
| 360 | // This is unexpected. If tracking is disabled then no check token should have been |
| 361 | // generated by the package tracker. An updater should never create its own token. |
| 362 | // This could be a bug in the updater. |
| 363 | Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken |
| 364 | + " has been unexpectedly provided. Resetting tracking state."); |
| 365 | } |
| 366 | mPackageStatusStorage.resetCheckState(); |
| 367 | return; |
| 368 | } |
| 369 | |
| 370 | if (checkToken == null) { |
| 371 | /* |
| 372 | * If the checkToken is null it suggests an install / uninstall / acknowledgement has |
| 373 | * occurred without a prior trigger (or the client didn't return the token it was given |
| 374 | * for some reason, perhaps a bug). |
| 375 | * |
| 376 | * This shouldn't happen under normal circumstances: |
| 377 | * |
| 378 | * If package tracking is enabled, we assume it is the package tracker responsible for |
| 379 | * triggering updates and a token should have been produced and returned. |
| 380 | * |
| 381 | * If the OEM is handling time zone updates case package tracking should be disabled. |
| 382 | * |
| 383 | * This could happen in tests. The device should recover back to a known state by |
| 384 | * itself rather than be left in an invalid state. |
| 385 | * |
| 386 | * We treat this as putting the device into an unknown state and make sure that |
| 387 | * reliability triggering is enabled so we should recover. |
| 388 | */ |
| 389 | Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting" |
| 390 | + " storage state."); |
| 391 | mPackageStatusStorage.resetCheckState(); |
| 392 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 393 | // Schedule a reliability trigger and reset the failure count so we know that the |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 394 | // next reliability trigger will do something. |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 395 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 396 | mCheckFailureCount = 0; |
| 397 | } else { |
| 398 | // This is the expected case when tracking is enabled: a check was triggered and it has |
| 399 | // completed. |
| 400 | boolean recordedCheckCompleteSuccessfully = |
| 401 | mPackageStatusStorage.markChecked(checkToken, success); |
| 402 | if (recordedCheckCompleteSuccessfully) { |
| 403 | // If we have recorded the result (whatever it was) we know there is no check in |
| 404 | // progress. |
| 405 | setCheckComplete(); |
| 406 | |
| 407 | if (success) { |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 408 | // Since the check was successful, no reliability trigger is required until |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 409 | // there is a package change. |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 410 | mIntentHelper.unscheduleReliabilityTrigger(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 411 | mCheckFailureCount = 0; |
| 412 | } else { |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 413 | // Enable schedule a reliability trigger to check again in future. |
| 414 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 415 | mCheckFailureCount++; |
| 416 | } |
| 417 | } else { |
| 418 | // The failure to record the check means an optimistic lock failure and suggests |
| 419 | // that another check was triggered after the token was generated. |
| 420 | Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken |
| 421 | + " with success=" + success + ". Optimistic lock failure"); |
| 422 | |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 423 | // Schedule a reliability trigger to potentially try again in future. |
| 424 | mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 425 | mCheckFailureCount++; |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | /** Access to consecutive failure counts for use in tests. */ |
| 431 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| 432 | protected int getCheckFailureCountForTests() { |
| 433 | return mCheckFailureCount; |
| 434 | } |
| 435 | |
| 436 | private void setCheckInProgress() { |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 437 | mLastTriggerTimestamp = mElapsedRealtimeClock.millis(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 438 | } |
| 439 | |
| 440 | private void setCheckComplete() { |
| 441 | mLastTriggerTimestamp = null; |
| 442 | } |
| 443 | |
| 444 | private boolean isCheckInProgress() { |
| 445 | return mLastTriggerTimestamp != null; |
| 446 | } |
| 447 | |
| 448 | private boolean isCheckResponseOverdue() { |
| 449 | if (mLastTriggerTimestamp == null) { |
| 450 | return false; |
| 451 | } |
| 452 | // Risk of overflow, but highly unlikely given the implementation and not problematic. |
Neil Fuller | a7d21f8 | 2017-12-05 15:09:35 +0000 | [diff] [blame] | 453 | return mElapsedRealtimeClock.millis() > mLastTriggerTimestamp + mCheckTimeAllowedMillis; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 454 | } |
| 455 | |
| 456 | private PackageVersions lookupInstalledPackageVersions() { |
Dianne Hackborn | 3accca0 | 2013-09-20 09:32:11 -0700 | [diff] [blame] | 457 | long updatePackageVersion; |
| 458 | long dataPackageVersion; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 459 | try { |
| 460 | updatePackageVersion = |
| 461 | mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName); |
| 462 | dataPackageVersion = |
| 463 | mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName); |
| 464 | } catch (PackageManager.NameNotFoundException e) { |
| 465 | Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package" |
| 466 | + " versions", e); |
| 467 | return null; |
| 468 | } |
| 469 | return new PackageVersions(updatePackageVersion, dataPackageVersion); |
| 470 | } |
| 471 | |
| 472 | private boolean validateDataAppManifest() { |
| 473 | // We only want to talk to a provider that exposed by the known data app package |
| 474 | // so we look up the providers exposed by that app and check the well-known authority is |
| 475 | // there. This prevents the case where *even if* the data app doesn't expose the provider |
| 476 | // required, another app cannot expose one to replace it. |
| 477 | if (!mPackageManagerHelper.contentProviderRegistered( |
| 478 | TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) { |
| 479 | // Error! Found the package but it didn't expose the correct provider. |
| 480 | Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName |
| 481 | + " does not expose the required provider with authority=" |
| 482 | + TimeZoneRulesDataContract.AUTHORITY); |
| 483 | return false; |
| 484 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 485 | return true; |
| 486 | } |
| 487 | |
| 488 | private boolean validateUpdaterAppManifest() { |
| 489 | try { |
| 490 | // The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission. |
| 491 | // The updater app is expected to have a receiver for the intent we are going to trigger |
| 492 | // and require the TRIGGER_TIME_ZONE_RULES_CHECK. |
| 493 | if (!mPackageManagerHelper.usesPermission( |
| 494 | mUpdateAppPackageName, |
| 495 | RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) { |
| 496 | Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName |
| 497 | + " does not use permission=" |
| 498 | + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION); |
| 499 | return false; |
| 500 | } |
| 501 | if (!mPackageManagerHelper.receiverRegistered( |
| 502 | RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName), |
| 503 | RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) { |
| 504 | return false; |
| 505 | } |
| 506 | |
| 507 | return true; |
| 508 | } catch (PackageManager.NameNotFoundException e) { |
| 509 | Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName |
| 510 | + " does not expose the required broadcast receiver.", e); |
| 511 | return false; |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) { |
| 516 | if (value == null || value.trim().isEmpty()) { |
| 517 | throw logAndThrowRuntimeException(message, null); |
| 518 | } |
| 519 | } |
| 520 | |
| 521 | private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) { |
| 522 | Slog.wtf(TAG, message, cause); |
| 523 | throw new RuntimeException(message, cause); |
| 524 | } |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 525 | |
| 526 | public void dump(PrintWriter fout) { |
| 527 | fout.println("PackageTrackerState: " + toString()); |
| 528 | mPackageStatusStorage.dump(fout); |
| 529 | } |
| 530 | |
| 531 | @Override |
| 532 | public String toString() { |
| 533 | return "PackageTracker{" + |
| 534 | "mTrackingEnabled=" + mTrackingEnabled + |
| 535 | ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' + |
| 536 | ", mDataAppPackageName='" + mDataAppPackageName + '\'' + |
| 537 | ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis + |
Neil Fuller | cd1a109 | 2017-09-13 21:59:59 +0100 | [diff] [blame] | 538 | ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis + |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 539 | ", mFailedCheckRetryCount=" + mFailedCheckRetryCount + |
| 540 | ", mLastTriggerTimestamp=" + mLastTriggerTimestamp + |
| 541 | ", mCheckTriggered=" + mCheckTriggered + |
| 542 | ", mCheckFailureCount=" + mCheckFailureCount + |
| 543 | '}'; |
| 544 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 545 | } |