blob: e8dfd779a715f16fb98d21450d85d09b329e2227 [file] [log] [blame]
Neil Fuller68f66662017-03-16 18:32:21 +00001/*
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
17package com.android.server.timezone;
18
19import com.android.internal.annotations.VisibleForTesting;
20
21import android.app.timezone.RulesUpdaterContract;
22import android.content.Context;
23import android.content.pm.PackageManager;
Neil Fuller5f6750f2017-05-17 04:43:12 +010024import android.os.Environment;
Neil Fuller68f66662017-03-16 18:32:21 +000025import android.provider.TimeZoneRulesDataContract;
26import android.util.Slog;
27
Neil Fuller5f6750f2017-05-17 04:43:12 +010028import java.io.File;
Neil Fuller87b11282017-06-23 16:43:45 +010029import java.io.PrintWriter;
Neil Fuller5f6750f2017-05-17 04:43:12 +010030
Neil Fuller68f66662017-03-16 18:32:21 +000031/**
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)
54public 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 Fuller5f6750f2017-05-17 04:43:12 +010088 // 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 Fuller68f66662017-03-16 18:32:21 +000094 return new PackageTracker(
95 helperImpl /* clock */,
96 helperImpl /* configHelper */,
97 helperImpl /* packageManagerHelper */,
Neil Fuller5f6750f2017-05-17 04:43:12 +010098 new PackageStatusStorage(storageDir),
Neil Fuller68f66662017-03-16 18:32:21 +000099 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 Fuller87b11282017-06-23 16:43:45 +0100514
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 Fuller68f66662017-03-16 18:32:21 +0000533}