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