Merge "Change TimeDetectorStrategy.Environment API"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user