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; }