From 8247153b486838872d18d7c61215b5d03d3f3f92 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Thu, 26 May 2022 18:23:19 +0100 Subject: [PATCH] Change TimeDetectorStrategy.Environment API Switch TimeDetectorStrategy.Environment to returning ConfigurationInternal in place of an individual property. This is in preparation for implementing the methods to update time detector configuration and support listeners. More properties will be added to ConfigurationInternal / removed from Environment in later commits. This mirrors the situation in com.android.server.timezonedetector and is part of a sequence of changes to make com.android.server.timedetector follow the patterns for configuration used there. Bug: 172891783 Bug: 229740080 Test: atest com.android.server.timedetector android.app.time Change-Id: I0ffe467ede4b6b0279b284108a368c5265a323a0 --- .../android/app/time/TimeCapabilities.java | 103 ++- .../android/app/time/TimeConfiguration.java | 110 ++- .../app/time/TimeCapabilitiesTest.java | 204 ++++- .../timedetector/ConfigurationInternal.java | 157 +++- .../server/timedetector/EnvironmentImpl.java | 65 +- .../timedetector/ServiceConfigAccessor.java | 39 +- .../ServiceConfigAccessorImpl.java | 183 ++++- .../timedetector/TimeDetectorService.java | 26 +- .../timedetector/TimeDetectorStrategy.java | 5 +- .../TimeDetectorStrategyImpl.java | 157 ++-- .../ConfigurationInternal.java | 7 +- .../ConfigurationInternalTest.java | 160 +++- .../FakeServiceConfigAccessor.java | 125 +++ .../timedetector/TimeDetectorServiceTest.java | 60 +- .../TimeDetectorStrategyImplTest.java | 719 +++++++++--------- 15 files changed, 1473 insertions(+), 647 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java index fff36c4a70962..9b9dce2bcbe4e 100644 --- a/core/java/android/app/time/TimeCapabilities.java +++ b/core/java/android/app/time/TimeCapabilities.java @@ -16,7 +16,10 @@ package android.app.time; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.time.Capabilities.CapabilityState; import android.os.Parcel; import android.os.Parcelable; @@ -54,40 +57,40 @@ public final class TimeCapabilities implements Parcelable { */ @NonNull private final UserHandle mUserHandle; - private final @CapabilityState int mConfigureAutoTimeDetectionEnabledCapability; - private final @CapabilityState int mSuggestTimeManuallyCapability; + private final @CapabilityState int mConfigureAutoDetectionEnabledCapability; + private final @CapabilityState int mSuggestManualTimeCapability; private TimeCapabilities(@NonNull Builder builder) { this.mUserHandle = Objects.requireNonNull(builder.mUserHandle); - this.mConfigureAutoTimeDetectionEnabledCapability = + this.mConfigureAutoDetectionEnabledCapability = builder.mConfigureAutoDetectionEnabledCapability; - this.mSuggestTimeManuallyCapability = - builder.mSuggestTimeManuallyCapability; + this.mSuggestManualTimeCapability = builder.mSuggestManualTimeCapability; } @NonNull private static TimeCapabilities createFromParcel(Parcel in) { UserHandle userHandle = UserHandle.readFromParcel(in); return new TimeCapabilities.Builder(userHandle) - .setConfigureAutoTimeDetectionEnabledCapability(in.readInt()) - .setSuggestTimeManuallyCapability(in.readInt()) + .setConfigureAutoDetectionEnabledCapability(in.readInt()) + .setSuggestManualTimeCapability(in.readInt()) .build(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { UserHandle.writeToParcel(mUserHandle, dest); - dest.writeInt(mConfigureAutoTimeDetectionEnabledCapability); - dest.writeInt(mSuggestTimeManuallyCapability); + dest.writeInt(mConfigureAutoDetectionEnabledCapability); + dest.writeInt(mSuggestManualTimeCapability); } /** * Returns the capability state associated with the user's ability to modify the automatic time - * detection setting. + * detection setting. The setting can be updated via {@link + * TimeManager#updateTimeConfiguration(TimeConfiguration)}. */ @CapabilityState - public int getConfigureAutoTimeDetectionEnabledCapability() { - return mConfigureAutoTimeDetectionEnabledCapability; + public int getConfigureAutoDetectionEnabledCapability() { + return mConfigureAutoDetectionEnabledCapability; } /** @@ -95,8 +98,31 @@ public final class TimeCapabilities implements Parcelable { * device. */ @CapabilityState - public int getSuggestTimeManuallyCapability() { - return mSuggestTimeManuallyCapability; + public int getSuggestManualTimeCapability() { + return mSuggestManualTimeCapability; + } + + /** + * Tries to create a new {@link TimeConfiguration} from the {@code config} and the set of + * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is + * returned. If the capabilities do not permit one or more of the requested changes then {@code + * null} is returned. + * + * @hide + */ + @Nullable + public TimeConfiguration tryApplyConfigChanges( + @NonNull TimeConfiguration config, + @NonNull TimeConfiguration requestedChanges) { + TimeConfiguration.Builder newConfigBuilder = new TimeConfiguration.Builder(config); + if (requestedChanges.hasIsAutoDetectionEnabled()) { + if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) { + return null; + } + newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled()); + } + + return newConfigBuilder.build(); } @Override @@ -109,25 +135,25 @@ public final class TimeCapabilities implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TimeCapabilities that = (TimeCapabilities) o; - return mConfigureAutoTimeDetectionEnabledCapability - == that.mConfigureAutoTimeDetectionEnabledCapability - && mSuggestTimeManuallyCapability == that.mSuggestTimeManuallyCapability + return mConfigureAutoDetectionEnabledCapability + == that.mConfigureAutoDetectionEnabledCapability + && mSuggestManualTimeCapability == that.mSuggestManualTimeCapability && mUserHandle.equals(that.mUserHandle); } @Override public int hashCode() { - return Objects.hash(mUserHandle, mConfigureAutoTimeDetectionEnabledCapability, - mSuggestTimeManuallyCapability); + return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability, + mSuggestManualTimeCapability); } @Override public String toString() { return "TimeCapabilities{" + "mUserHandle=" + mUserHandle - + ", mConfigureAutoTimeDetectionEnabledCapability=" - + mConfigureAutoTimeDetectionEnabledCapability - + ", mSuggestTimeManuallyCapability=" + mSuggestTimeManuallyCapability + + ", mConfigureAutoDetectionEnabledCapability=" + + mConfigureAutoDetectionEnabledCapability + + ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability + '}'; } @@ -137,35 +163,32 @@ public final class TimeCapabilities implements Parcelable { * @hide */ public static class Builder { + @NonNull private final UserHandle mUserHandle; private @CapabilityState int mConfigureAutoDetectionEnabledCapability; - private @CapabilityState int mSuggestTimeManuallyCapability; - - public Builder(@NonNull TimeCapabilities timeCapabilities) { - Objects.requireNonNull(timeCapabilities); - this.mUserHandle = timeCapabilities.mUserHandle; - this.mConfigureAutoDetectionEnabledCapability = - timeCapabilities.mConfigureAutoTimeDetectionEnabledCapability; - this.mSuggestTimeManuallyCapability = - timeCapabilities.mSuggestTimeManuallyCapability; - } + private @CapabilityState int mSuggestManualTimeCapability; public Builder(@NonNull UserHandle userHandle) { this.mUserHandle = Objects.requireNonNull(userHandle); } - /** Sets the state for automatic time detection config. */ - public Builder setConfigureAutoTimeDetectionEnabledCapability( - @CapabilityState int setConfigureAutoTimeDetectionEnabledCapability) { + public Builder(@NonNull TimeCapabilities timeCapabilities) { + Objects.requireNonNull(timeCapabilities); + this.mUserHandle = timeCapabilities.mUserHandle; this.mConfigureAutoDetectionEnabledCapability = - setConfigureAutoTimeDetectionEnabledCapability; + timeCapabilities.mConfigureAutoDetectionEnabledCapability; + this.mSuggestManualTimeCapability = timeCapabilities.mSuggestManualTimeCapability; + } + + /** Sets the state for automatic time detection config. */ + public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) { + this.mConfigureAutoDetectionEnabledCapability = value; return this; } /** Sets the state for manual time change. */ - public Builder setSuggestTimeManuallyCapability( - @CapabilityState int suggestTimeManuallyCapability) { - this.mSuggestTimeManuallyCapability = suggestTimeManuallyCapability; + public Builder setSuggestManualTimeCapability(@CapabilityState int value) { + this.mSuggestManualTimeCapability = value; return this; } @@ -173,7 +196,7 @@ public final class TimeCapabilities implements Parcelable { public TimeCapabilities build() { verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability, "configureAutoDetectionEnabledCapability"); - verifyCapabilitySet(mSuggestTimeManuallyCapability, "suggestTimeManuallyCapability"); + verifyCapabilitySet(mSuggestManualTimeCapability, "mSuggestManualTimeCapability"); return new TimeCapabilities(this); } diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java index 70aede034d27c..11f6ed2a9f880 100644 --- a/core/java/android/app/time/TimeConfiguration.java +++ b/core/java/android/app/time/TimeConfiguration.java @@ -27,8 +27,16 @@ import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * User visible settings that control the behavior of the time zone detector / manual time zone - * entry. + * User visible settings that control the behavior of the time detector / manual time entry. + * + *

When reading the configuration, values for all settings will be provided. In some cases, such + * as when the device behavior relies on optional hardware / OEM configuration, or the value of + * several settings, the device behavior may not be directly affected by the setting value. + * + *

Settings can be left absent when updating configuration via {@link + * TimeManager#updateTimeConfiguration(TimeConfiguration)} and those settings will not be + * changed. Not all configuration settings can be modified by all users: see {@link + * TimeManager#getTimeCapabilitiesAndConfig()} and {@link TimeCapabilities} for details. * * @hide */ @@ -61,18 +69,10 @@ public final class TimeConfiguration implements Parcelable { this.mBundle = builder.mBundle; } - /** - * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This - * controls whether a device will attempt to determine the time automatically using - * contextual information if the device supports auto detection. - */ - public boolean isAutoDetectionEnabled() { - return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED); - } - - @Override - public int describeContents() { - return 0; + private static TimeConfiguration readFromParcel(Parcel in) { + return new TimeConfiguration.Builder() + .setPropertyBundleInternal(in.readBundle()) + .build(); } @Override @@ -80,10 +80,42 @@ public final class TimeConfiguration implements Parcelable { dest.writeBundle(mBundle); } - private static TimeConfiguration readFromParcel(Parcel in) { - return new TimeConfiguration.Builder() - .merge(in.readBundle()) - .build(); + /** + * Returns {@code true} if all known settings are present. + * + * @hide + */ + public boolean isComplete() { + return hasIsAutoDetectionEnabled(); + } + + /** + * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This + * controls whether a device will attempt to determine the time automatically using + * contextual information if the device supports auto detection. + * + *

See {@link TimeCapabilities#getConfigureAutoDetectionEnabledCapability()} for how to + * tell if the setting is meaningful for the current user at this time. + * + * @throws IllegalStateException if the setting is not present + */ + public boolean isAutoDetectionEnabled() { + enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED); + return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED); + } + + /** + * Returns {@code true} if the {@link #isAutoDetectionEnabled()} setting is present. + * + * @hide + */ + public boolean hasIsAutoDetectionEnabled() { + return mBundle.containsKey(SETTING_AUTO_DETECTION_ENABLED); + } + + @Override + public int describeContents() { + return 0; } @Override @@ -106,18 +138,49 @@ public final class TimeConfiguration implements Parcelable { + '}'; } + private void enforceSettingPresent(@TimeZoneConfiguration.Setting String setting) { + if (!mBundle.containsKey(setting)) { + throw new IllegalStateException(setting + " is not set"); + } + } + /** * A builder for {@link TimeConfiguration} objects. * * @hide */ public static final class Builder { + private final Bundle mBundle = new Bundle(); + /** + * Creates a new Builder with no settings held. + */ public Builder() {} - public Builder(@NonNull TimeConfiguration configuration) { - mBundle.putAll(configuration.mBundle); + /** + * Creates a new Builder by copying the settings from an existing instance. + */ + public Builder(@NonNull TimeConfiguration toCopy) { + mergeProperties(toCopy); + } + + /** + * Merges {@code other} settings into this instances, replacing existing values in this + * where the settings appear in both. + * + * @hide + */ + @NonNull + public Builder mergeProperties(@NonNull TimeConfiguration toCopy) { + mBundle.putAll(toCopy.mBundle); + return this; + } + + @NonNull + Builder setPropertyBundleInternal(@NonNull Bundle bundle) { + this.mBundle.putAll(bundle); + return this; } /** Sets whether auto detection is enabled or not. */ @@ -127,12 +190,7 @@ public final class TimeConfiguration implements Parcelable { return this; } - Builder merge(@NonNull Bundle bundle) { - mBundle.putAll(bundle); - return this; - } - - /** Returns {@link TimeConfiguration} object. */ + /** Returns the {@link TimeConfiguration}. */ @NonNull public TimeConfiguration build() { return new TimeConfiguration(this); diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java index 85073b0ecfddc..9d7dde2141bb6 100644 --- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java +++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java @@ -17,6 +17,7 @@ package android.app.time; +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; import static android.app.time.Capabilities.CAPABILITY_POSSESSED; @@ -24,6 +25,9 @@ import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTrip import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import android.os.UserHandle; @@ -38,75 +42,193 @@ import org.junit.runner.RunWith; @SmallTest public class TimeCapabilitiesTest { - private static final UserHandle USER_HANDLE = UserHandle.of(332211); + private static final UserHandle TEST_USER_HANDLE = UserHandle.of(332211); @Test - public void testBuilder() { - TimeCapabilities capabilities = new TimeCapabilities.Builder(USER_HANDLE) - .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) - .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED) - .build(); - - assertThat(capabilities.getConfigureAutoTimeDetectionEnabledCapability()) - .isEqualTo(CAPABILITY_NOT_APPLICABLE); - assertThat(capabilities.getSuggestTimeManuallyCapability()) - .isEqualTo(CAPABILITY_NOT_SUPPORTED); - - try { - new TimeCapabilities.Builder(USER_HANDLE) - .build(); - fail("Should throw IllegalStateException"); - } catch (IllegalStateException ignored) { - // expected + public void testEquals() { + TimeCapabilities.Builder builder1 = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED); + TimeCapabilities.Builder builder2 = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED); + { + TimeCapabilities one = builder1.build(); + TimeCapabilities two = builder2.build(); + assertEquals(one, two); } - try { - new TimeCapabilities.Builder(USER_HANDLE) - .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) - .build(); - fail("Should throw IllegalStateException"); - } catch (IllegalStateException ignored) { - // expected + builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeCapabilities one = builder1.build(); + TimeCapabilities two = builder2.build(); + assertNotEquals(one, two); } - try { - new TimeCapabilities.Builder(USER_HANDLE) - .setSuggestTimeManuallyCapability(CAPABILITY_NOT_APPLICABLE) - .build(); - fail("Should throw IllegalStateException"); - } catch (IllegalStateException ignored) { - // expected + builder1.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeCapabilities one = builder1.build(); + TimeCapabilities two = builder2.build(); + assertEquals(one, two); + } + + builder2.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED); + { + TimeCapabilities one = builder1.build(); + TimeCapabilities two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED); + { + TimeCapabilities one = builder1.build(); + TimeCapabilities two = builder2.build(); + assertEquals(one, two); } } @Test public void userHandle_notIgnoredInEquals() { TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1)) - .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) .build(); TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2)) - .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) .build(); assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities); } + @Test + public void testBuilder() { + TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) + .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED) + .build(); + + assertThat(capabilities.getConfigureAutoDetectionEnabledCapability()) + .isEqualTo(CAPABILITY_NOT_APPLICABLE); + assertThat(capabilities.getSuggestManualTimeCapability()) + .isEqualTo(CAPABILITY_NOT_SUPPORTED); + + try { + new TimeCapabilities.Builder(TEST_USER_HANDLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + + try { + new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + + try { + new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setSuggestManualTimeCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ignored) { + // expected + } + } + @Test public void testParcelable() { - TimeCapabilities.Builder builder = new TimeCapabilities.Builder(USER_HANDLE) - .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED) - .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED); + TimeCapabilities.Builder builder = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED) + .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED); assertRoundTripParcelable(builder.build()); - builder.setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED); + builder.setSuggestManualTimeCapability(CAPABILITY_POSSESSED); assertRoundTripParcelable(builder.build()); - builder.setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED); + builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED); assertRoundTripParcelable(builder.build()); } + @Test + public void testTryApplyConfigChanges_permitted() { + TimeConfiguration oldConfiguration = + new TimeConfiguration.Builder() + .setAutoDetectionEnabled(true) + .build(); + TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .build(); + + TimeConfiguration configChange = new TimeConfiguration.Builder() + .setAutoDetectionEnabled(false) + .build(); + + TimeConfiguration expected = new TimeConfiguration.Builder(oldConfiguration) + .setAutoDetectionEnabled(false) + .build(); + assertEquals(expected, capabilities.tryApplyConfigChanges(oldConfiguration, configChange)); + } + + @Test + public void testTryApplyConfigChanges_notPermitted() { + TimeConfiguration oldConfiguration = + new TimeConfiguration.Builder() + .setAutoDetectionEnabled(true) + .build(); + TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) + .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .build(); + + TimeConfiguration configChange = new TimeConfiguration.Builder() + .setAutoDetectionEnabled(false) + .build(); + + assertNull(capabilities.tryApplyConfigChanges(oldConfiguration, configChange)); + } + + @Test + public void copyBuilder_copiesAllFields() { + TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) + .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .build(); + + { + TimeCapabilities updatedCapabilities = + new TimeCapabilities.Builder(capabilities) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .build(); + TimeCapabilities expectedCapabilities = + new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .build(); + + assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); + } + + { + TimeCapabilities updatedCapabilities = + new TimeCapabilities.Builder(capabilities) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .build(); + + TimeCapabilities expectedCapabilities = + new TimeCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) + .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .build(); + + assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); + } + } } diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java index f00f856c2910f..3f6e2117a1fca 100644 --- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java @@ -17,8 +17,11 @@ package com.android.server.timedetector; import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; import static android.app.time.Capabilities.CAPABILITY_POSSESSED; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.time.Capabilities.CapabilityState; import android.app.time.TimeCapabilities; @@ -29,18 +32,57 @@ import android.os.UserHandle; import java.util.Objects; /** - * Holds configuration values that affect time behaviour. + * Holds configuration values that affect user-facing time behavior and some associated logic. + * Some configuration is global, some is user scoped, but this class deliberately doesn't make a + * distinction for simplicity. */ public final class ConfigurationInternal { + private final boolean mAutoDetectionSupported; + private final boolean mAutoDetectionEnabledSetting; private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; - private final boolean mAutoDetectionEnabled; private ConfigurationInternal(Builder builder) { + mAutoDetectionSupported = builder.mAutoDetectionSupported; + mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting; + mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; - mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + } + + /** Returns true if the device supports any form of auto time detection. */ + public boolean isAutoDetectionSupported() { + return mAutoDetectionSupported; + } + + /** Returns the value of the auto time detection enabled setting. */ + public boolean getAutoDetectionEnabledSetting() { + return mAutoDetectionEnabledSetting; + } + + /** + * Returns true if auto time detection behavior is actually enabled, which can be distinct + * from the raw setting value. + */ + public boolean getAutoDetectionEnabledBehavior() { + return isAutoDetectionSupported() && mAutoDetectionEnabledSetting; + } + + /** Returns the ID of the user this configuration is associated with. */ + public @UserIdInt int getUserId() { + return mUserId; + } + + /** Returns the handle of the user this configuration is associated with. */ + @NonNull + public UserHandle getUserHandle() { + return UserHandle.of(mUserId); + } + + /** Returns true if the user allowed to modify time zone configuration. */ + public boolean isUserConfigAllowed() { + return mUserConfigAllowed; } /** Returns a {@link TimeCapabilitiesAndConfig} objects based on configuration values. */ @@ -48,28 +90,58 @@ public final class ConfigurationInternal { return new TimeCapabilitiesAndConfig(timeCapabilities(), timeConfiguration()); } + private TimeCapabilities timeCapabilities() { + UserHandle userHandle = UserHandle.of(mUserId); + TimeCapabilities.Builder builder = new TimeCapabilities.Builder(userHandle); + + boolean allowConfigDateTime = isUserConfigAllowed(); + + boolean deviceHasAutoTimeDetection = isAutoDetectionSupported(); + final @CapabilityState int configureAutoDetectionEnabledCapability; + if (!deviceHasAutoTimeDetection) { + configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; + } else if (!allowConfigDateTime) { + configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED; + } else { + configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED; + } + builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability); + + // The ability to make manual time suggestions can also be restricted by policy. With the + // current logic above, this could lead to a situation where a device hardware does not + // support auto detection, the device has been forced into "auto" mode by an admin and the + // user is unable to disable auto detection. + final @CapabilityState int suggestManualTimeZoneCapability; + if (!allowConfigDateTime) { + suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED; + } else if (getAutoDetectionEnabledBehavior()) { + suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE; + } else { + suggestManualTimeZoneCapability = CAPABILITY_POSSESSED; + } + builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability); + + return builder.build(); + } + + /** Returns a {@link TimeConfiguration} from the configuration values. */ private TimeConfiguration timeConfiguration() { return new TimeConfiguration.Builder() - .setAutoDetectionEnabled(mAutoDetectionEnabled) + .setAutoDetectionEnabled(getAutoDetectionEnabledSetting()) .build(); } - private TimeCapabilities timeCapabilities() { - @CapabilityState int configureAutoTimeDetectionEnabledCapability = - mUserConfigAllowed - ? CAPABILITY_POSSESSED - : CAPABILITY_NOT_ALLOWED; - - @CapabilityState int suggestTimeManuallyCapability = - mUserConfigAllowed - ? CAPABILITY_POSSESSED - : CAPABILITY_NOT_ALLOWED; - - return new TimeCapabilities.Builder(UserHandle.of(mUserId)) - .setConfigureAutoTimeDetectionEnabledCapability( - configureAutoTimeDetectionEnabledCapability) - .setSuggestTimeManuallyCapability(suggestTimeManuallyCapability) - .build(); + /** + * Merges the configuration values from this with any properties set in {@code + * newConfiguration}. The new configuration has precedence. Used to apply user updates to + * internal configuration. + */ + public ConfigurationInternal merge(TimeConfiguration newConfiguration) { + Builder builder = new Builder(this); + if (newConfiguration.hasIsAutoDetectionEnabled()) { + builder.setAutoDetectionEnabledSetting(newConfiguration.isAutoDetectionEnabled()); + } + return builder.build(); } @Override @@ -77,44 +149,75 @@ public final class ConfigurationInternal { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConfigurationInternal that = (ConfigurationInternal) o; - return mUserId == that.mUserId + return mAutoDetectionSupported == that.mAutoDetectionSupported + && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed - && mAutoDetectionEnabled == that.mAutoDetectionEnabled; + && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting; } @Override public int hashCode() { - return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionEnabled); + return Objects.hash(mAutoDetectionSupported, mUserId, + mUserConfigAllowed, mAutoDetectionEnabledSetting); } @Override public String toString() { return "ConfigurationInternal{" + + "mAutoDetectionSupported=" + mAutoDetectionSupported + "mUserId=" + mUserId + ", mUserConfigAllowed=" + mUserConfigAllowed - + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled + + ", mAutoDetectionEnabled=" + mAutoDetectionEnabledSetting + '}'; } static final class Builder { private final @UserIdInt int mUserId; + private boolean mUserConfigAllowed; - private boolean mAutoDetectionEnabled; + private boolean mAutoDetectionSupported; + private boolean mAutoDetectionEnabledSetting; Builder(@UserIdInt int userId) { mUserId = userId; } + /** + * Creates a new Builder by copying values from an existing instance. + */ + Builder(ConfigurationInternal toCopy) { + this.mUserId = toCopy.mUserId; + this.mUserConfigAllowed = toCopy.mUserConfigAllowed; + this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported; + this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting; + } + + /** + * Sets whether the user is allowed to configure time settings on this device. + */ Builder setUserConfigAllowed(boolean userConfigAllowed) { mUserConfigAllowed = userConfigAllowed; return this; } - Builder setAutoDetectionEnabled(boolean autoDetectionEnabled) { - mAutoDetectionEnabled = autoDetectionEnabled; + /** + * Sets whether automatic time detection is supported on this device. + */ + public Builder setAutoDetectionSupported(boolean supported) { + mAutoDetectionSupported = supported; return this; } + /** + * Sets the value of the automatic time detection enabled setting for this device. + */ + Builder setAutoDetectionEnabledSetting(boolean autoDetectionEnabledSetting) { + mAutoDetectionEnabledSetting = autoDetectionEnabledSetting; + return this; + } + + /** Returns a new {@link ConfigurationInternal}. */ + @NonNull ConfigurationInternal build() { return new ConfigurationInternal(this); } diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java index 9d263516db3ab..fe4eb66922af7 100644 --- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java @@ -17,21 +17,16 @@ package com.android.server.timedetector; import android.annotation.NonNull; -import android.annotation.UserIdInt; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; import android.os.Build; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; -import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.server.timezonedetector.ConfigurationChangeListener; import java.time.Instant; @@ -52,10 +47,6 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { @NonNull private final AlarmManager mAlarmManager; @NonNull private final UserManager mUserManager; - // @NonNull after setConfigChangeListener() is called. - @GuardedBy("this") - private ConfigurationChangeListener mConfigChangeListener; - EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); @@ -70,39 +61,14 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class)); mUserManager = Objects.requireNonNull(context.getSystemService(UserManager.class)); - - // Wire up the config change listeners. All invocations are performed on the mHandler - // thread. - - ContentResolver contentResolver = context.getContentResolver(); - contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, - new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - handleAutoTimeDetectionChangedOnHandlerThread(); - } - }); - mServiceConfigAccessor.addListener( - () -> mHandler.post( - EnvironmentImpl.this::handleAutoTimeDetectionChangedOnHandlerThread)); - } - - /** Internal method for handling the auto time setting being changed. */ - private void handleAutoTimeDetectionChangedOnHandlerThread() { - synchronized (this) { - if (mConfigChangeListener == null) { - Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null"); - } - mConfigChangeListener.onChange(); - } } @Override - public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) { - synchronized (this) { - mConfigChangeListener = Objects.requireNonNull(listener); - } + public void setConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + ConfigurationChangeListener configurationChangeListener = + () -> mHandler.post(listener::onChange); + mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener); } @Override @@ -110,15 +76,6 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { return mServiceConfigAccessor.systemClockUpdateThresholdMillis(); } - @Override - public boolean isAutoTimeDetectionEnabled() { - try { - return Settings.Global.getInt(mContentResolver, Settings.Global.AUTO_TIME) != 0; - } catch (Settings.SettingNotFoundException snfe) { - return true; - } - } - @Override public Instant autoTimeLowerBound() { return mServiceConfigAccessor.autoTimeLowerBound(); @@ -130,11 +87,8 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { } @Override - public ConfigurationInternal configurationInternal(@UserIdInt int userId) { - return new ConfigurationInternal.Builder(userId) - .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setAutoDetectionEnabled(isAutoTimeDetectionEnabled()) - .build(); + public ConfigurationInternal getCurrentUserConfigurationInternal() { + return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @Override @@ -177,9 +131,4 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " not held"); } } - - private boolean isUserConfigAllowed(@UserIdInt int userId) { - UserHandle userHandle = UserHandle.of(userId); - return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); - } } diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java index b4aa84e27b09b..05d21723f2875 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java @@ -16,6 +16,8 @@ package com.android.server.timedetector; import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.time.TimeConfiguration; import com.android.server.timedetector.TimeDetectorStrategy.Origin; import com.android.server.timezonedetector.ConfigurationChangeListener; @@ -33,12 +35,23 @@ public interface ServiceConfigAccessor { /** * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. * The listener is invoked on the main thread. - * - * - *

Note: Only for use by long-lived objects. There is deliberately no associated remove - * method. */ - void addListener(@NonNull ConfigurationChangeListener listener); + void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener); + + /** + * Removes a listener previously added via {@link + * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}. + */ + void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener); + + /** + * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a + * snapshot so callers must use {@link + * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it + * changes. + */ + @NonNull + ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Returns the absolute threshold below which the system clock need not be updated. i.e. if @@ -62,4 +75,20 @@ public interface ServiceConfigAccessor { */ @NonNull @Origin int[] getOriginPriorities(); + + /** + * Updates the configuration properties that control a device's time behavior. + * + *

This method returns {@code true} if the configuration was changed, + * {@code false} otherwise. + */ + boolean updateConfiguration( + @UserIdInt int userId, @NonNull TimeConfiguration requestedConfiguration); + + /** + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * specified user. + */ + @NonNull + ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); } diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java index fa654e2e3b605..b53c5124081e8 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java @@ -15,25 +15,46 @@ */ package com.android.server.timedetector; +import static android.content.Intent.ACTION_USER_SWITCHED; + import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE; import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE; +import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL; +import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; +import android.app.time.TimeCapabilities; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.os.Build; import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; import com.android.server.timedetector.TimeDetectorStrategy.Origin; import com.android.server.timezonedetector.ConfigurationChangeListener; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -75,9 +96,15 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { @NonNull private final Context mContext; @NonNull private final ServerFlags mServerFlags; + @NonNull private final ContentResolver mCr; + @NonNull private final UserManager mUserManager; @NonNull private final ConfigOriginPrioritiesSupplier mConfigOriginPrioritiesSupplier; @NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier; + @GuardedBy("this") + @NonNull private final List mConfigurationInternalListeners = + new ArrayList<>(); + /** * If a newly calculated system clock time and the current system clock time differs by this or * more the system clock will actually be updated. Used to prevent the system clock being set @@ -87,6 +114,8 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { private ServiceConfigAccessorImpl(@NonNull Context context) { mContext = Objects.requireNonNull(context); + mCr = context.getContentResolver(); + mUserManager = context.getSystemService(UserManager.class); mServerFlags = ServerFlags.getInstance(mContext); mConfigOriginPrioritiesSupplier = new ConfigOriginPrioritiesSupplier(context); mServerFlagsOriginPrioritiesSupplier = @@ -94,6 +123,35 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { mSystemClockUpdateThresholdMillis = SystemProperties.getInt("ro.sys.time_detector_update_diff", SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT); + + // Wire up the config change listeners for anything that could affect ConfigurationInternal. + // Use the main thread for event delivery, listeners can post to their chosen thread. + + // Listen for the user changing / the user's location mode changing. Report on the main + // thread. + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USER_SWITCHED); + mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleConfigurationInternalChangeOnMainThread(); + } + }, filter, null, null /* main thread */); + + // Add async callbacks for global settings being changed. + ContentResolver contentResolver = mContext.getContentResolver(); + ContentObserver contentObserver = new ContentObserver(mContext.getMainThreadHandler()) { + @Override + public void onChange(boolean selfChange) { + handleConfigurationInternalChangeOnMainThread(); + } + }; + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, contentObserver); + + // Watch server flags. + mServerFlags.addListener(this::handleConfigurationInternalChangeOnMainThread, + SERVER_FLAGS_KEYS_TO_WATCH); } /** Returns the singleton instance. */ @@ -106,15 +164,22 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { } } - /** - * Adds a listener that will be called when server flags related to this class change. The - * callbacks are delivered on the main looper thread. - * - *

Note: Only for use by long-lived objects. There is deliberately no associated remove - * method. - */ - public void addListener(@NonNull ConfigurationChangeListener listener) { - mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH); + private synchronized void handleConfigurationInternalChangeOnMainThread() { + for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) { + changeListener.onChange(); + } + } + + @Override + public synchronized void addConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + mConfigurationInternalListeners.add(Objects.requireNonNull(listener)); + } + + @Override + public synchronized void removeConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + mConfigurationInternalListeners.remove(Objects.requireNonNull(listener)); } @Override @@ -144,6 +209,106 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { .orElse(TIME_LOWER_BOUND_DEFAULT); } + @Override + @NonNull + public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() { + int currentUserId = + LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId(); + return getConfigurationInternal(currentUserId); + } + + @Override + public synchronized boolean updateConfiguration(@UserIdInt int userId, + @NonNull TimeConfiguration requestedConfiguration) { + Objects.requireNonNull(requestedConfiguration); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = + getCurrentUserConfigurationInternal().capabilitiesAndConfig(); + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + TimeConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration(); + + final TimeConfiguration newConfiguration = + capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration); + if (newConfiguration == null) { + // The changes could not be made because the user's capabilities do not allow it. + return false; + } + + // Store the configuration / notify as needed. This will cause the mEnvironment to invoke + // handleConfigChanged() asynchronously. + storeConfiguration(userId, newConfiguration); + + return true; + } + + /** + * Stores the configuration properties contained in {@code newConfiguration}. + * All checks about user capabilities must be done by the caller and + * {@link TimeConfiguration#isComplete()} must be {@code true}. + */ + @GuardedBy("this") + private void storeConfiguration( + @UserIdInt int userId, @NonNull TimeConfiguration configuration) { + Objects.requireNonNull(configuration); + + // Avoid writing the auto detection enabled setting for devices that do not support auto + // time detection: if we wrote it down then we'd set the value explicitly, which would + // prevent detecting "default" later. That might influence what happens on later releases + // that support new types of auto detection on the same hardware. + if (isAutoDetectionSupported()) { + final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); + setAutoDetectionEnabledIfRequired(autoDetectionEnabled); + } + } + + @Override + @NonNull + public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { + return new ConfigurationInternal.Builder(userId) + .setUserConfigAllowed(isUserConfigAllowed(userId)) + .setAutoDetectionSupported(isAutoDetectionSupported()) + .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting()) + .build(); + } + + private void setAutoDetectionEnabledIfRequired(boolean enabled) { + // This check is racey, but the whole settings update process is racey. This check prevents + // a ConfigurationChangeListener callback triggering due to ContentObserver's still + // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary + // for stable behavior during tests. + if (getAutoDetectionEnabledSetting() != enabled) { + Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME, enabled ? 1 : 0); + } + } + + private boolean isUserConfigAllowed(@UserIdInt int userId) { + UserHandle userHandle = UserHandle.of(userId); + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); + } + + private boolean getAutoDetectionEnabledSetting() { + return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME, 1 /* default */) > 0; + } + + /** Returns {@code true} if any form of automatic time detection is supported. */ + private boolean isAutoDetectionSupported() { + @Origin int[] originsSupported = getOriginPriorities(); + for (@Origin int originSupported : originsSupported) { + if (originSupported == ORIGIN_NETWORK + || originSupported == ORIGIN_EXTERNAL + || originSupported == ORIGIN_GNSS) { + return true; + } else if (originSupported == ORIGIN_TELEPHONY) { + boolean deviceHasTelephony = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + if (deviceHasTelephony) { + return true; + } + } + } + return false; + } + /** * A base supplier of an array of time origin integers in priority order. * It handles memoization of the result to avoid repeated string parsing when nothing has diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 51a5f5636bb6c..a6efa73d58eae 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -28,7 +28,6 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; -import android.os.Binder; import android.os.Handler; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -71,8 +70,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { TimeDetectorStrategy timeDetectorStrategy = TimeDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); - TimeDetectorService service = - new TimeDetectorService(context, handler, timeDetectorStrategy); + TimeDetectorService service = new TimeDetectorService( + context, handler, serviceConfigAccessor, timeDetectorStrategy); // Publish the binder service so it can be accessed from other (appropriately // permissioned) processes. @@ -82,21 +81,26 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @NonNull private final Handler mHandler; @NonNull private final Context mContext; - @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; @NonNull private final CallerIdentityInjector mCallerIdentityInjector; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; + @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeDetectorStrategy timeDetectorStrategy) { - this(context, handler, timeDetectorStrategy, CallerIdentityInjector.REAL); + this(context, handler, serviceConfigAccessor, timeDetectorStrategy, + CallerIdentityInjector.REAL); } @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeDetectorStrategy timeDetectorStrategy, @NonNull CallerIdentityInjector callerIdentityInjector) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); } @@ -108,13 +112,13 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { return getTimeCapabilitiesAndConfig(userId); } - private TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) { + TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) { enforceManageTimeDetectorPermission(); final long token = mCallerIdentityInjector.clearCallingIdentity(); try { ConfigurationInternal configurationInternal = - mTimeDetectorStrategy.getConfigurationInternal(userId); + mServiceConfigAccessor.getConfigurationInternal(userId); return configurationInternal.capabilitiesAndConfig(); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); @@ -141,11 +145,12 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { enforceSuggestManualTimePermission(); Objects.requireNonNull(timeSignal); - final long token = Binder.clearCallingIdentity(); + int userId = mCallerIdentityInjector.getCallingUserId(); + final long token = mCallerIdentityInjector.clearCallingIdentity(); try { - return mTimeDetectorStrategy.suggestManualTime(timeSignal); + return mTimeDetectorStrategy.suggestManualTime(userId, timeSignal); } finally { - Binder.restoreCallingIdentity(token); + mCallerIdentityInjector.restoreCallingIdentity(token); } } @@ -226,5 +231,4 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION, "manage time and time zone detection"); } - } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index acabb6e393a80..cec383ce77bed 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -76,7 +76,7 @@ public interface TimeDetectorStrategy extends Dumpable { * suggestion was accepted. A suggestion that is valid but does not change the time because it * matches the current device time is considered accepted. */ - boolean suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); + boolean suggestManualTime(@UserIdInt int userId, @NonNull ManualTimeSuggestion timeSuggestion); /** Processes the suggested time from network sources. */ void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion); @@ -87,9 +87,6 @@ public interface TimeDetectorStrategy extends Dumpable { /** Processes the suggested time from external sources. */ void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion); - /** Returns the configuration that controls time detector behaviour for specified user. */ - ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); - // Utility methods below are to be moved to a better home when one becomes more obvious. /** diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 4b430114902c3..ea6dfc31c31e1 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -20,6 +20,8 @@ import static com.android.server.timedetector.TimeDetectorStrategy.originToStrin import static java.util.stream.Collectors.joining; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -99,6 +101,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull private final Environment mEnvironment; + @GuardedBy("this") + @NonNull + private ConfigurationInternal mCurrentConfigurationInternal; + // Used to store the last time the system clock state was set automatically. It is used to // detect (and log) issues with the realtime clock or whether the clock is being set without // going through this strategy code. @@ -128,32 +134,32 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); /** - * The interface used by the strategy to interact with the surrounding service. + * Used by {@link TimeDetectorStrategyImpl} to interact with device configuration / settings + * / system properties. It can be faked for testing. * - *

Note: Because the system properties-derived value {@link #isAutoTimeDetectionEnabled()} - * can be modified independently and from different threads (and processes!), its use is prone - * to race conditions. That will be true until the responsibility for setting their values is - * moved to {@link TimeDetectorStrategy}. There are similar issues with - * {@link #systemClockMillis()} while any process can modify the system clock. + *

Note: Because the settings / system properties-derived values can currently be modified + * independently and from different threads (and processes!), their use is prone to race + * conditions. */ public interface Environment { /** * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any - * changes that could affect time detection. This is invoked during system server setup. + * changes that could affect the content of {@link ConfigurationInternal}. + * This is invoked during system server setup. */ - void setConfigChangeListener(@NonNull ConfigurationChangeListener listener); + void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener); + + /** Returns the {@link ConfigurationInternal} for the current user. */ + @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** - * The absolute threshold below which the system clock need not be updated. i.e. if setting - * the system clock would adjust it by less than this (either backwards or forwards) then it - * need not be set. + * Returns the absolute threshold below which the system clock need not be updated. i.e. if + * setting the system clock would adjust it by less than this (either backwards or forwards) + * then it need not be set. */ int systemClockUpdateThresholdMillis(); - /** Returns true if automatic time detection is enabled. */ - boolean isAutoTimeDetectionEnabled(); - /** * Returns a lower bound for valid automatic times. It is guaranteed to be in the past, * i.e. it is unrelated to the current system clock time. @@ -169,23 +175,19 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @Origin int[] autoOriginPriorities(); - /** - * Returns {@link ConfigurationInternal} for specified user. - */ - @NonNull - ConfigurationInternal configurationInternal(@UserIdInt int userId); - /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); /** Returns the elapsedRealtimeMillis clock value. */ + @ElapsedRealtimeLong long elapsedRealtimeMillis(); /** Returns the system clock value. */ + @CurrentTimeMillisLong long systemClockMillis(); /** Sets the device system clock. The WakeLock must be held. */ - void setSystemClock(long newTimeMillis); + void setSystemClock(@CurrentTimeMillisLong long newTimeMillis); /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); @@ -209,39 +211,73 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @VisibleForTesting TimeDetectorStrategyImpl(@NonNull Environment environment) { mEnvironment = Objects.requireNonNull(environment); - mEnvironment.setConfigChangeListener(this::handleAutoTimeConfigChanged); + + synchronized (this) { + mEnvironment.setConfigurationInternalChangeListener( + this::handleConfigurationInternalChanged); + mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal(); + } } @Override - public synchronized void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) { - final TimestampedValue newUnixEpochTime = timeSuggestion.getUnixEpochTime(); + public synchronized void suggestExternalTime(@NonNull ExternalTimeSuggestion suggestion) { + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (DBG) { + Slog.d(LOG_TAG, "External suggestion received." + + " currentUserConfig=" + currentUserConfig + + " newSuggestion=" + suggestion); + } + Objects.requireNonNull(suggestion); - if (!validateAutoSuggestionTime(newUnixEpochTime, timeSuggestion)) { + final TimestampedValue newUnixEpochTime = suggestion.getUnixEpochTime(); + + if (!validateAutoSuggestionTime(newUnixEpochTime, suggestion)) { return; } - mLastExternalSuggestion.set(timeSuggestion); + mLastExternalSuggestion.set(suggestion); - String reason = "External time suggestion received: suggestion=" + timeSuggestion; + String reason = "External time suggestion received: suggestion=" + suggestion; doAutoTimeDetection(reason); } @Override - public synchronized void suggestGnssTime(@NonNull GnssTimeSuggestion timeSuggestion) { - final TimestampedValue newUnixEpochTime = timeSuggestion.getUnixEpochTime(); + public synchronized void suggestGnssTime(@NonNull GnssTimeSuggestion suggestion) { + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (DBG) { + Slog.d(LOG_TAG, "GNSS suggestion received." + + " currentUserConfig=" + currentUserConfig + + " newSuggestion=" + suggestion); + } + Objects.requireNonNull(suggestion); - if (!validateAutoSuggestionTime(newUnixEpochTime, timeSuggestion)) { + final TimestampedValue newUnixEpochTime = suggestion.getUnixEpochTime(); + + if (!validateAutoSuggestionTime(newUnixEpochTime, suggestion)) { return; } - mLastGnssSuggestion.set(timeSuggestion); + mLastGnssSuggestion.set(suggestion); - String reason = "GNSS time suggestion received: suggestion=" + timeSuggestion; + String reason = "GNSS time suggestion received: suggestion=" + suggestion; doAutoTimeDetection(reason); } @Override - public synchronized boolean suggestManualTime(@NonNull ManualTimeSuggestion suggestion) { + public synchronized boolean suggestManualTime( + @UserIdInt int userId, @NonNull ManualTimeSuggestion suggestion) { + + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (currentUserConfig.getUserId() != userId) { + Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId + + " suggestion=" + suggestion); + + // Only listen to changes from the current user. + return false; + } + + Objects.requireNonNull(suggestion); + final TimestampedValue newUnixEpochTime = suggestion.getUnixEpochTime(); if (!validateSuggestionTime(newUnixEpochTime, suggestion)) { @@ -253,8 +289,16 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override - public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion) { - if (!validateAutoSuggestionTime(timeSuggestion.getUnixEpochTime(), timeSuggestion)) { + public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion) { + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (DBG) { + Slog.d(LOG_TAG, "Network suggestion received." + + " currentUserConfig=" + currentUserConfig + + " newSuggestion=" + suggestion); + } + Objects.requireNonNull(suggestion); + + if (!validateAutoSuggestionTime(suggestion.getUnixEpochTime(), suggestion)) { return; } @@ -266,13 +310,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // the suggestion and device state are always re-evaluated, which might produce a different // detected time if, for example, the age of all suggestions are considered. NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get(); - if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) { - mLastNetworkSuggestion.set(timeSuggestion); + if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(suggestion)) { + mLastNetworkSuggestion.set(suggestion); } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. - String reason = "New network time suggested. timeSuggestion=" + timeSuggestion; + String reason = "New network time suggested. timeSuggestion=" + suggestion; doAutoTimeDetection(reason); } @@ -303,17 +347,20 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { doAutoTimeDetection(reason); } - @Override - @NonNull - public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { - return mEnvironment.configurationInternal(userId); - } + private synchronized void handleConfigurationInternalChanged() { + ConfigurationInternal currentUserConfig = + mEnvironment.getCurrentUserConfigurationInternal(); + String logMsg = "handleConfigurationInternalChanged:" + + " oldConfiguration=" + mCurrentConfigurationInternal + + ", newConfiguration=" + currentUserConfig; + logTimeDetectorChange(logMsg); + mCurrentConfigurationInternal = currentUserConfig; - private synchronized void handleAutoTimeConfigChanged() { - boolean enabled = mEnvironment.isAutoTimeDetectionEnabled(); + boolean autoDetectionEnabled = + mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior(); // When automatic time detection is enabled we update the system clock instantly if we can. // Conversely, when automatic time detection is disabled we leave the clock as it is. - if (enabled) { + if (autoDetectionEnabled) { String reason = "Auto time zone detection config changed."; doAutoTimeDetection(reason); } else { @@ -323,14 +370,22 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } } + private void logTimeDetectorChange(@NonNull String logMsg) { + if (DBG) { + Slog.d(LOG_TAG, logMsg); + } + mTimeChangesLog.log(logMsg); + } + @Override public synchronized void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { ipw.println("TimeDetectorStrategy:"); ipw.increaseIndent(); // level 1 ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet); - ipw.println("mEnvironment.isAutoTimeDetectionEnabled()=" - + mEnvironment.isAutoTimeDetectionEnabled()); + ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal); + ipw.println("[Capabilities=" + mCurrentConfigurationInternal.capabilitiesAndConfig() + + "]"); long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n", Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); @@ -463,7 +518,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @GuardedBy("this") private void doAutoTimeDetection(@NonNull String detectionReason) { - if (!mEnvironment.isAutoTimeDetectionEnabled()) { + if (!mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) { // Avoid doing unnecessary work with this (race-prone) check. return; } @@ -692,7 +747,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { boolean isOriginAutomatic = isOriginAutomatic(origin); if (isOriginAutomatic) { - if (!mEnvironment.isAutoTimeDetectionEnabled()) { + if (!mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) { if (DBG) { Slog.d(LOG_TAG, "Auto time detection is not enabled." + " origin=" + originToString(origin) @@ -702,7 +757,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return false; } } else { - if (mEnvironment.isAutoTimeDetectionEnabled()) { + if (mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) { if (DBG) { Slog.d(LOG_TAG, "Auto time detection is enabled." + " origin=" + originToString(origin) diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index ef4e42a6a8d35..ef99d616cbeca 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -24,6 +24,7 @@ import static android.app.time.Capabilities.CAPABILITY_POSSESSED; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.Capabilities.CapabilityState; import android.app.time.TimeZoneCapabilities; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; @@ -205,7 +206,7 @@ public final class ConfigurationInternal { // network available or geolocation time zone detection is possible. boolean deviceHasAutoTimeZoneDetection = isAutoDetectionSupported(); - final int configureAutoDetectionEnabledCapability; + final @CapabilityState int configureAutoDetectionEnabledCapability; if (!deviceHasAutoTimeZoneDetection) { configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; } else if (!allowConfigDateTime) { @@ -219,7 +220,7 @@ public final class ConfigurationInternal { // Note: allowConfigDateTime does not restrict the ability to change location time zone // detection enabled. This is intentional as it has user privacy implications and so it // makes sense to leave this under a user's control. - final int configureGeolocationDetectionEnabledCapability; + final @CapabilityState int configureGeolocationDetectionEnabledCapability; if (!deviceHasLocationTimeZoneDetection) { configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) { @@ -234,7 +235,7 @@ public final class ConfigurationInternal { // the current logic above, this could lead to a situation where a device hardware does not // support auto detection, the device has been forced into "auto" mode by an admin and the // user is unable to disable auto detection. - final int suggestManualTimeZoneCapability; + final @CapabilityState int suggestManualTimeZoneCapability; if (!allowConfigDateTime) { suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED; } else if (getAutoDetectionEnabledBehavior()) { diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java index d4222e6e0b63a..8425d13e36b74 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java @@ -16,13 +16,18 @@ package com.android.server.timedetector; -import static com.google.common.truth.Truth.assertThat; +import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.Capabilities.CAPABILITY_POSSESSED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import android.app.time.Capabilities; import android.app.time.TimeCapabilities; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; -import android.os.UserHandle; import androidx.test.runner.AndroidJUnit4; @@ -32,25 +37,146 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ConfigurationInternalTest { + private static final int ARBITRARY_USER_ID = 99999; + + /** + * Tests when {@link ConfigurationInternal#isUserConfigAllowed()} and + * {@link ConfigurationInternal#isAutoDetectionSupported()} are both true. + */ @Test - public void capabilitiesAndConfig() { - int userId = 112233; - ConfigurationInternal configurationInternal = new ConfigurationInternal.Builder(userId) - .setAutoDetectionEnabled(true) + public void test_unrestricted() { + ConfigurationInternal + baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) + .setAutoDetectionSupported(true) + .setAutoDetectionEnabledSetting(true) .build(); + { + ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(true) + .build(); + assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); + assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior()); - TimeCapabilities timeCapabilities = new TimeCapabilities.Builder(UserHandle.of(userId)) - .setConfigureAutoTimeDetectionEnabledCapability(Capabilities.CAPABILITY_POSSESSED) - .setSuggestTimeManuallyCapability(Capabilities.CAPABILITY_POSSESSED) - .build(); - TimeConfiguration timeConfiguration = new TimeConfiguration.Builder() - .setAutoDetectionEnabled(true) - .build(); - TimeCapabilitiesAndConfig expected = - new TimeCapabilitiesAndConfig(timeCapabilities, timeConfiguration); + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOnConfig.capabilitiesAndConfig(); - assertThat(configurationInternal.capabilitiesAndConfig()).isEqualTo(expected); + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + } + + { + ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(false) + .build(); + assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); + assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOffConfig.capabilitiesAndConfig(); + + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + } } + /** Tests when {@link ConfigurationInternal#isUserConfigAllowed()} is false */ + @Test + public void test_restricted() { + ConfigurationInternal + baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) + .setUserConfigAllowed(false) + .setAutoDetectionSupported(true) + .setAutoDetectionEnabledSetting(true) + .build(); + { + ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(true) + .build(); + assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); + assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior()); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOnConfig.capabilitiesAndConfig(); + + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + } + + { + ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(false) + .build(); + assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); + assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOffConfig.capabilitiesAndConfig(); + + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + } + } + + /** Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is false. */ + @Test + public void test_autoDetectNotSupported() { + ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) + .setUserConfigAllowed(true) + .setAutoDetectionSupported(false) + .setAutoDetectionEnabledSetting(true) + .build(); + { + ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(true) + .build(); + assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); + assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior()); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOnConfig.capabilitiesAndConfig(); + + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + } + { + ConfigurationInternal + autoOffConfig = new ConfigurationInternal.Builder(baseConfig) + .setAutoDetectionEnabledSetting(false) + .build(); + assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); + assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); + + TimeCapabilitiesAndConfig capabilitiesAndConfig = autoOffConfig.capabilitiesAndConfig(); + + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability()); + + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java new file mode 100644 index 0000000000000..dd686f3c08401 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timedetector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.time.TimeCapabilities; +import android.app.time.TimeCapabilitiesAndConfig; +import android.app.time.TimeConfiguration; + +import com.android.server.timezonedetector.ConfigurationChangeListener; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */ +class FakeServiceConfigAccessor implements ServiceConfigAccessor { + + private final List mConfigurationInternalChangeListeners = + new ArrayList<>(); + private ConfigurationInternal mConfigurationInternal; + + @Override + public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) { + mConfigurationInternalChangeListeners.add(listener); + } + + @Override + public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) { + mConfigurationInternalChangeListeners.remove(listener); + } + + @Override + public ConfigurationInternal getCurrentUserConfigurationInternal() { + return mConfigurationInternal; + } + + @Override + public boolean updateConfiguration( + @UserIdInt int userID, @NonNull TimeConfiguration requestedChanges) { + assertNotNull(mConfigurationInternal); + assertNotNull(requestedChanges); + + // Simulate the real strategy's behavior: the new configuration will be updated to be the + // old configuration merged with the new if the user has the capability to up the settings. + // Then, if the configuration changed, the change listener is invoked. + TimeCapabilitiesAndConfig capabilitiesAndConfig = + mConfigurationInternal.capabilitiesAndConfig(); + TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + TimeConfiguration newConfiguration = + capabilities.tryApplyConfigChanges(configuration, requestedChanges); + if (newConfiguration == null) { + return false; + } + + if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) { + mConfigurationInternal = mConfigurationInternal.merge(newConfiguration); + + // Note: Unlike the real strategy, the listeners are invoked synchronously. + simulateConfigurationChangeForTests(); + } + return true; + } + + void initializeConfiguration(ConfigurationInternal configurationInternal) { + mConfigurationInternal = configurationInternal; + } + + void simulateConfigurationChangeForTests() { + for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) { + listener.onChange(); + } + } + + @Override + public ConfigurationInternal getConfigurationInternal(int userId) { + assertEquals("Multi-user testing not supported currently", + userId, mConfigurationInternal.getUserId()); + return mConfigurationInternal; + } + + @Override + public int systemClockUpdateThresholdMillis() { + failUnimplemented(); + return 0; + } + + @Override + public Instant autoTimeLowerBound() { + failUnimplemented(); + return null; + } + + @Override + public @TimeDetectorStrategy.Origin int[] getOriginPriorities() { + failUnimplemented(); + return new int[0]; + } + + @SuppressWarnings("UnusedReturnValue") + private static T failUnimplemented() { + fail("Unimplemented"); + return null; + } +} diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 4519890e72a15..ad2cb53a3fe86 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -41,6 +41,7 @@ import android.util.IndentingPrintWriter; import androidx.test.runner.AndroidJUnit4; +import com.android.server.timezonedetector.TestCallerIdentityInjector; import com.android.server.timezonedetector.TestHandler; import org.junit.After; @@ -54,12 +55,16 @@ import java.io.StringWriter; @RunWith(AndroidJUnit4.class) public class TimeDetectorServiceTest { + private static final int ARBITRARY_USER_ID = 9999; + private Context mMockContext; - private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy; private TimeDetectorService mTimeDetectorService; private HandlerThread mHandlerThread; private TestHandler mTestHandler; + private TestCallerIdentityInjector mTestCallerIdentityInjector; + private FakeServiceConfigAccessor mFakeServiceConfigAccessor; + private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy; @Before @@ -71,10 +76,15 @@ public class TimeDetectorServiceTest { mHandlerThread.start(); mTestHandler = new TestHandler(mHandlerThread.getLooper()); + mTestCallerIdentityInjector = new TestCallerIdentityInjector(); + mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID); + mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy(); + mFakeServiceConfigAccessor = new FakeServiceConfigAccessor(); mTimeDetectorService = new TimeDetectorService( - mMockContext, mTestHandler, mStubbedTimeDetectorStrategy); + mMockContext, mTestHandler, mFakeServiceConfigAccessor, + mStubbedTimeDetectorStrategy, mTestCallerIdentityInjector); } @After @@ -83,6 +93,37 @@ public class TimeDetectorServiceTest { mHandlerThread.join(); } + @Test(expected = SecurityException.class) + public void testGetCapabilitiesAndConfig_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + try { + mTimeDetectorService.getCapabilitiesAndConfig(); + fail("Expected SecurityException"); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testGetCapabilitiesAndConfig() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + ConfigurationInternal configuration = + createConfigurationInternal(true /* autoDetectionEnabled*/); + mFakeServiceConfigAccessor.initializeConfiguration(configuration); + + assertEquals(configuration.capabilitiesAndConfig(), + mTimeDetectorService.getCapabilitiesAndConfig()); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + @Test(expected = SecurityException.class) public void testSuggestTelephonyTime_withoutPermission() { doThrow(new SecurityException("Mock")) @@ -248,6 +289,14 @@ public class TimeDetectorServiceTest { mStubbedTimeDetectorStrategy.verifyDumpCalled(); } + private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) { + return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) + .setUserConfigAllowed(true) + .setAutoDetectionSupported(true) + .setAutoDetectionEnabledSetting(autoDetectionEnabled) + .build(); + } + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() { int slotIndex = 1234; TimestampedValue timeValue = new TimestampedValue<>(100L, 1_000_000L); @@ -291,7 +340,7 @@ public class TimeDetectorServiceTest { } @Override - public boolean suggestManualTime(ManualTimeSuggestion timeSuggestion) { + public boolean suggestManualTime(int userId, ManualTimeSuggestion timeSuggestion) { mLastManualSuggestion = timeSuggestion; return true; } @@ -311,11 +360,6 @@ public class TimeDetectorServiceTest { mLastExternalSuggestion = timeSuggestion; } - @Override - public ConfigurationInternal getConfigurationInternal(int userId) { - throw new UnsupportedOperationException(); - } - @Override public void dump(IndentingPrintWriter pw, String[] args) { mDumpCalled = true; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 2248ddb9e69ae..2d2b29887938b 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; @@ -52,6 +53,7 @@ import java.util.Objects; @RunWith(AndroidJUnit4.class) public class TimeDetectorStrategyImplTest { + private static final @UserIdInt int USER_ID = 9876; private static final Instant TIME_LOWER_BOUND = createUnixEpochTime(2009, 1, 1, 12, 0, 0); private static final TimestampedValue ARBITRARY_CLOCK_INITIALIZATION_INFO = @@ -70,41 +72,57 @@ public class TimeDetectorStrategyImplTest { private static final int ARBITRARY_SLOT_INDEX = 123456; - private Script mScript; + private static final ConfigurationInternal CONFIG_AUTO_DISABLED = + new ConfigurationInternal.Builder(USER_ID) + .setUserConfigAllowed(true) + .setAutoDetectionSupported(true) + .setAutoDetectionEnabledSetting(false) + .build(); + + private static final ConfigurationInternal CONFIG_AUTO_ENABLED = + new ConfigurationInternal.Builder(USER_ID) + .setUserConfigAllowed(true) + .setAutoDetectionSupported(true) + .setAutoDetectionEnabledSetting(true) + .build(); + + private FakeEnvironment mFakeEnvironment; @Before public void setUp() { - mScript = new Script(); + mFakeEnvironment = new FakeEnvironment(); + mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED); + mFakeEnvironment.initializeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO); } @Test public void testSuggestTelephonyTime_autoTimeEnabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; Instant testTime = ARBITRARY_TEST_TIME; TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); - mScript.simulateTimePassing() + script.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.simulateTimePassing() .simulateTelephonyTimeSuggestion(timeSuggestion); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); - mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) + script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); + script.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); } @Test public void testSuggestTelephonyTime_emptySuggestionIgnored() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, null); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion) + script.generateTelephonyTimeSuggestion(slotIndex, null); + script.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, null); } @@ -113,24 +131,24 @@ public class TimeDetectorStrategyImplTest { public void testSuggestTelephonyTime_systemClockThreshold() { final int systemClockUpdateThresholdMillis = 1000; final int clockIncrementMillis = 100; - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeThresholds(systemClockUpdateThresholdMillis) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; // Send the first time signal. It should be used. { TelephonyTimeSuggestion timeSuggestion1 = - mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME); + script.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME); // Increment the device clocks to simulate the passage of time. - mScript.simulateTimePassing(clockIncrementMillis); + script.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis1 = - mScript.calculateTimeInMillisForNow(timeSuggestion1.getUnixEpochTime()); + script.calculateTimeInMillisForNow(timeSuggestion1.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) + script.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); } @@ -139,9 +157,9 @@ public class TimeDetectorStrategyImplTest { // stored, but not used to set the system clock. { int underThresholdMillis = systemClockUpdateThresholdMillis - 1; - TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( - slotIndex, mScript.peekSystemClockMillis() + underThresholdMillis); - mScript.simulateTimePassing(clockIncrementMillis) + TelephonyTimeSuggestion timeSuggestion2 = script.generateTelephonyTimeSuggestion( + slotIndex, script.peekSystemClockMillis() + underThresholdMillis); + script.simulateTimePassing(clockIncrementMillis) .simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); @@ -149,15 +167,15 @@ public class TimeDetectorStrategyImplTest { // Now send another time signal, but one that is on the threshold and so should be used. { - TelephonyTimeSuggestion timeSuggestion3 = mScript.generateTelephonyTimeSuggestion( + TelephonyTimeSuggestion timeSuggestion3 = script.generateTelephonyTimeSuggestion( slotIndex, - mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); - mScript.simulateTimePassing(clockIncrementMillis); + script.peekSystemClockMillis() + systemClockUpdateThresholdMillis); + script.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis3 = - mScript.calculateTimeInMillisForNow(timeSuggestion3.getUnixEpochTime()); + script.calculateTimeInMillisForNow(timeSuggestion3.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) + script.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion3); } @@ -165,8 +183,8 @@ public class TimeDetectorStrategyImplTest { @Test public void testSuggestTelephonyTime_multipleSlotIndexsAndBucketing() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); // There are 2 slotIndexes in this test. slotIndex1 and slotIndex2 have different opinions // about the current time. slotIndex1 < slotIndex2 (which is important because the strategy @@ -179,63 +197,63 @@ public class TimeDetectorStrategyImplTest { // Make a suggestion with slotIndex2. { TelephonyTimeSuggestion slotIndex2TimeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); - mScript.simulateTimePassing(); + script.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); + script.simulateTimePassing(); - long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow( + long expectedSystemClockMillis = script.calculateTimeInMillisForNow( slotIndex2TimeSuggestion.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) + script.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestTelephonySuggestion(slotIndex1, null) .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } - mScript.simulateTimePassing(); + script.simulateTimePassing(); // Now make a different suggestion with slotIndex1. { TelephonyTimeSuggestion slotIndex1TimeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1Time); - mScript.simulateTimePassing(); + script.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1Time); + script.simulateTimePassing(); - long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow( + long expectedSystemClockMillis = script.calculateTimeInMillisForNow( slotIndex1TimeSuggestion.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion) + script.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestTelephonySuggestion(slotIndex1, slotIndex1TimeSuggestion); } - mScript.simulateTimePassing(); + script.simulateTimePassing(); // Make another suggestion with slotIndex2. It should be stored but not used because the // slotIndex1 suggestion will still "win". { TelephonyTimeSuggestion slotIndex2TimeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); - mScript.simulateTimePassing(); + script.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); + script.simulateTimePassing(); - mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) + script.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } // Let enough time pass that slotIndex1's suggestion should now be too old. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS); + script.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS); // Make another suggestion with slotIndex2. It should be used because the slotIndex1 // is in an older "bucket". { TelephonyTimeSuggestion slotIndex2TimeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); - mScript.simulateTimePassing(); + script.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time); + script.simulateTimePassing(); - long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow( + long expectedSystemClockMillis = script.calculateTimeInMillisForNow( slotIndex2TimeSuggestion.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) + script.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } @@ -243,13 +261,13 @@ public class TimeDetectorStrategyImplTest { @Test public void testSuggestTelephonyTime_autoTimeDisabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(false); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); int slotIndex = ARBITRARY_SLOT_INDEX; TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME); - mScript.simulateTimePassing() + script.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME); + script.simulateTimePassing() .simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); @@ -258,21 +276,21 @@ public class TimeDetectorStrategyImplTest { @Test public void testSuggestTelephonyTime_invalidNitzReferenceTimesIgnored() { final int systemClockUpdateThreshold = 2000; - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeThresholds(systemClockUpdateThreshold) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); Instant testTime = ARBITRARY_TEST_TIME; int slotIndex = ARBITRARY_SLOT_INDEX; TelephonyTimeSuggestion timeSuggestion1 = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.generateTelephonyTimeSuggestion(slotIndex, testTime); TimestampedValue unixEpochTime1 = timeSuggestion1.getUnixEpochTime(); // Initialize the strategy / device with a time set from a telephony suggestion. - mScript.simulateTimePassing(); - long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(unixEpochTime1); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) + script.simulateTimePassing(); + long expectedSystemClockMillis1 = script.calculateTimeInMillisForNow(unixEpochTime1); + script.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); @@ -288,7 +306,7 @@ public class TimeDetectorStrategyImplTest { referenceTimeBeforeLastSignalMillis, validUnixEpochTimeMillis); TelephonyTimeSuggestion timeSuggestion2 = createTelephonyTimeSuggestion(slotIndex, unixEpochTime2); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) + script.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); @@ -300,7 +318,7 @@ public class TimeDetectorStrategyImplTest { referenceTimeInFutureMillis, validUnixEpochTimeMillis); TelephonyTimeSuggestion timeSuggestion3 = createTelephonyTimeSuggestion(slotIndex, unixEpochTime3); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) + script.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); @@ -308,27 +326,27 @@ public class TimeDetectorStrategyImplTest { long validReferenceTimeMillis = unixEpochTime1.getReferenceTimeMillis() + 100; TimestampedValue unixEpochTime4 = new TimestampedValue<>( validReferenceTimeMillis, validUnixEpochTimeMillis); - long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(unixEpochTime4); + long expectedSystemClockMillis4 = script.calculateTimeInMillisForNow(unixEpochTime4); TelephonyTimeSuggestion timeSuggestion4 = createTelephonyTimeSuggestion(slotIndex, unixEpochTime4); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion4) + script.simulateTelephonyTimeSuggestion(timeSuggestion4) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion4); } @Test public void telephonyTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1)); TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion( + script.generateTelephonyTimeSuggestion( slotIndex, suggestedTime); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion) + script.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, null); } @@ -337,325 +355,326 @@ public class TimeDetectorStrategyImplTest { public void testSuggestTelephonyTime_timeDetectionToggled() { final int clockIncrementMillis = 100; final int systemClockUpdateThreshold = 2000; - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeThresholds(systemClockUpdateThreshold) - .pokeAutoTimeDetectionEnabled(false); + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); int slotIndex = ARBITRARY_SLOT_INDEX; Instant testTime = ARBITRARY_TEST_TIME; TelephonyTimeSuggestion timeSuggestion1 = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.generateTelephonyTimeSuggestion(slotIndex, testTime); TimestampedValue unixEpochTime1 = timeSuggestion1.getUnixEpochTime(); // Simulate time passing. - mScript.simulateTimePassing(clockIncrementMillis); + script.simulateTimePassing(clockIncrementMillis); // Simulate the time signal being received. It should not be used because auto time // detection is off but it should be recorded. - mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) + script.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Simulate more time passing. - mScript.simulateTimePassing(clockIncrementMillis); + script.simulateTimePassing(clockIncrementMillis); - long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(unixEpochTime1); + long expectedSystemClockMillis1 = script.calculateTimeInMillisForNow(unixEpochTime1); // Turn on auto time detection. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Turn off auto time detection. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Receive another valid time signal. // It should be on the threshold and accounting for the clock increments. - TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( - slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); + TelephonyTimeSuggestion timeSuggestion2 = script.generateTelephonyTimeSuggestion( + slotIndex, script.peekSystemClockMillis() + systemClockUpdateThreshold); // Simulate more time passing. - mScript.simulateTimePassing(clockIncrementMillis); + script.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis2 = - mScript.calculateTimeInMillisForNow(timeSuggestion2.getUnixEpochTime()); + script.calculateTimeInMillisForNow(timeSuggestion2.getUnixEpochTime()); // The new time, though valid, should not be set in the system clock because auto time is // disabled. - mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) + script.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); // Turn on auto time detection. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2) .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); } @Test public void testSuggestTelephonyTime_maxSuggestionAge() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; Instant testTime = ARBITRARY_TEST_TIME; TelephonyTimeSuggestion telephonySuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.generateTelephonyTimeSuggestion(slotIndex, testTime); - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(telephonySuggestion.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(telephonySuggestion) + script.calculateTimeInMillisForNow(telephonySuggestion.getUnixEpochTime()); + script.simulateTelephonyTimeSuggestion(telephonySuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedSystemClockMillis /* expectedNetworkBroadcast */) .assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); // Look inside and check what the strategy considers the current best telephony suggestion. - assertEquals(telephonySuggestion, mScript.peekBestTelephonySuggestion()); + assertEquals(telephonySuggestion, script.peekBestTelephonySuggestion()); // Simulate time passing, long enough that telephonySuggestion is now too old. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS); + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS); // Look inside and check what the strategy considers the current best telephony suggestion. // It should still be the, it's just no longer used. - assertNull(mScript.peekBestTelephonySuggestion()); - mScript.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); + assertNull(script.peekBestTelephonySuggestion()); + script.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); } @Test public void testSuggestManualTime_autoTimeDisabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(false); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); ManualTimeSuggestion timeSuggestion = - mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); - mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */) + script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); + script.simulateManualTimeSuggestion(USER_ID, timeSuggestion, true /* expectedResult */) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test public void testSuggestManualTime_retainsAutoSignal() { // Configure the start state. - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); int slotIndex = ARBITRARY_SLOT_INDEX; // Simulate a telephony suggestion. Instant testTime = ARBITRARY_TEST_TIME; TelephonyTimeSuggestion telephonyTimeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.generateTelephonyTimeSuggestion(slotIndex, testTime); // Simulate the passage of time. - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime()); - mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) + script.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime()); + script.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(); + script.simulateTimePassing(); // Switch to manual. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(); + script.simulateTimePassing(); // Simulate a manual suggestion 1 day different from the auto suggestion. Instant manualTime = testTime.plus(Duration.ofDays(1)); ManualTimeSuggestion manualTimeSuggestion = - mScript.generateManualTimeSuggestion(manualTime); - mScript.simulateTimePassing(); + script.generateManualTimeSuggestion(manualTime); + script.simulateTimePassing(); long expectedManualClockMillis = - mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUnixEpochTime()); - mScript.simulateManualTimeSuggestion(manualTimeSuggestion, true /* expectedResult */) + script.calculateTimeInMillisForNow(manualTimeSuggestion.getUnixEpochTime()); + script.simulateManualTimeSuggestion( + USER_ID, manualTimeSuggestion, true /* expectedResult */) .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis) .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(); + script.simulateTimePassing(); // Switch back to auto. - mScript.simulateAutoTimeDetectionToggle(); + script.simulateAutoTimeDetectionToggle(); expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime()); - mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) + script.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime()); + script.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Switch back to manual - nothing should happen to the clock. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); } @Test public void manualTimeSuggestion_isIgnored_whenAutoTimeEnabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); ManualTimeSuggestion timeSuggestion = - mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing() - .simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */) + script.simulateTimePassing() + .simulateManualTimeSuggestion(USER_ID, timeSuggestion, false /* expectedResult */) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void manualTimeSuggestion_ignoresTimeLowerBound() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(false); + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1)); ManualTimeSuggestion timeSuggestion = - mScript.generateManualTimeSuggestion(suggestedTime); + script.generateManualTimeSuggestion(suggestedTime); - mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */) + script.simulateManualTimeSuggestion(USER_ID, timeSuggestion, true /* expectedResult */) .verifySystemClockWasSetAndResetCallTracking(suggestedTime.toEpochMilli()); } @Test public void testSuggestNetworkTime_autoTimeEnabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_NETWORK) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); NetworkTimeSuggestion timeSuggestion = - mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); - mScript.simulateNetworkTimeSuggestion(timeSuggestion) + script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); + script.simulateNetworkTimeSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test public void testSuggestNetworkTime_autoTimeDisabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_NETWORK) - .pokeAutoTimeDetectionEnabled(false); + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); NetworkTimeSuggestion timeSuggestion = - mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing() + script.simulateTimePassing() .simulateNetworkTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void networkTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_NETWORK) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1)); - NetworkTimeSuggestion timeSuggestion = mScript - .generateNetworkTimeSuggestion(suggestedTime); + NetworkTimeSuggestion timeSuggestion = + script.generateNetworkTimeSuggestion(suggestedTime); - mScript.simulateNetworkTimeSuggestion(timeSuggestion) + script.simulateNetworkTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestNetworkSuggestion(null); } @Test public void testSuggestGnssTime_autoTimeEnabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_GNSS) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); GnssTimeSuggestion timeSuggestion = - mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); - mScript.simulateGnssTimeSuggestion(timeSuggestion) + script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); + script.simulateGnssTimeSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test public void testSuggestGnssTime_autoTimeDisabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_GNSS) - .pokeAutoTimeDetectionEnabled(false); + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); GnssTimeSuggestion timeSuggestion = - mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing() + script.simulateTimePassing() .simulateGnssTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void testSuggestExternalTime_autoTimeEnabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_EXTERNAL) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); ExternalTimeSuggestion timeSuggestion = - mScript.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing(); + script.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); - mScript.simulateExternalTimeSuggestion(timeSuggestion) + script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); + script.simulateExternalTimeSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test public void testSuggestExternalTime_autoTimeDisabled() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_EXTERNAL) - .pokeAutoTimeDetectionEnabled(false); + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED); ExternalTimeSuggestion timeSuggestion = - mScript.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateTimePassing() + script.simulateTimePassing() .simulateExternalTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void externalTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + Script script = new Script() .pokeAutoOriginPriorities(ORIGIN_EXTERNAL) - .pokeAutoTimeDetectionEnabled(true); + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED); Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1)); - ExternalTimeSuggestion timeSuggestion = mScript - .generateExternalTimeSuggestion(suggestedTime); + ExternalTimeSuggestion timeSuggestion = + script.generateExternalTimeSuggestion(suggestedTime); - mScript.simulateExternalTimeSuggestion(timeSuggestion) + script.simulateExternalTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestExternalSuggestion(null); } @Test public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK); // Three obviously different times that could not be mistaken for each other. @@ -668,99 +687,99 @@ public class TimeDetectorStrategyImplTest { // A network suggestion is made. It should be used because there is no telephony suggestion. NetworkTimeSuggestion networkTimeSuggestion1 = - mScript.generateNetworkTimeSuggestion(networkTime1); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateNetworkTimeSuggestion(networkTime1); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( networkTimeSuggestion1.getUnixEpochTime())); // Check internal state. - mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null) + script.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null) .assertLatestNetworkSuggestion(networkTimeSuggestion1); - assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertNull("No telephony suggestions were made:", mScript.peekBestTelephonySuggestion()); + assertEquals(networkTimeSuggestion1, script.peekLatestValidNetworkSuggestion()); + assertNull("No telephony suggestions were made:", script.peekBestTelephonySuggestion()); // Simulate a little time passing. - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.simulateTimePassing(smallTimeIncrementMillis) .verifySystemClockWasNotSetAndResetCallTracking(); // Now a telephony suggestion is made. Telephony suggestions are prioritized over network // suggestions so it should "win". TelephonyTimeSuggestion telephonyTimeSuggestion = - mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( telephonyTimeSuggestion.getUnixEpochTime())); // Check internal state. - mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) + script.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion1); - assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); + assertEquals(networkTimeSuggestion1, script.peekLatestValidNetworkSuggestion()); + assertEquals(telephonyTimeSuggestion, script.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) .verifySystemClockWasNotSetAndResetCallTracking(); // Now another network suggestion is made. Telephony suggestions are prioritized over // network suggestions so the latest telephony suggestion should still "win". NetworkTimeSuggestion networkTimeSuggestion2 = - mScript.generateNetworkTimeSuggestion(networkTime2); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateNetworkTimeSuggestion(networkTime2); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) + script.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); - assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); + assertEquals(networkTimeSuggestion2, script.peekLatestValidNetworkSuggestion()); + assertEquals(telephonyTimeSuggestion, script.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to // be used but networkTimeSuggestion2 is not. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle // to re-run the detection logic. This may change in future but until then we rely on a // steady stream of suggestions to re-evaluate. - mScript.verifySystemClockWasNotSetAndResetCallTracking(); + script.verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) + script.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); - assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(networkTimeSuggestion2, script.peekLatestValidNetworkSuggestion()); assertNull( "Telephony suggestion should be expired:", - mScript.peekBestTelephonySuggestion()); + script.peekBestTelephonySuggestion()); // Toggle auto-time off and on to force the detection logic to run. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .simulateTimePassing(smallTimeIncrementMillis) .simulateAutoTimeDetectionToggle(); // Verify the latest network time now wins. - mScript.verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUnixEpochTime())); + script.verifySystemClockWasSetAndResetCallTracking( + script.calculateTimeInMillisForNow(networkTimeSuggestion2.getUnixEpochTime())); // Check internal state. - mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) + script.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); - assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(networkTimeSuggestion2, script.peekLatestValidNetworkSuggestion()); assertNull( "Telephony suggestion should still be expired:", - mScript.peekBestTelephonySuggestion()); + script.peekBestTelephonySuggestion()); } @Test public void highPrioritySuggestionsBeatLowerPrioritySuggestions_networkGnssOrigins() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_NETWORK, ORIGIN_GNSS); // Three obviously different times that could not be mistaken for each other. @@ -773,99 +792,99 @@ public class TimeDetectorStrategyImplTest { // A gnss suggestion is made. It should be used because there is no network suggestion. GnssTimeSuggestion gnssTimeSuggestion1 = - mScript.generateGnssTimeSuggestion(gnssTime1); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateGnssTimeSuggestion(gnssTime1); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateGnssTimeSuggestion(gnssTimeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( gnssTimeSuggestion1.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(null) + script.assertLatestNetworkSuggestion(null) .assertLatestGnssSuggestion(gnssTimeSuggestion1); - assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion()); - assertNull("No network suggestions were made:", mScript.peekLatestValidNetworkSuggestion()); + assertEquals(gnssTimeSuggestion1, script.peekLatestValidGnssSuggestion()); + assertNull("No network suggestions were made:", script.peekLatestValidNetworkSuggestion()); // Simulate a little time passing. - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.simulateTimePassing(smallTimeIncrementMillis) .verifySystemClockWasNotSetAndResetCallTracking(); // Now a network suggestion is made. Network suggestions are prioritized over gnss // suggestions so it should "win". NetworkTimeSuggestion networkTimeSuggestion = - mScript.generateNetworkTimeSuggestion(networkTime); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateNetworkTimeSuggestion(networkTime); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( networkTimeSuggestion.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestGnssSuggestion(gnssTimeSuggestion1); - assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion()); - assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(gnssTimeSuggestion1, script.peekLatestValidGnssSuggestion()); + assertEquals(networkTimeSuggestion, script.peekLatestValidNetworkSuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) .verifySystemClockWasNotSetAndResetCallTracking(); // Now another gnss suggestion is made. Network suggestions are prioritized over // gnss suggestions so the latest network suggestion should still "win". GnssTimeSuggestion gnssTimeSuggestion2 = - mScript.generateGnssTimeSuggestion(gnssTime2); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateGnssTimeSuggestion(gnssTime2); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateGnssTimeSuggestion(gnssTimeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestGnssSuggestion(gnssTimeSuggestion2); - assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion()); - assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(gnssTimeSuggestion2, script.peekLatestValidGnssSuggestion()); + assertEquals(networkTimeSuggestion, script.peekLatestValidNetworkSuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to // be used but networkTimeSuggestion2 is not. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle // to re-run the detection logic. This may change in future but until then we rely on a // steady stream of suggestions to re-evaluate. - mScript.verifySystemClockWasNotSetAndResetCallTracking(); + script.verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestGnssSuggestion(gnssTimeSuggestion2); - assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion()); + assertEquals(gnssTimeSuggestion2, script.peekLatestValidGnssSuggestion()); assertNull( "Network suggestion should be expired:", - mScript.peekLatestValidNetworkSuggestion()); + script.peekLatestValidNetworkSuggestion()); // Toggle auto-time off and on to force the detection logic to run. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .simulateTimePassing(smallTimeIncrementMillis) .simulateAutoTimeDetectionToggle(); // Verify the latest gnss time now wins. - mScript.verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(gnssTimeSuggestion2.getUnixEpochTime())); + script.verifySystemClockWasSetAndResetCallTracking( + script.calculateTimeInMillisForNow(gnssTimeSuggestion2.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestGnssSuggestion(gnssTimeSuggestion2); - assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion()); + assertEquals(gnssTimeSuggestion2, script.peekLatestValidGnssSuggestion()); assertNull( "Network suggestion should still be expired:", - mScript.peekLatestValidNetworkSuggestion()); + script.peekLatestValidNetworkSuggestion()); } @Test public void highPrioritySuggestionsBeatLowerPrioritySuggestions_networkExternalOrigins() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_NETWORK, ORIGIN_EXTERNAL); // Three obviously different times that could not be mistaken for each other. @@ -878,99 +897,99 @@ public class TimeDetectorStrategyImplTest { // A external suggestion is made. It should be used because there is no network suggestion. ExternalTimeSuggestion externalTimeSuggestion1 = - mScript.generateExternalTimeSuggestion(externalTime1); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateExternalTimeSuggestion(externalTime1); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateExternalTimeSuggestion(externalTimeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( externalTimeSuggestion1.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(null) + script.assertLatestNetworkSuggestion(null) .assertLatestExternalSuggestion(externalTimeSuggestion1); - assertEquals(externalTimeSuggestion1, mScript.peekLatestValidExternalSuggestion()); - assertNull("No network suggestions were made:", mScript.peekLatestValidNetworkSuggestion()); + assertEquals(externalTimeSuggestion1, script.peekLatestValidExternalSuggestion()); + assertNull("No network suggestions were made:", script.peekLatestValidNetworkSuggestion()); // Simulate a little time passing. - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.simulateTimePassing(smallTimeIncrementMillis) .verifySystemClockWasNotSetAndResetCallTracking(); // Now a network suggestion is made. Network suggestions are prioritized over external // suggestions so it should "win". NetworkTimeSuggestion networkTimeSuggestion = - mScript.generateNetworkTimeSuggestion(networkTime); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateNetworkTimeSuggestion(networkTime); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow( + script.calculateTimeInMillisForNow( networkTimeSuggestion.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestExternalSuggestion(externalTimeSuggestion1); - assertEquals(externalTimeSuggestion1, mScript.peekLatestValidExternalSuggestion()); - assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(externalTimeSuggestion1, script.peekLatestValidExternalSuggestion()); + assertEquals(networkTimeSuggestion, script.peekLatestValidNetworkSuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2) .verifySystemClockWasNotSetAndResetCallTracking(); // Now another external suggestion is made. Network suggestions are prioritized over // external suggestions so the latest network suggestion should still "win". ExternalTimeSuggestion externalTimeSuggestion2 = - mScript.generateExternalTimeSuggestion(externalTime2); - mScript.simulateTimePassing(smallTimeIncrementMillis) + script.generateExternalTimeSuggestion(externalTime2); + script.simulateTimePassing(smallTimeIncrementMillis) .simulateExternalTimeSuggestion(externalTimeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestExternalSuggestion(externalTimeSuggestion2); - assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion()); - assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(externalTimeSuggestion2, script.peekLatestValidExternalSuggestion()); + assertEquals(networkTimeSuggestion, script.peekLatestValidNetworkSuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". This should mean that networkTimeSuggestion is now too old to // be used but externalTimeSuggestion2 is not. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); + script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2); // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle // to re-run the detection logic. This may change in future but until then we rely on a // steady stream of suggestions to re-evaluate. - mScript.verifySystemClockWasNotSetAndResetCallTracking(); + script.verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestExternalSuggestion(externalTimeSuggestion2); - assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion()); + assertEquals(externalTimeSuggestion2, script.peekLatestValidExternalSuggestion()); assertNull( "Network suggestion should be expired:", - mScript.peekLatestValidNetworkSuggestion()); + script.peekLatestValidNetworkSuggestion()); // Toggle auto-time off and on to force the detection logic to run. - mScript.simulateAutoTimeDetectionToggle() + script.simulateAutoTimeDetectionToggle() .simulateTimePassing(smallTimeIncrementMillis) .simulateAutoTimeDetectionToggle(); // Verify the latest external time now wins. - mScript.verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(externalTimeSuggestion2.getUnixEpochTime())); + script.verifySystemClockWasSetAndResetCallTracking( + script.calculateTimeInMillisForNow(externalTimeSuggestion2.getUnixEpochTime())); // Check internal state. - mScript.assertLatestNetworkSuggestion(networkTimeSuggestion) + script.assertLatestNetworkSuggestion(networkTimeSuggestion) .assertLatestExternalSuggestion(externalTimeSuggestion2); - assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion()); + assertEquals(externalTimeSuggestion2, script.peekLatestValidExternalSuggestion()); assertNull( "Network suggestion should still be expired:", - mScript.peekLatestValidNetworkSuggestion()); + script.peekLatestValidNetworkSuggestion()); } @Test public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_lowerPriorityComesFirst() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_EXTERNAL, ORIGIN_GNSS); @@ -980,15 +999,15 @@ public class TimeDetectorStrategyImplTest { Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60)); NetworkTimeSuggestion networkTimeSuggestion = - mScript.generateNetworkTimeSuggestion(networkTime); + script.generateNetworkTimeSuggestion(networkTime); ExternalTimeSuggestion externalTimeSuggestion = - mScript.generateExternalTimeSuggestion(externalTime); + script.generateExternalTimeSuggestion(externalTime); GnssTimeSuggestion gnssTimeSuggestion = - mScript.generateGnssTimeSuggestion(gnssTime); + script.generateGnssTimeSuggestion(gnssTime); TelephonyTimeSuggestion telephonyTimeSuggestion = - mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); + script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); - mScript.simulateNetworkTimeSuggestion(networkTimeSuggestion) + script.simulateNetworkTimeSuggestion(networkTimeSuggestion) .simulateExternalTimeSuggestion(externalTimeSuggestion) .simulateGnssTimeSuggestion(gnssTimeSuggestion) .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) @@ -1001,8 +1020,8 @@ public class TimeDetectorStrategyImplTest { @Test public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_higherPriorityComesFirst() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_EXTERNAL, ORIGIN_GNSS); @@ -1012,15 +1031,15 @@ public class TimeDetectorStrategyImplTest { Instant gnssTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60)); NetworkTimeSuggestion networkTimeSuggestion = - mScript.generateNetworkTimeSuggestion(networkTime); + script.generateNetworkTimeSuggestion(networkTime); TelephonyTimeSuggestion telephonyTimeSuggestion = - mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); + script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime); GnssTimeSuggestion gnssTimeSuggestion = - mScript.generateGnssTimeSuggestion(gnssTime); + script.generateGnssTimeSuggestion(gnssTime); ExternalTimeSuggestion externalTimeSuggestion = - mScript.generateExternalTimeSuggestion(externalTime); + script.generateExternalTimeSuggestion(externalTime); - mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) + script.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .simulateNetworkTimeSuggestion(networkTimeSuggestion) .simulateGnssTimeSuggestion(gnssTimeSuggestion) .simulateExternalTimeSuggestion(externalTimeSuggestion) @@ -1033,144 +1052,144 @@ public class TimeDetectorStrategyImplTest { @Test public void whenHighestPrioritySuggestionIsNotAvailable_fallbacksToNext() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK); NetworkTimeSuggestion timeSuggestion = - mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateNetworkTimeSuggestion(timeSuggestion) + script.simulateNetworkTimeSuggestion(timeSuggestion) .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli()); } @Test public void whenHigherPrioritySuggestionsAreNotAvailable_fallbacksToNext() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_EXTERNAL, ORIGIN_GNSS); GnssTimeSuggestion timeSuggestion = - mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateGnssTimeSuggestion(timeSuggestion) + script.simulateGnssTimeSuggestion(timeSuggestion) .assertLatestGnssSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli()); } @Test public void suggestionsFromTelephonyOriginNotInPriorityList_areIgnored() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_NETWORK); int slotIndex = ARBITRARY_SLOT_INDEX; Instant testTime = ARBITRARY_TEST_TIME; TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, testTime); + script.generateTelephonyTimeSuggestion(slotIndex, testTime); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion) + script.simulateTelephonyTimeSuggestion(timeSuggestion) .assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void suggestionsFromNetworkOriginNotInPriorityList_areIgnored() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY); - NetworkTimeSuggestion timeSuggestion = mScript.generateNetworkTimeSuggestion( + NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion( ARBITRARY_TEST_TIME); - mScript.simulateNetworkTimeSuggestion(timeSuggestion) + script.simulateNetworkTimeSuggestion(timeSuggestion) .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void suggestionsFromGnssOriginNotInPriorityList_areIgnored() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY); - GnssTimeSuggestion timeSuggestion = mScript.generateGnssTimeSuggestion( + GnssTimeSuggestion timeSuggestion = script.generateGnssTimeSuggestion( ARBITRARY_TEST_TIME); - mScript.simulateGnssTimeSuggestion(timeSuggestion) + script.simulateGnssTimeSuggestion(timeSuggestion) .assertLatestGnssSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void suggestionsFromExternalOriginNotInPriorityList_areIgnored() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY); - ExternalTimeSuggestion timeSuggestion = mScript.generateExternalTimeSuggestion( + ExternalTimeSuggestion timeSuggestion = script.generateExternalTimeSuggestion( ARBITRARY_TEST_TIME); - mScript.simulateExternalTimeSuggestion(timeSuggestion) + script.simulateExternalTimeSuggestion(timeSuggestion) .assertLatestExternalSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void autoOriginPrioritiesList_doesNotAffectManualSuggestion() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(false) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY); ManualTimeSuggestion timeSuggestion = - mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); + script.generateManualTimeSuggestion(ARBITRARY_TEST_TIME); - mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */) + script.simulateManualTimeSuggestion(USER_ID, timeSuggestion, true /* expectedResult */) .verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli()); } @Test public void manualY2038SuggestionsAreRejectedOnAffectedDevices() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(false) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY) .pokeDeviceHasY2038Issues(true); Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L); - ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(y2038IssueTime); - mScript.simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */) + ManualTimeSuggestion timeSuggestion = script.generateManualTimeSuggestion(y2038IssueTime); + script.simulateManualTimeSuggestion(USER_ID, timeSuggestion, false /* expectedResult */) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void telephonyY2038SuggestionsAreRejectedOnAffectedDevices() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY) .pokeDeviceHasY2038Issues(true); final int slotIndex = 0; Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L); TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion) + script.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime); + script.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test public void telephonyY2038SuggestionsAreNotRejectedOnUnaffectedDevices() { - mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) - .pokeAutoTimeDetectionEnabled(true) + Script script = new Script() + .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) .pokeAutoOriginPriorities(ORIGIN_TELEPHONY) .pokeDeviceHasY2038Issues(false); final int slotIndex = 0; Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L); TelephonyTimeSuggestion timeSuggestion = - mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime); - mScript.simulateTelephonyTimeSuggestion(timeSuggestion) + script.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime); + script.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking(y2038IssueTime.toEpochMilli()); } @@ -1179,21 +1198,31 @@ public class TimeDetectorStrategyImplTest { * changes and behaving like the real thing should, it also asserts preconditions. */ private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment { - private boolean mAutoTimeDetectionEnabled; + + private ConfigurationInternal mConfigurationInternal; private boolean mWakeLockAcquired; private long mElapsedRealtimeMillis; private long mSystemClockMillis; private int mSystemClockUpdateThresholdMillis = 2000; private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY; - private ConfigurationChangeListener mConfigChangeListener; + private ConfigurationChangeListener mConfigurationInternalChangeListener; private boolean mDeviceHas2038Issues = false; // Tracking operations. private boolean mSystemClockWasSet; + void initializeConfig(ConfigurationInternal configurationInternal) { + mConfigurationInternal = configurationInternal; + } + + public void initializeFakeClocks(TimestampedValue timeInfo) { + pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis()); + pokeSystemClockMillis(timeInfo.getValue().toEpochMilli()); + } + @Override - public void setConfigChangeListener(ConfigurationChangeListener listener) { - mConfigChangeListener = Objects.requireNonNull(listener); + public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) { + mConfigurationInternalChangeListener = Objects.requireNonNull(listener); } @Override @@ -1201,11 +1230,6 @@ public class TimeDetectorStrategyImplTest { return mSystemClockUpdateThresholdMillis; } - @Override - public boolean isAutoTimeDetectionEnabled() { - return mAutoTimeDetectionEnabled; - } - @Override public Instant autoTimeLowerBound() { return TIME_LOWER_BOUND; @@ -1217,8 +1241,8 @@ public class TimeDetectorStrategyImplTest { } @Override - public ConfigurationInternal configurationInternal(int userId) { - throw new UnsupportedOperationException(); + public ConfigurationInternal getCurrentUserConfigurationInternal() { + return mConfigurationInternal; } @Override @@ -1263,6 +1287,11 @@ public class TimeDetectorStrategyImplTest { // Methods below are for managing the fake's behavior. + void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) { + mConfigurationInternal = configurationInternal; + mConfigurationInternalChangeListener.onChange(); + } + void pokeSystemClockUpdateThreshold(int thresholdMillis) { mSystemClockUpdateThresholdMillis = thresholdMillis; } @@ -1275,10 +1304,6 @@ public class TimeDetectorStrategyImplTest { mSystemClockMillis = systemClockMillis; } - void pokeAutoTimeDetectionEnabled(boolean enabled) { - mAutoTimeDetectionEnabled = enabled; - } - void pokeAutoOriginPriorities(@Origin int[] autoOriginPriorities) { mAutoOriginPriorities = autoOriginPriorities; } @@ -1296,11 +1321,6 @@ public class TimeDetectorStrategyImplTest { mSystemClockMillis += incrementMillis; } - void simulateAutoTimeZoneDetectionToggle() { - mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled; - mConfigChangeListener.onChange(); - } - void verifySystemClockNotSet() { assertFalse( String.format("System clock was manipulated and set to %s(=%s)", @@ -1328,7 +1348,6 @@ public class TimeDetectorStrategyImplTest { */ private class Script { - private final FakeEnvironment mFakeEnvironment; private final TimeDetectorStrategyImpl mTimeDetectorStrategy; Script() { @@ -1336,17 +1355,6 @@ public class TimeDetectorStrategyImplTest { mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment); } - Script pokeAutoTimeDetectionEnabled(boolean enabled) { - mFakeEnvironment.pokeAutoTimeDetectionEnabled(enabled); - return this; - } - - Script pokeFakeClocks(TimestampedValue timeInfo) { - mFakeEnvironment.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis()); - mFakeEnvironment.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli()); - return this; - } - Script pokeThresholds(int systemClockUpdateThreshold) { mFakeEnvironment.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold); return this; @@ -1370,20 +1378,29 @@ public class TimeDetectorStrategyImplTest { return mFakeEnvironment.peekSystemClockMillis(); } + /** + * Simulates the user / user's configuration changing. + */ + Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) { + mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal); + return this; + } + Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) { mTimeDetectorStrategy.suggestTelephonyTime(timeSuggestion); return this; } Script simulateManualTimeSuggestion( - ManualTimeSuggestion timeSuggestion, boolean expectedResult) { + @UserIdInt int userId, ManualTimeSuggestion timeSuggestion, + boolean expectedResult) { String errorMessage = expectedResult ? "Manual time suggestion was ignored, but expected to be accepted." : "Manual time suggestion was accepted, but expected to be ignored."; assertEquals( errorMessage, expectedResult, - mTimeDetectorStrategy.suggestManualTime(timeSuggestion)); + mTimeDetectorStrategy.suggestManualTime(userId, timeSuggestion)); return this; } @@ -1403,7 +1420,15 @@ public class TimeDetectorStrategyImplTest { } Script simulateAutoTimeDetectionToggle() { - mFakeEnvironment.simulateAutoTimeZoneDetectionToggle(); + ConfigurationInternal configurationInternal = + mFakeEnvironment.getCurrentUserConfigurationInternal(); + boolean autoDetectionEnabledSetting = + !configurationInternal.getAutoDetectionEnabledSetting(); + ConfigurationInternal newConfigurationInternal = + new ConfigurationInternal.Builder(configurationInternal) + .setAutoDetectionEnabledSetting(autoDetectionEnabledSetting) + .build(); + mFakeEnvironment.simulateConfigurationInternalChange(newConfigurationInternal); return this; }