Change TimeDetectorStrategy.Environment API

Switch TimeDetectorStrategy.Environment to returning
ConfigurationInternal in place of an individual property.

This is in preparation for implementing the methods to update
time detector configuration and support listeners. More properties will
be added to ConfigurationInternal / removed from Environment in later
commits.

This mirrors the situation in com.android.server.timezonedetector and is
part of a sequence of changes to make com.android.server.timedetector
follow the patterns for configuration used there.

Bug: 172891783
Bug: 229740080
Test: atest com.android.server.timedetector android.app.time
Change-Id: I0ffe467ede4b6b0279b284108a368c5265a323a0
This commit is contained in:
Neil Fuller
2022-05-26 18:23:19 +01:00
parent a5db2b6c3a
commit 8247153b48
15 changed files with 1473 additions and 647 deletions

View File

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

View File

@@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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);

View File

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

View File

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

View File

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

View File

@@ -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.
*
*
* <p>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.
*
* <p>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);
}

View File

@@ -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<ConfigurationChangeListener> 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.
*
* <p>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

View File

@@ -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");
}
}

View File

@@ -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.
/**

View File

@@ -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.
*
* <p>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.
* <p>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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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)

View File

@@ -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()) {

View File

@@ -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());
}
}
}

View File

@@ -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<ConfigurationChangeListener> 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> T failUnimplemented() {
fail("Unimplemented");
return null;
}
}

View File

@@ -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<Long> 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;