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 | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 25 | import android.provider.TimeZoneRulesDataContract; |
| 26 | import android.util.Slog; |
| 27 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 28 | import java.io.File; |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 29 | import java.io.PrintWriter; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 30 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 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 implements IntentHelper.Listener { |
| 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 | // Reliability check state: If a check was triggered but not acknowledged within |
| 76 | // mCheckTimeAllowedMillis then another one can be triggered. |
| 77 | private Long mLastTriggerTimestamp = null; |
| 78 | |
| 79 | // Reliability check state: Whether any checks have been triggered at all. |
| 80 | private boolean mCheckTriggered; |
| 81 | |
| 82 | // Reliability check state: A count of how many failures have occurred consecutively. |
| 83 | private int mCheckFailureCount; |
| 84 | |
| 85 | /** Creates the {@link PackageTracker} for normal use. */ |
| 86 | static PackageTracker create(Context context) { |
| 87 | PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 88 | // TODO(nfuller): Switch to FileUtils.createDir() when available. http://b/31008728 |
| 89 | File storageDir = new File(Environment.getDataSystemDirectory(), "timezone"); |
| 90 | if (!storageDir.exists()) { |
| 91 | storageDir.mkdir(); |
| 92 | } |
| 93 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 94 | return new PackageTracker( |
| 95 | helperImpl /* clock */, |
| 96 | helperImpl /* configHelper */, |
| 97 | helperImpl /* packageManagerHelper */, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 98 | new PackageStatusStorage(storageDir), |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 99 | new IntentHelperImpl(context)); |
| 100 | } |
| 101 | |
| 102 | // A constructor that can be used by tests to supply mocked / faked dependencies. |
| 103 | PackageTracker(ClockHelper clockHelper, ConfigHelper configHelper, |
| 104 | PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage, |
| 105 | IntentHelper intentHelper) { |
| 106 | mClockHelper = clockHelper; |
| 107 | mConfigHelper = configHelper; |
| 108 | mPackageManagerHelper = packageManagerHelper; |
| 109 | mPackageStatusStorage = packageStatusStorage; |
| 110 | mIntentHelper = intentHelper; |
| 111 | } |
| 112 | |
| 113 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| 114 | protected synchronized void start() { |
| 115 | mTrackingEnabled = mConfigHelper.isTrackingEnabled(); |
| 116 | if (!mTrackingEnabled) { |
| 117 | Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled."); |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName(); |
| 122 | mDataAppPackageName = mConfigHelper.getDataAppPackageName(); |
| 123 | mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis(); |
| 124 | mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount(); |
| 125 | |
| 126 | // Validate the device configuration including the application packages. |
| 127 | // The manifest entries in the apps themselves are not validated until use as they can |
| 128 | // change and we don't want to prevent the system server starting due to a bad application. |
| 129 | throwIfDeviceSettingsOrAppsAreBad(); |
| 130 | |
| 131 | // Explicitly start in a reliability state where reliability triggering will do something. |
| 132 | mCheckTriggered = false; |
| 133 | mCheckFailureCount = 0; |
| 134 | |
| 135 | // Initialize the intent helper. |
| 136 | mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this); |
| 137 | |
| 138 | // Enable the reliability triggering so we will have at least one reliability trigger if |
| 139 | // a package isn't updated. |
| 140 | mIntentHelper.enableReliabilityTriggering(); |
| 141 | |
| 142 | Slog.i(TAG, "Time zone updater / data package tracking enabled"); |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Performs checks that confirm the system image has correctly configured package |
| 147 | * tracking configuration. Only called if package tracking is enabled. Throws an exception if |
| 148 | * the device is configured badly which will prevent the device booting. |
| 149 | */ |
| 150 | private void throwIfDeviceSettingsOrAppsAreBad() { |
| 151 | // None of the checks below can be based on application manifest settings, otherwise a bad |
| 152 | // update could leave the device in an unbootable state. See validateDataAppManifest() and |
| 153 | // validateUpdaterAppManifest() for softer errors. |
| 154 | |
| 155 | throwRuntimeExceptionIfNullOrEmpty( |
| 156 | mUpdateAppPackageName, "Update app package name missing."); |
| 157 | throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing."); |
| 158 | if (mFailedCheckRetryCount < 1) { |
| 159 | throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null); |
| 160 | } |
| 161 | if (mCheckTimeAllowedMillis < 1000) { |
| 162 | throw logAndThrowRuntimeException( |
| 163 | "mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null); |
| 164 | } |
| 165 | |
| 166 | // Validate the updater application package. |
| 167 | // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app |
| 168 | // after it is replaced by one in data so this check fails. http://b/35995024 |
| 169 | // try { |
| 170 | // if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) { |
| 171 | // throw failWithException( |
| 172 | // "Update app " + mUpdateAppPackageName + " must be a priv-app.", null); |
| 173 | // } |
| 174 | // } catch (PackageManager.NameNotFoundException e) { |
| 175 | // throw failWithException("Could not determine update app package details for " |
| 176 | // + mUpdateAppPackageName, e); |
| 177 | // } |
| 178 | // TODO(nfuller) Consider permission checks. While an updated system app retains permissions |
| 179 | // obtained by the system version it's not clear how to check them. |
| 180 | Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid."); |
| 181 | |
| 182 | // Validate the data application package. |
| 183 | // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app |
| 184 | // after it is replaced by one in data. http://b/35995024 |
| 185 | // try { |
| 186 | // if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) { |
| 187 | // throw failWithException( |
| 188 | // "Data app " + mDataAppPackageName + " must be a priv-app.", null); |
| 189 | // } |
| 190 | // } catch (PackageManager.NameNotFoundException e) { |
| 191 | // throw failWithException("Could not determine data app package details for " |
| 192 | // + mDataAppPackageName, e); |
| 193 | // } |
| 194 | // TODO(nfuller) Consider permission checks. While an updated system app retains permissions |
| 195 | // obtained by the system version it's not clear how to check them. |
| 196 | Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid."); |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Inspects the current in-memory state, installed packages and storage state to determine if an |
| 201 | * update check is needed and then trigger if it is. |
| 202 | * |
| 203 | * @param packageChanged true if this method was called because a known packaged definitely |
| 204 | * changed, false if the cause is a reliability trigger |
| 205 | */ |
| 206 | @Override |
| 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 reliability checks if the current packages are bad. |
| 224 | mIntentHelper.disableReliabilityTriggering(); |
| 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 | // Not doing any work, but also not disabling future reliability triggers. |
| 250 | return; |
| 251 | } |
| 252 | } else if (mCheckFailureCount > mFailedCheckRetryCount) { |
| 253 | // Case 3. If the system is in some kind of persistent failure state we don't want |
| 254 | // to keep checking, so just stop. |
| 255 | Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures" |
| 256 | + " exceeded. Stopping reliability triggers until next reboot or package" |
| 257 | + " update."); |
| 258 | mIntentHelper.disableReliabilityTriggering(); |
| 259 | return; |
| 260 | } else if (mCheckFailureCount == 0) { |
| 261 | // Case 4. |
| 262 | Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was" |
| 263 | + " successful."); |
| 264 | mIntentHelper.disableReliabilityTriggering(); |
| 265 | return; |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // Read the currently installed data / updater package versions. |
| 270 | PackageVersions currentInstalledVersions = lookupInstalledPackageVersions(); |
| 271 | if (currentInstalledVersions == null) { |
| 272 | // This should not happen if the device is configured in a valid way. |
| 273 | Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null"); |
| 274 | mIntentHelper.disableReliabilityTriggering(); |
| 275 | return; |
| 276 | } |
| 277 | |
| 278 | // Establish the current state using package manager and stored state. Determine if we have |
| 279 | // already successfully checked the installed versions. |
| 280 | PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus(); |
| 281 | if (packageStatus == null) { |
| 282 | // This can imply corrupt, uninitialized storage state (e.g. first check ever on a |
| 283 | // device) or after some kind of reset. |
| 284 | Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed."); |
| 285 | } else if (!packageStatus.mVersions.equals(currentInstalledVersions)) { |
| 286 | // The stored package version information differs from the installed version. |
| 287 | // Trigger the check in all cases. |
| 288 | Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions=" |
| 289 | + packageStatus.mVersions + ", do not match current package versions=" |
| 290 | + currentInstalledVersions + ". Triggering check."); |
| 291 | } else { |
| 292 | Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently" |
| 293 | + " installed versions, currentInstalledVersions=" + currentInstalledVersions |
| 294 | + ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus); |
| 295 | if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) { |
| 296 | // The last check succeeded and nothing has changed. Do nothing and disable |
| 297 | // reliability checks. |
| 298 | Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger."); |
| 299 | mIntentHelper.disableReliabilityTriggering(); |
| 300 | return; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | // Generate a token to send to the updater app. |
| 305 | CheckToken checkToken = |
| 306 | mPackageStatusStorage.generateCheckToken(currentInstalledVersions); |
| 307 | if (checkToken == null) { |
| 308 | Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token." |
| 309 | + " Not sending check request."); |
| 310 | return; |
| 311 | } |
| 312 | |
| 313 | // Trigger the update check. |
| 314 | mIntentHelper.sendTriggerUpdateCheck(checkToken); |
| 315 | mCheckTriggered = true; |
| 316 | |
| 317 | // Update the reliability check state in case the update fails. |
| 318 | setCheckInProgress(); |
| 319 | |
| 320 | // Enable reliability triggering in case the check doesn't succeed and there is no |
| 321 | // response at all. Enabling reliability triggering is idempotent. |
| 322 | mIntentHelper.enableReliabilityTriggering(); |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * Used to record the result of a check. Can be called even if active package tracking is |
| 327 | * disabled. |
| 328 | */ |
| 329 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| 330 | protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) { |
| 331 | Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success); |
| 332 | |
| 333 | // If package tracking is disabled it means no record-keeping is required. However, we do |
| 334 | // want to clear out any stored state to make it clear that the current state is unknown and |
| 335 | // should tracking become enabled again (perhaps through an OTA) we'd need to perform an |
| 336 | // update check. |
| 337 | if (!mTrackingEnabled) { |
| 338 | // This means an updater has spontaneously modified time zone data without having been |
| 339 | // triggered. This can happen if the OEM is handling their own updates, but we don't |
| 340 | // need to do any tracking in this case. |
| 341 | |
| 342 | if (checkToken == null) { |
| 343 | // This is the expected case if tracking is disabled but an OEM is handling time |
| 344 | // zone installs using their own mechanism. |
| 345 | Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been" |
| 346 | + " provided. Resetting tracking state."); |
| 347 | } else { |
| 348 | // This is unexpected. If tracking is disabled then no check token should have been |
| 349 | // generated by the package tracker. An updater should never create its own token. |
| 350 | // This could be a bug in the updater. |
| 351 | Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken |
| 352 | + " has been unexpectedly provided. Resetting tracking state."); |
| 353 | } |
| 354 | mPackageStatusStorage.resetCheckState(); |
| 355 | return; |
| 356 | } |
| 357 | |
| 358 | if (checkToken == null) { |
| 359 | /* |
| 360 | * If the checkToken is null it suggests an install / uninstall / acknowledgement has |
| 361 | * occurred without a prior trigger (or the client didn't return the token it was given |
| 362 | * for some reason, perhaps a bug). |
| 363 | * |
| 364 | * This shouldn't happen under normal circumstances: |
| 365 | * |
| 366 | * If package tracking is enabled, we assume it is the package tracker responsible for |
| 367 | * triggering updates and a token should have been produced and returned. |
| 368 | * |
| 369 | * If the OEM is handling time zone updates case package tracking should be disabled. |
| 370 | * |
| 371 | * This could happen in tests. The device should recover back to a known state by |
| 372 | * itself rather than be left in an invalid state. |
| 373 | * |
| 374 | * We treat this as putting the device into an unknown state and make sure that |
| 375 | * reliability triggering is enabled so we should recover. |
| 376 | */ |
| 377 | Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting" |
| 378 | + " storage state."); |
| 379 | mPackageStatusStorage.resetCheckState(); |
| 380 | |
| 381 | // Enable reliability triggering and reset the failure count so we know that the |
| 382 | // next reliability trigger will do something. |
| 383 | mIntentHelper.enableReliabilityTriggering(); |
| 384 | mCheckFailureCount = 0; |
| 385 | } else { |
| 386 | // This is the expected case when tracking is enabled: a check was triggered and it has |
| 387 | // completed. |
| 388 | boolean recordedCheckCompleteSuccessfully = |
| 389 | mPackageStatusStorage.markChecked(checkToken, success); |
| 390 | if (recordedCheckCompleteSuccessfully) { |
| 391 | // If we have recorded the result (whatever it was) we know there is no check in |
| 392 | // progress. |
| 393 | setCheckComplete(); |
| 394 | |
| 395 | if (success) { |
| 396 | // Since the check was successful, no more reliability checks are required until |
| 397 | // there is a package change. |
| 398 | mIntentHelper.disableReliabilityTriggering(); |
| 399 | mCheckFailureCount = 0; |
| 400 | } else { |
| 401 | // Enable reliability triggering to potentially check again in future. |
| 402 | mIntentHelper.enableReliabilityTriggering(); |
| 403 | mCheckFailureCount++; |
| 404 | } |
| 405 | } else { |
| 406 | // The failure to record the check means an optimistic lock failure and suggests |
| 407 | // that another check was triggered after the token was generated. |
| 408 | Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken |
| 409 | + " with success=" + success + ". Optimistic lock failure"); |
| 410 | |
| 411 | // Enable reliability triggering to potentially try again in future. |
| 412 | mIntentHelper.enableReliabilityTriggering(); |
| 413 | mCheckFailureCount++; |
| 414 | } |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /** Access to consecutive failure counts for use in tests. */ |
| 419 | @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| 420 | protected int getCheckFailureCountForTests() { |
| 421 | return mCheckFailureCount; |
| 422 | } |
| 423 | |
| 424 | private void setCheckInProgress() { |
| 425 | mLastTriggerTimestamp = mClockHelper.currentTimestamp(); |
| 426 | } |
| 427 | |
| 428 | private void setCheckComplete() { |
| 429 | mLastTriggerTimestamp = null; |
| 430 | } |
| 431 | |
| 432 | private boolean isCheckInProgress() { |
| 433 | return mLastTriggerTimestamp != null; |
| 434 | } |
| 435 | |
| 436 | private boolean isCheckResponseOverdue() { |
| 437 | if (mLastTriggerTimestamp == null) { |
| 438 | return false; |
| 439 | } |
| 440 | // Risk of overflow, but highly unlikely given the implementation and not problematic. |
| 441 | return mClockHelper.currentTimestamp() > mLastTriggerTimestamp + mCheckTimeAllowedMillis; |
| 442 | } |
| 443 | |
| 444 | private PackageVersions lookupInstalledPackageVersions() { |
| 445 | int updatePackageVersion; |
| 446 | int dataPackageVersion; |
| 447 | try { |
| 448 | updatePackageVersion = |
| 449 | mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName); |
| 450 | dataPackageVersion = |
| 451 | mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName); |
| 452 | } catch (PackageManager.NameNotFoundException e) { |
| 453 | Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package" |
| 454 | + " versions", e); |
| 455 | return null; |
| 456 | } |
| 457 | return new PackageVersions(updatePackageVersion, dataPackageVersion); |
| 458 | } |
| 459 | |
| 460 | private boolean validateDataAppManifest() { |
| 461 | // We only want to talk to a provider that exposed by the known data app package |
| 462 | // so we look up the providers exposed by that app and check the well-known authority is |
| 463 | // there. This prevents the case where *even if* the data app doesn't expose the provider |
| 464 | // required, another app cannot expose one to replace it. |
| 465 | if (!mPackageManagerHelper.contentProviderRegistered( |
| 466 | TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) { |
| 467 | // Error! Found the package but it didn't expose the correct provider. |
| 468 | Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName |
| 469 | + " does not expose the required provider with authority=" |
| 470 | + TimeZoneRulesDataContract.AUTHORITY); |
| 471 | return false; |
| 472 | } |
| 473 | // TODO(nfuller) Add any permissions checks needed. |
| 474 | return true; |
| 475 | } |
| 476 | |
| 477 | private boolean validateUpdaterAppManifest() { |
| 478 | try { |
| 479 | // The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission. |
| 480 | // The updater app is expected to have a receiver for the intent we are going to trigger |
| 481 | // and require the TRIGGER_TIME_ZONE_RULES_CHECK. |
| 482 | if (!mPackageManagerHelper.usesPermission( |
| 483 | mUpdateAppPackageName, |
| 484 | RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) { |
| 485 | Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName |
| 486 | + " does not use permission=" |
| 487 | + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION); |
| 488 | return false; |
| 489 | } |
| 490 | if (!mPackageManagerHelper.receiverRegistered( |
| 491 | RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName), |
| 492 | RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) { |
| 493 | return false; |
| 494 | } |
| 495 | |
| 496 | return true; |
| 497 | } catch (PackageManager.NameNotFoundException e) { |
| 498 | Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName |
| 499 | + " does not expose the required broadcast receiver.", e); |
| 500 | return false; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) { |
| 505 | if (value == null || value.trim().isEmpty()) { |
| 506 | throw logAndThrowRuntimeException(message, null); |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) { |
| 511 | Slog.wtf(TAG, message, cause); |
| 512 | throw new RuntimeException(message, cause); |
| 513 | } |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 514 | |
| 515 | public void dump(PrintWriter fout) { |
| 516 | fout.println("PackageTrackerState: " + toString()); |
| 517 | mPackageStatusStorage.dump(fout); |
| 518 | } |
| 519 | |
| 520 | @Override |
| 521 | public String toString() { |
| 522 | return "PackageTracker{" + |
| 523 | "mTrackingEnabled=" + mTrackingEnabled + |
| 524 | ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' + |
| 525 | ", mDataAppPackageName='" + mDataAppPackageName + '\'' + |
| 526 | ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis + |
| 527 | ", mFailedCheckRetryCount=" + mFailedCheckRetryCount + |
| 528 | ", mLastTriggerTimestamp=" + mLastTriggerTimestamp + |
| 529 | ", mCheckTriggered=" + mCheckTriggered + |
| 530 | ", mCheckFailureCount=" + mCheckFailureCount + |
| 531 | '}'; |
| 532 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 533 | } |