Merge "Change TimeDetectorStrategy.Environment API"

This commit is contained in:
Neil Fuller
2022-05-31 18:44:44 +00:00
committed by Android (Google) Code Review
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;