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.os.FileUtils; 26 import android.os.SystemClock; 27 import android.provider.TimeZoneRulesDataContract; 28 import android.util.Slog; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.PrintWriter; 33 import java.time.Clock; 34 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) 58 public class PackageTracker { 59 private static final String TAG = "timezone.PackageTracker"; 60 61 private final PackageManagerHelper mPackageManagerHelper; 62 private final PackageTrackerIntentHelper mIntentHelper; 63 private final ConfigHelper mConfigHelper; 64 private final PackageStatusStorage mPackageStatusStorage; 65 private final Clock mElapsedRealtimeClock; 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 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 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. */ create(Context context)97 static PackageTracker create(Context context) { 98 Clock elapsedRealtimeClock = SystemClock.elapsedRealtimeClock(); 99 PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context); 100 File storageDir = FileUtils.createDir(Environment.getDataSystemDirectory(), "timezone"); 101 return new PackageTracker( 102 elapsedRealtimeClock /* elapsedRealtimeClock */, 103 helperImpl /* configHelper */, 104 helperImpl /* packageManagerHelper */, 105 new PackageStatusStorage(storageDir), 106 new PackageTrackerIntentHelperImpl(context)); 107 } 108 109 // A constructor that can be used by tests to supply mocked / faked dependencies. PackageTracker(Clock elapsedRealtimeClock, ConfigHelper configHelper, PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage, PackageTrackerIntentHelper intentHelper)110 PackageTracker(Clock elapsedRealtimeClock, ConfigHelper configHelper, 111 PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage, 112 PackageTrackerIntentHelper intentHelper) { 113 mElapsedRealtimeClock = elapsedRealtimeClock; 114 mConfigHelper = configHelper; 115 mPackageManagerHelper = packageManagerHelper; 116 mPackageStatusStorage = packageStatusStorage; 117 mIntentHelper = intentHelper; 118 } 119 120 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) start()121 protected synchronized boolean start() { 122 mTrackingEnabled = mConfigHelper.isTrackingEnabled(); 123 if (!mTrackingEnabled) { 124 Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled."); 125 return false; 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 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 151 // Initialize the intent helper. 152 mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this); 153 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); 158 159 Slog.i(TAG, "Time zone updater / data package tracking enabled"); 160 return true; 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 */ throwIfDeviceSettingsOrAppsAreBad()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. 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 } 194 Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid."); 195 196 // Validate the data application package. 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 } 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 */ triggerUpdateIfNeeded(boolean packageChanged)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 232 // There's no point in doing any reliability triggers if the current packages are bad. 233 mIntentHelper.unscheduleReliabilityTrigger(); 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."); 258 // Don't do any work now but we do schedule a future reliability trigger. 259 mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); 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."); 268 mIntentHelper.unscheduleReliabilityTrigger(); 269 return; 270 } else if (mCheckFailureCount == 0) { 271 // Case 4. 272 Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was" 273 + " successful."); 274 mIntentHelper.unscheduleReliabilityTrigger(); 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"); 284 mIntentHelper.unscheduleReliabilityTrigger(); 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."); 309 mIntentHelper.unscheduleReliabilityTrigger(); 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."); 320 // Trigger again later: perhaps we'll have better luck. 321 mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); 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 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); 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) recordCheckResult(CheckToken checkToken, boolean success)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 393 // Schedule a reliability trigger and reset the failure count so we know that the 394 // next reliability trigger will do something. 395 mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); 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) { 408 // Since the check was successful, no reliability trigger is required until 409 // there is a package change. 410 mIntentHelper.unscheduleReliabilityTrigger(); 411 mCheckFailureCount = 0; 412 } else { 413 // Enable schedule a reliability trigger to check again in future. 414 mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); 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 423 // Schedule a reliability trigger to potentially try again in future. 424 mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis); 425 mCheckFailureCount++; 426 } 427 } 428 } 429 430 /** Access to consecutive failure counts for use in tests. */ 431 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getCheckFailureCountForTests()432 protected int getCheckFailureCountForTests() { 433 return mCheckFailureCount; 434 } 435 setCheckInProgress()436 private void setCheckInProgress() { 437 mLastTriggerTimestamp = mElapsedRealtimeClock.millis(); 438 } 439 setCheckComplete()440 private void setCheckComplete() { 441 mLastTriggerTimestamp = null; 442 } 443 isCheckInProgress()444 private boolean isCheckInProgress() { 445 return mLastTriggerTimestamp != null; 446 } 447 isCheckResponseOverdue()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. 453 return mElapsedRealtimeClock.millis() > mLastTriggerTimestamp + mCheckTimeAllowedMillis; 454 } 455 lookupInstalledPackageVersions()456 private PackageVersions lookupInstalledPackageVersions() { 457 long updatePackageVersion; 458 long dataPackageVersion; 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 validateDataAppManifest()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 } 485 return true; 486 } 487 validateUpdaterAppManifest()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 throwRuntimeExceptionIfNullOrEmpty(String value, String message)515 private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) { 516 if (value == null || value.trim().isEmpty()) { 517 throw logAndThrowRuntimeException(message, null); 518 } 519 } 520 logAndThrowRuntimeException(String message, Throwable cause)521 private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) { 522 Slog.wtf(TAG, message, cause); 523 throw new RuntimeException(message, cause); 524 } 525 dump(PrintWriter fout)526 public void dump(PrintWriter fout) { 527 fout.println("PackageTrackerState: " + toString()); 528 mPackageStatusStorage.dump(fout); 529 } 530 531 @Override toString()532 public String toString() { 533 return "PackageTracker{" + 534 "mTrackingEnabled=" + mTrackingEnabled + 535 ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' + 536 ", mDataAppPackageName='" + mDataAppPackageName + '\'' + 537 ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis + 538 ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis + 539 ", mFailedCheckRetryCount=" + mFailedCheckRetryCount + 540 ", mLastTriggerTimestamp=" + mLastTriggerTimestamp + 541 ", mCheckTriggered=" + mCheckTriggered + 542 ", mCheckFailureCount=" + mCheckFailureCount + 543 '}'; 544 } 545 } 546