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