summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Neil Fuller <nfuller@google.com> 2020-08-24 19:47:49 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-08-24 19:47:49 +0000
commit1bb318e04e6d9b9c3957e01e63ee8a60e98df4d6 (patch)
tree476a293dc6fb3aa8d2c6da32da8d46b7ce69ea96
parentee43e2f0581425d1a274fddb545e07da4aebfa95 (diff)
parenta223581eee79357f71254dd41099f2a54f9cef87 (diff)
Merge "Implement user-scoped geolocation configuration"
-rw-r--r--core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl2
-rw-r--r--core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl6
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneCapabilities.java109
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneConfiguration.java148
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java42
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java21
-rw-r--r--core/java/android/provider/Settings.java11
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java84
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java53
-rw-r--r--services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java62
-rw-r--r--services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java26
-rw-r--r--services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java288
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java213
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java6
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java37
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java184
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java88
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java286
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java182
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java83
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java95
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java681
24 files changed, 1634 insertions, 1139 deletions
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
index af77fe05b902..6d0fe72b9de1 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
@@ -20,5 +20,5 @@ import android.app.timezonedetector.TimeZoneConfiguration;
/** {@hide} */
oneway interface ITimeZoneConfigurationListener {
- void onChange(in TimeZoneConfiguration configuration);
+ void onChange();
} \ No newline at end of file
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 6e93af6a053b..4f7e1f62928a 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -32,17 +32,15 @@ import android.app.timezonedetector.TimeZoneConfiguration;
* this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
* for more complete documentation.
*
- *
* {@hide}
*/
interface ITimeZoneDetectorService {
TimeZoneCapabilities getCapabilities();
-
- TimeZoneConfiguration getConfiguration();
- boolean updateConfiguration(in TimeZoneConfiguration configuration);
void addConfigurationListener(ITimeZoneConfigurationListener listener);
void removeConfigurationListener(ITimeZoneConfigurationListener listener);
+ boolean updateConfiguration(in TimeZoneConfiguration configuration);
+
boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index cc0af3f97e49..09fffe9f4f25 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -16,9 +16,12 @@
package android.app.timezonedetector;
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UserIdInt;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -38,9 +41,9 @@ import java.util.Objects;
*
* <p>Actions have associated methods, see the documentation for each action for details.
*
- * <p>For configuration capabilities, the associated current configuration value can be retrieved
- * using {@link TimeZoneDetector#getConfiguration()} and may be changed using
- * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)}.
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link #getConfiguration()} and may be changed using {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
*
* <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
*
@@ -60,7 +63,8 @@ public final class TimeZoneCapabilities implements Parcelable {
public static final int CAPABILITY_NOT_SUPPORTED = 10;
/**
- * Indicates that a capability is supported on this device, but not allowed for the user.
+ * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+ * if the capability relates to the ability to modify settings the user is not able to.
* This could be because of the user's type (e.g. maybe it applies to the primary user only) or
* device policy. Depending on the capability, this could mean the associated UI
* should be hidden, or displayed but disabled.
@@ -68,9 +72,11 @@ public final class TimeZoneCapabilities implements Parcelable {
public static final int CAPABILITY_NOT_ALLOWED = 20;
/**
- * Indicates that a capability is possessed but not applicable, e.g. if it is configuration,
- * the current configuration or device state renders it irrelevant. The associated UI may be
- * hidden, disabled, or left visible (but ineffective) depending on requirements.
+ * Indicates that a capability is possessed but not currently applicable, e.g. if the
+ * capability relates to the ability to modify settings, the user has the ability to modify
+ * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+ * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+ * ineffective) depending on requirements.
*/
public static final int CAPABILITY_NOT_APPLICABLE = 30;
@@ -89,13 +95,13 @@ public final class TimeZoneCapabilities implements Parcelable {
};
- private final @UserIdInt int mUserId;
+ @NonNull private final TimeZoneConfiguration mConfiguration;
private final @CapabilityState int mConfigureAutoDetectionEnabled;
private final @CapabilityState int mConfigureGeoDetectionEnabled;
private final @CapabilityState int mSuggestManualTimeZone;
private TimeZoneCapabilities(@NonNull Builder builder) {
- this.mUserId = builder.mUserId;
+ this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
@@ -103,7 +109,8 @@ public final class TimeZoneCapabilities implements Parcelable {
@NonNull
private static TimeZoneCapabilities createFromParcel(Parcel in) {
- return new TimeZoneCapabilities.Builder(in.readInt())
+ return new TimeZoneCapabilities.Builder()
+ .setConfiguration(in.readParcelable(null))
.setConfigureAutoDetectionEnabled(in.readInt())
.setConfigureGeoDetectionEnabled(in.readInt())
.setSuggestManualTimeZone(in.readInt())
@@ -112,21 +119,24 @@ public final class TimeZoneCapabilities implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mUserId);
+ dest.writeParcelable(mConfiguration, flags);
dest.writeInt(mConfigureAutoDetectionEnabled);
dest.writeInt(mConfigureGeoDetectionEnabled);
dest.writeInt(mSuggestManualTimeZone);
}
- /** Returns the user ID the capabilities are for. */
- public @UserIdInt int getUserId() {
- return mUserId;
+ /**
+ * Returns the user's time zone behavior configuration.
+ */
+ public @NonNull TimeZoneConfiguration getConfiguration() {
+ return mConfiguration;
}
/**
- * Returns the user's capability state for controlling whether automatic time zone detection is
- * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
- * TimeZoneConfiguration#isAutoDetectionEnabled()}.
+ * Returns the capability state associated with the user's ability to modify the automatic time
+ * zone detection setting. The setting can be updated via {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+ * #getConfiguration()}.
*/
@CapabilityState
public int getConfigureAutoDetectionEnabled() {
@@ -134,9 +144,10 @@ public final class TimeZoneCapabilities implements Parcelable {
}
/**
- * Returns the user's capability state for controlling whether geolocation can be used to detect
- * time zone via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
- * TimeZoneConfiguration#isGeoDetectionEnabled()}.
+ * Returns the capability state associated with the user's ability to modify the geolocation
+ * detection setting. The setting can be updated via {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+ * #getConfiguration()}.
*/
@CapabilityState
public int getConfigureGeoDetectionEnabled() {
@@ -144,8 +155,8 @@ public final class TimeZoneCapabilities implements Parcelable {
}
/**
- * Returns the user's capability state for manually setting the time zone on a device via
- * {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+ * Returns the capability state associated with the user's ability to manually set the time zone
+ * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
*
* <p>The suggestion will be ignored in all cases unless the value is {@link
* #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
@@ -155,6 +166,38 @@ public final class TimeZoneCapabilities implements Parcelable {
return mSuggestManualTimeZone;
}
+ /**
+ * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
+ * {@code requestedChanges}, if the current capabilities allow. The new configuration is
+ * returned and the capabilities are left unchanged. If the capabilities do not permit one or
+ * more of the changes then {@code null} is returned.
+ */
+ @Nullable
+ public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
+ if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
+ throw new IllegalArgumentException("User does not match:"
+ + " this=" + mConfiguration + ", other=" + requestedChanges);
+ }
+
+ TimeZoneConfiguration.Builder newConfigBuilder =
+ new TimeZoneConfiguration.Builder(mConfiguration);
+ if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
+ if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+ return null;
+ }
+ newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
+ }
+
+ if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
+ if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+ return null;
+ }
+ newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
+ }
+
+ return newConfigBuilder.build();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -169,7 +212,7 @@ public final class TimeZoneCapabilities implements Parcelable {
return false;
}
TimeZoneCapabilities that = (TimeZoneCapabilities) o;
- return mUserId == that.mUserId
+ return Objects.equals(mConfiguration, that.mConfiguration)
&& mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
&& mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
&& mSuggestManualTimeZone == that.mSuggestManualTimeZone;
@@ -177,7 +220,7 @@ public final class TimeZoneCapabilities implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mUserId,
+ return Objects.hash(mConfiguration,
mConfigureAutoDetectionEnabled,
mConfigureGeoDetectionEnabled,
mSuggestManualTimeZone);
@@ -186,7 +229,7 @@ public final class TimeZoneCapabilities implements Parcelable {
@Override
public String toString() {
return "TimeZoneDetectorCapabilities{"
- + "mUserId=" + mUserId
+ + "mConfiguration=" + mConfiguration
+ ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
+ ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
+ ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
@@ -196,16 +239,18 @@ public final class TimeZoneCapabilities implements Parcelable {
/** @hide */
public static class Builder {
- private final @UserIdInt int mUserId;
+ private TimeZoneConfiguration mConfiguration;
private @CapabilityState int mConfigureAutoDetectionEnabled;
private @CapabilityState int mConfigureGeoDetectionEnabled;
private @CapabilityState int mSuggestManualTimeZone;
- /**
- * Creates a new Builder with no properties set.
- */
- public Builder(@UserIdInt int userId) {
- mUserId = userId;
+ /** Sets the user-visible configuration settings. */
+ public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
+ if (!configuration.isComplete()) {
+ throw new IllegalArgumentException(configuration + " is not complete");
+ }
+ this.mConfiguration = configuration;
+ return this;
}
/** Sets the state for the automatic time zone detection enabled config. */
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 6f84ee22a985..95db0a26cc6e 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -18,6 +18,7 @@ package android.app.timezonedetector;
import android.annotation.NonNull;
import android.annotation.StringDef;
+import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,21 +28,20 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
- * Configuration that controls the behavior of the time zone detector associated with a specific
- * user.
+ * User visible settings that control the behavior of the time zone detector / manual time zone
+ * entry.
*
- * <p>Configuration consists of a set of known properties. When reading configuration via
- * {@link TimeZoneDetector#getConfiguration()} values for all known properties will be provided. In
- * some cases, such as when the configuration relies on optional hardware, the values may be
- * meaningless / defaulted to safe values.
+ * <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>Configuration properties can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those values will not be
- * changed. Not all configuration properties can be modified by all users. See {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities}.
+ * <p>Settings can be left absent when updating configuration via {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * changed. Not all configuration settings can be modified by all users: see {@link
+ * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
*
- * <p>See {@link #isComplete()} to tell if all known properties are present, and {@link
- * #hasProperty(String)} with {@code PROPERTY_} constants for testing individual properties.
+ * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
+ * of individual settings.
*
* @hide
*/
@@ -59,80 +59,82 @@ public final class TimeZoneConfiguration implements Parcelable {
};
/** All configuration properties */
- @StringDef(PROPERTY_AUTO_DETECTION_ENABLED)
+ @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
@Retention(RetentionPolicy.SOURCE)
- @interface Property {}
+ @interface Setting {}
/** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
- @Property
- public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+ @Setting
+ public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
/** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
- @Property
- public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+ @Setting
+ public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
- private final Bundle mBundle;
+ private final @UserIdInt int mUserId;
+ @NonNull private final Bundle mBundle;
private TimeZoneConfiguration(Builder builder) {
- this.mBundle = builder.mBundle;
+ this.mUserId = builder.mUserId;
+ this.mBundle = Objects.requireNonNull(builder.mBundle);
}
private static TimeZoneConfiguration createFromParcel(Parcel in) {
- return new TimeZoneConfiguration.Builder()
+ return new TimeZoneConfiguration.Builder(in.readInt())
.setPropertyBundleInternal(in.readBundle())
.build();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mUserId);
dest.writeBundle(mBundle);
}
- /** Returns {@code true} if all known properties are set. */
+ /** Returns the ID of the user this configuration is associated with. */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ /** Returns {@code true} if all known settings are present. */
public boolean isComplete() {
- return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
- && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
+ return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
+ && hasSetting(SETTING_GEO_DETECTION_ENABLED);
}
- /** Returns true if the specified property is set. */
- public boolean hasProperty(@Property String property) {
- return mBundle.containsKey(property);
+ /** Returns true if the specified setting is set. */
+ public boolean hasSetting(@Setting String setting) {
+ return mBundle.containsKey(setting);
}
/**
- * Returns the value of the {@link #PROPERTY_AUTO_DETECTION_ENABLED} property. This
+ * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This
* controls whether a device will attempt to determine the time zone automatically using
- * contextual information.
+ * contextual information if the device supports auto detection.
+ *
+ * <p>This setting is global and can be updated by some users.
*
- * @throws IllegalStateException if the field has not been set
+ * @throws IllegalStateException if the setting has not been set
*/
public boolean isAutoDetectionEnabled() {
- if (!mBundle.containsKey(PROPERTY_AUTO_DETECTION_ENABLED)) {
- throw new IllegalStateException(PROPERTY_AUTO_DETECTION_ENABLED + " is not set");
- }
- return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
+ enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
+ return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED);
}
/**
- * Returns the value of the {@link #PROPERTY_GEO_DETECTION_ENABLED} property. This
- * controls whether a device can use location to determine time zone. Only used when
- * {@link #isAutoDetectionEnabled()} is true.
+ * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
+ * controls whether a device can use geolocation to determine time zone. Only used when
+ * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
+ * location to be used.
+ *
+ * <p>This setting is user-scoped and can be updated by some users.
+ * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
*
- * @throws IllegalStateException if the field has not been set
+ * @throws IllegalStateException if the setting has not been set
*/
public boolean isGeoDetectionEnabled() {
- if (!mBundle.containsKey(PROPERTY_GEO_DETECTION_ENABLED)) {
- throw new IllegalStateException(PROPERTY_GEO_DETECTION_ENABLED + " is not set");
- }
- return mBundle.getBoolean(PROPERTY_GEO_DETECTION_ENABLED);
- }
-
- /**
- * Convenience method to merge this with another. The argument configuration properties have
- * precedence.
- */
- public TimeZoneConfiguration with(TimeZoneConfiguration other) {
- return new Builder(this).mergeProperties(other).build();
+ enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
+ return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
}
@Override
@@ -149,43 +151,61 @@ public final class TimeZoneConfiguration implements Parcelable {
return false;
}
TimeZoneConfiguration that = (TimeZoneConfiguration) o;
- return mBundle.kindofEquals(that.mBundle);
+ return mUserId == that.mUserId
+ && mBundle.kindofEquals(that.mBundle);
}
@Override
public int hashCode() {
- return Objects.hash(mBundle);
+ return Objects.hash(mUserId, mBundle);
}
@Override
public String toString() {
return "TimeZoneDetectorConfiguration{"
+ + "mUserId=" + mUserId
+ "mBundle=" + mBundle
+ '}';
}
+ private void enforceSettingPresent(@Setting String setting) {
+ if (!mBundle.containsKey(setting)) {
+ throw new IllegalStateException(setting + " is not set");
+ }
+ }
+
/** @hide */
public static class Builder {
- private Bundle mBundle = new Bundle();
+ private final @UserIdInt int mUserId;
+ private final Bundle mBundle = new Bundle();
/**
- * Creates a new Builder with no properties set.
+ * Creates a new Builder for a userId with no settings held.
*/
- public Builder() {}
+ public Builder(@UserIdInt int userId) {
+ mUserId = userId;
+ }
/**
- * Creates a new Builder by copying properties from an existing instance.
+ * Creates a new Builder by copying the user ID and settings from an existing instance.
*/
public Builder(TimeZoneConfiguration toCopy) {
+ this.mUserId = toCopy.mUserId;
mergeProperties(toCopy);
}
/**
- * Merges {@code other} properties into this instances, replacing existing values in this
- * where the properties appear in both.
+ * Merges {@code other} settings into this instances, replacing existing values in this
+ * where the settings appear in both.
*/
public Builder mergeProperties(TimeZoneConfiguration other) {
+ if (mUserId != other.mUserId) {
+ throw new IllegalArgumentException(
+ "Cannot merge configurations for different user IDs."
+ + " this.mUserId=" + this.mUserId
+ + ", other.mUserId=" + other.mUserId);
+ }
this.mBundle.putAll(other.mBundle);
return this;
}
@@ -195,15 +215,19 @@ public final class TimeZoneConfiguration implements Parcelable {
return this;
}
- /** Sets the desired state of the automatic time zone detection property. */
+ /**
+ * Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
+ */
public Builder setAutoDetectionEnabled(boolean enabled) {
- this.mBundle.putBoolean(PROPERTY_AUTO_DETECTION_ENABLED, enabled);
+ this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
return this;
}
- /** Sets the desired state of the geolocation time zone detection enabled property. */
+ /**
+ * Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
+ */
public Builder setGeoDetectionEnabled(boolean enabled) {
- this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+ this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
return this;
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 7885613bfb59..2b1cbf259c55 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -37,37 +37,29 @@ public interface TimeZoneDetector {
TimeZoneCapabilities getCapabilities();
/**
- * Returns the current user's complete time zone configuration. See {@link
- * TimeZoneConfiguration}.
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- @NonNull
- TimeZoneConfiguration getConfiguration();
-
- /**
* Modifies the time zone detection configuration.
*
- * <p>Configuration properties vary in scope: some may be device-wide, others may be specific to
- * the current user.
+ * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+ * specific to the current user.
*
- * <p>The ability to modify configuration properties can be subject to restrictions. For
+ * <p>The ability to modify configuration settings can be subject to restrictions. For
* example, they may be determined by device hardware, general policy (i.e. only the primary
- * user can set them), or by a managed device policy. See {@link #getCapabilities()} to obtain
+ * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
* information at runtime about the user's capabilities.
*
- * <p>Attempts to set configuration with capabilities that are {@link
+ * <p>Attempts to modify configuration settings with capabilities that are {@link
* TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
* TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
- * will be returned. Setting configuration with capabilities that are {@link
+ * will be returned. Modifying configuration settings with capabilities that are {@link
* TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
* TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
* TimeZoneCapabilities} for further details.
*
- * <p>If the configuration is not "complete", then only the specified properties will be
- * updated (where the user's capabilities allow) and other settings will be left unchanged. See
- * {@link TimeZoneConfiguration#isComplete()}.
+ * <p>If the supplied configuration only has some values set, then only the specified settings
+ * will be updated (where the user's capabilities allow) and other settings will be left
+ * unchanged.
*
- * @return {@code true} if all the configuration properties specified have been set to the
+ * @return {@code true} if all the configuration settings specified have been set to the
* new values, {@code false} if none have
*/
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -76,14 +68,20 @@ public interface TimeZoneDetector {
/**
* An interface that can be used to listen for changes to the time zone detector configuration.
*/
+ @FunctionalInterface
interface TimeZoneConfigurationListener {
- /** Called when the configuration changes. There are no guarantees about the thread used. */
- void onChange(@NonNull TimeZoneConfiguration configuration);
+ /**
+ * Called when something about the time zone configuration on the device has changed.
+ * This could be because the current user has changed, one of the device's relevant settings
+ * has changed, or something that could affect a user's capabilities has changed.
+ * There are no guarantees about the thread used.
+ */
+ void onChange();
}
/**
- * Registers a listener that will be informed when the configuration changes. The complete
- * configuration is passed to the listener, not just the properties that have changed.
+ * Registers a listener that will be informed when something about the time zone configuration
+ * changes.
*/
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 0770aff4e9bb..4c69732abec9 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -57,19 +57,6 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
}
@Override
- @NonNull
- public TimeZoneConfiguration getConfiguration() {
- if (DEBUG) {
- Log.d(TAG, "getConfiguration called");
- }
- try {
- return mITimeZoneDetectorService.getConfiguration();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
if (DEBUG) {
Log.d(TAG, "updateConfiguration called: " + configuration);
@@ -94,8 +81,8 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
ITimeZoneConfigurationListener iListener =
new ITimeZoneConfigurationListener.Stub() {
@Override
- public void onChange(@NonNull TimeZoneConfiguration configuration) {
- notifyConfigurationListeners(configuration);
+ public void onChange() {
+ notifyConfigurationListeners();
}
};
mConfigurationReceiver = iListener;
@@ -116,14 +103,14 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
}
}
- private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) {
+ private void notifyConfigurationListeners() {
final ArraySet<TimeZoneConfigurationListener> configurationListeners;
synchronized (this) {
configurationListeners = new ArraySet<>(mConfigurationListeners);
}
int size = configurationListeners.size();
for (int i = 0; i < size; i++) {
- configurationListeners.valueAt(i).onChange(configuration);
+ configurationListeners.valueAt(i).onChange();
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c302def19298..03cf0cf2ca78 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6404,6 +6404,17 @@ public final class Settings {
public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
/**
+ * The current location time zone detection enabled state for the user.
+ *
+ * See {@link
+ * android.app.timezonedetector.TimeZoneDetector#getCapabilities} for access. See {@link
+ * android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update.
+ * @hide
+ */
+ public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED =
+ "location_time_zone_detection_enabled";
+
+ /**
* The accuracy in meters used for coarsening location for clients with only the coarse
* location permission.
*
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 72391f4d7dec..db127c6cb9ed 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -22,6 +22,7 @@ import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSE
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import org.junit.Test;
@@ -31,11 +32,22 @@ public class TimeZoneCapabilitiesTest {
@Test
public void testEquals() {
- TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(false)
+ .setGeoDetectionEnabled(false)
+ .build();
+
+ TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
+ .setConfiguration(configuration1)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+ TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
+ .setConfiguration(configuration1)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -45,6 +57,20 @@ public class TimeZoneCapabilitiesTest {
assertEquals(one, two);
}
+ builder2.setConfiguration(configuration2);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfiguration(configuration2);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
@@ -90,7 +116,12 @@ public class TimeZoneCapabilitiesTest {
@Test
public void testParcelable() {
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+ .setConfiguration(configuration)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -105,4 +136,51 @@ public class TimeZoneCapabilitiesTest {
builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
}
+
+ @Test
+ public void testApplyUpdate_permitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+ .setConfiguration(oldConfiguration)
+ .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+ .build();
+ assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertEquals(expected, capabilities.applyUpdate(configChange));
+ }
+
+ @Test
+ public void testApplyUpdate_notPermitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+ .setConfiguration(oldConfiguration)
+ .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+ .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
+ .build();
+ assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ assertNull(capabilities.applyUpdate(configChange));
+ }
}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index 00dc73ed269f..faf908de8d4a 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -27,11 +27,14 @@ import org.junit.Test;
public class TimeZoneConfigurationTest {
+ private static final int ARBITRARY_USER_ID = 9876;
+
@Test
public void testBuilder_copyConstructor() {
- TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true);
+ TimeZoneConfiguration.Builder builder1 =
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true);
TimeZoneConfiguration configuration1 = builder1.build();
TimeZoneConfiguration configuration2 =
@@ -41,28 +44,28 @@ public class TimeZoneConfigurationTest {
}
@Test
- public void testIsComplete() {
- TimeZoneConfiguration.Builder builder =
- new TimeZoneConfiguration.Builder();
- assertFalse(builder.build().isComplete());
-
- builder.setAutoDetectionEnabled(true);
- assertFalse(builder.build().isComplete());
+ public void testIntrospectionMethods() {
+ TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+ assertFalse(empty.isComplete());
+ assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
- builder.setGeoDetectionEnabled(true);
- assertTrue(builder.build().isComplete());
+ TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ assertTrue(completeConfig.isComplete());
+ assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
}
@Test
public void testBuilder_mergeProperties() {
- TimeZoneConfiguration configuration1 =
- new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(true)
- .build();
+ TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionEnabled(true)
+ .build();
{
TimeZoneConfiguration mergedEmptyAnd1 =
- new TimeZoneConfiguration.Builder()
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
.mergeProperties(configuration1)
.build();
assertEquals(configuration1, mergedEmptyAnd1);
@@ -70,7 +73,7 @@ public class TimeZoneConfigurationTest {
{
TimeZoneConfiguration configuration2 =
- new TimeZoneConfiguration.Builder()
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
.setAutoDetectionEnabled(false)
.build();
@@ -87,14 +90,22 @@ public class TimeZoneConfigurationTest {
@Test
public void testEquals() {
TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder();
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
{
TimeZoneConfiguration one = builder1.build();
assertEquals(one, one);
}
+ {
+ TimeZoneConfiguration.Builder differentUserBuilder =
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
+ TimeZoneConfiguration one = builder1.build();
+ TimeZoneConfiguration two = differentUserBuilder.build();
+ assertNotEquals(one, two);
+ }
+
TimeZoneConfiguration.Builder builder2 =
- new TimeZoneConfiguration.Builder();
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
{
TimeZoneConfiguration one = builder1.build();
TimeZoneConfiguration two = builder2.build();
@@ -148,7 +159,7 @@ public class TimeZoneConfigurationTest {
@Test
public void testParcelable() {
TimeZoneConfiguration.Builder builder =
- new TimeZoneConfiguration.Builder();
+ new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
assertRoundTripParcelable(builder.build());
builder.setAutoDetectionEnabled(true);
diff --git a/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
new file mode 100644
index 000000000000..1500cfaeb3a2
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 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.timezonedetector;
+
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.UserHandle;
+
+/**
+ * An interface to wrap various difficult-to-intercept calls that services make to access / manage
+ * caller identity, e.g. {@link Binder#clearCallingIdentity()}.
+ */
+public interface CallerIdentityInjector {
+
+ /** A singleton for the real implementation of {@link CallerIdentityInjector}. */
+ CallerIdentityInjector REAL = new Real();
+
+ /** A {@link UserHandle#getCallingUserId()} call. */
+ @UserIdInt int getCallingUserId();
+
+ /** A {@link Binder#clearCallingIdentity()} call. */
+ long clearCallingIdentity();
+
+ /** A {@link Binder#restoreCallingIdentity(long)} ()} call. */
+ void restoreCallingIdentity(long token);
+
+ /** The real implementation of {@link CallerIdentityInjector}. */
+ class Real implements CallerIdentityInjector {
+
+ protected Real() {
+ }
+
+ @Override
+ public int getCallingUserId() {
+ return UserHandle.getCallingUserId();
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ return Binder.clearCallingIdentity();
+ }
+
+ @Override
+ public void restoreCallingIdentity(long token) {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
new file mode 100644
index 000000000000..4c7b1f38dd5a
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 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.timezonedetector;
+
+/**
+ * A listener used to receive notification that time zone configuration has changed.
+ */
+@FunctionalInterface
+public interface ConfigurationChangeListener {
+ /** Called when the current user or a configuration value has changed. */
+ void onChange();
+}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
new file mode 100644
index 000000000000..aee3d8d3499b
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 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.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.timezonedetector.TimeZoneConfiguration;
+
+import java.util.Objects;
+
+/**
+ * Holds all configuration values that affect time zone behavior and some associated logic, e.g.
+ * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
+ * #createCapabilities()}.
+ */
+public final class ConfigurationInternal {
+
+ private final @UserIdInt int mUserId;
+ private final boolean mUserConfigAllowed;
+ private final boolean mAutoDetectionSupported;
+ private final boolean mAutoDetectionEnabled;
+ private final boolean mLocationEnabled;
+ private final boolean mGeoDetectionEnabled;
+
+ private ConfigurationInternal(Builder builder) {
+ mUserId = builder.mUserId;
+ mUserConfigAllowed = builder.mUserConfigAllowed;
+ mAutoDetectionSupported = builder.mAutoDetectionSupported;
+ mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+ mLocationEnabled = builder.mLocationEnabled;
+ mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+ }
+
+ /** Returns the ID of the user this configuration is associated with. */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ /** Returns true if the user allowed to modify time zone configuration. */
+ public boolean isUserConfigAllowed() {
+ return mUserConfigAllowed;
+ }
+
+ /** Returns true if the device supports some form of auto time zone detection. */
+ public boolean isAutoDetectionSupported() {
+ return mAutoDetectionSupported;
+ }
+
+ /** Returns the value of the auto time zone detection enabled setting. */
+ public boolean getAutoDetectionEnabledSetting() {
+ return mAutoDetectionEnabled;
+ }
+
+ /**
+ * Returns true if auto time zone detection behavior is actually enabled, which can be distinct
+ * from the raw setting value. */
+ public boolean getAutoDetectionEnabledBehavior() {
+ return mAutoDetectionSupported && mAutoDetectionEnabled;
+ }
+
+ /** Returns true if user's location can be used generally. */
+ public boolean isLocationEnabled() {
+ return mLocationEnabled;
+ }
+
+ /** Returns the value of the geolocation time zone detection enabled setting. */
+ public boolean getGeoDetectionEnabledSetting() {
+ return mGeoDetectionEnabled;
+ }
+
+ /**
+ * Returns true if geolocation time zone detection behavior is actually enabled, which can be
+ * distinct from the raw setting value.
+ */
+ public boolean getGeoDetectionEnabledBehavior() {
+ if (getAutoDetectionEnabledBehavior()) {
+ return mLocationEnabled && mGeoDetectionEnabled;
+ }
+ return false;
+ }
+
+ /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
+ public TimeZoneCapabilities createCapabilities() {
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+ .setConfiguration(asConfiguration());
+
+ boolean allowConfigDateTime = isUserConfigAllowed();
+
+ // Automatic time zone detection is only supported on devices if there is a telephony
+ // network available or geolocation time zone detection is possible.
+ boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
+
+ final int configureAutoDetectionEnabledCapability;
+ if (!deviceHasTimeZoneDetection) {
+ configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+ } else if (!allowConfigDateTime) {
+ configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+ } else {
+ configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
+ }
+ builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+
+ final int configureGeolocationDetectionEnabledCapability;
+ if (!deviceHasTimeZoneDetection) {
+ configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+ } else if (!allowConfigDateTime) {
+ configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+ } else if (!isLocationEnabled()) {
+ configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
+ } else {
+ configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
+ }
+ builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+
+ // The ability to make manual time zone 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 int suggestManualTimeZoneCapability;
+ if (!allowConfigDateTime) {
+ suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED;
+ } else if (getAutoDetectionEnabledBehavior()) {
+ suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE;
+ } else {
+ suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
+ }
+ builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+
+ return builder.build();
+ }
+
+ /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
+ public TimeZoneConfiguration asConfiguration() {
+ return new TimeZoneConfiguration.Builder(mUserId)
+ .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
+ .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
+ .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(TimeZoneConfiguration newConfiguration) {
+ Builder builder = new Builder(this);
+ if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+ builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
+ }
+ if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+ builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ConfigurationInternal that = (ConfigurationInternal) o;
+ return mUserId == that.mUserId
+ && mUserConfigAllowed == that.mUserConfigAllowed
+ && mAutoDetectionSupported == that.mAutoDetectionSupported
+ && mAutoDetectionEnabled == that.mAutoDetectionEnabled
+ && mLocationEnabled == that.mLocationEnabled
+ && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
+ mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorConfiguration{"
+ + "mUserId=" + mUserId
+ + "mUserConfigAllowed=" + mUserConfigAllowed
+ + "mAutoDetectionSupported=" + mAutoDetectionSupported
+ + "mAutoDetectionEnabled=" + mAutoDetectionEnabled
+ + "mLocationEnabled=" + mLocationEnabled
+ + "mGeoDetectionEnabled=" + mGeoDetectionEnabled
+ + '}';
+ }
+
+ /**
+ * A Builder for {@link ConfigurationInternal}.
+ */
+ public static class Builder {
+
+ private final @UserIdInt int mUserId;
+ private boolean mUserConfigAllowed;
+ private boolean mAutoDetectionSupported;
+ private boolean mAutoDetectionEnabled;
+ private boolean mLocationEnabled;
+ private boolean mGeoDetectionEnabled;
+
+ /**
+ * Creates a new Builder with only the userId set.
+ */
+ public Builder(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Creates a new Builder by copying values from an existing instance.
+ */
+ public Builder(ConfigurationInternal toCopy) {
+ this.mUserId = toCopy.mUserId;
+ this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
+ this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
+ this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
+ this.mLocationEnabled = toCopy.mLocationEnabled;
+ this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
+ }
+
+ /**
+ * Sets whether the user is allowed to configure time zone settings on this device.
+ */
+ public Builder setUserConfigAllowed(boolean configAllowed) {
+ mUserConfigAllowed = configAllowed;
+ return this;
+ }
+
+ /**
+ * Sets whether automatic time zone detection is supported on this device.
+ */
+ public Builder setAutoDetectionSupported(boolean supported) {
+ mAutoDetectionSupported = supported;
+ return this;
+ }
+
+ /**
+ * Sets the value of the automatic time zone detection enabled setting for this device.
+ */
+ public Builder setAutoDetectionEnabled(boolean enabled) {
+ mAutoDetectionEnabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets the value of the location mode setting for this user.
+ */
+ public Builder setLocationEnabled(boolean enabled) {
+ mLocationEnabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets the value of the geolocation time zone detection setting for this user.
+ */
+ public Builder setGeoDetectionEnabled(boolean enabled) {
+ mGeoDetectionEnabled = enabled;
+ return this;
+ }
+
+ /** Returns a new {@link ConfigurationInternal}. */
+ @NonNull
+ public ConfigurationInternal build() {
+ return new ConfigurationInternal(this);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 0ca36e0fc258..d64032325539 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -16,24 +16,30 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.content.Intent.ACTION_USER_SWITCHED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.location.LocationManager;
import android.net.ConnectivityManager;
+import android.os.Handler;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
import java.util.Objects;
@@ -42,103 +48,87 @@ import java.util.Objects;
*/
public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
+ private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- private final Context mContext;
- private final ContentResolver mCr;
- private final UserManager mUserManager;
-
- TimeZoneDetectorCallbackImpl(Context context) {
- mContext = context;
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ContentResolver mCr;
+ @NonNull private final UserManager mUserManager;
+ @NonNull private final boolean mGeoDetectionFeatureEnabled;
+ @NonNull private final LocationManager mLocationManager;
+ // @NonNull after setConfigChangeListener() is called.
+ private ConfigurationChangeListener mConfigChangeListener;
+
+ TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
+ boolean geoDetectionFeatureEnabled) {
+ mContext = Objects.requireNonNull(context);
+ mHandler = Objects.requireNonNull(handler);
mCr = context.getContentResolver();
mUserManager = context.getSystemService(UserManager.class);
+ mLocationManager = context.getSystemService(LocationManager.class);
+ mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
+
+ // Wire up the change listener. All invocations are performed on the mHandler thread.
+
+ // Listen for the user changing / the user's location mode changing.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_USER_SWITCHED);
+ filter.addAction(LocationManager.MODE_CHANGED_ACTION);
+ mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleConfigChangeOnHandlerThread();
+ }
+ }, filter, null, mHandler);
+
+ // Add async callbacks for global settings being changed.
+ ContentResolver contentResolver = mContext.getContentResolver();
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+ new ContentObserver(mHandler) {
+ public void onChange(boolean selfChange) {
+ handleConfigChangeOnHandlerThread();
+ }
+ });
+
+ // Add async callbacks for user scoped location settings being changed.
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
+ true,
+ new ContentObserver(mHandler) {
+ public void onChange(boolean selfChange) {
+ handleConfigChangeOnHandlerThread();
+ }
+ }, UserHandle.USER_ALL);
}
- @Override
- public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
- UserHandle userHandle = UserHandle.of(userId);
- boolean disallowConfigDateTime =
- mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
-
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userId);
-
- // Automatic time zone detection is only supported (currently) on devices if there is a
- // telephony network available.
- if (!deviceHasTelephonyNetwork()) {
- builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
- } else if (disallowConfigDateTime) {
- builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- } else {
- builder.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED);
- }
-
- // TODO(b/149014708) Replace this with real logic when the settings storage is fully
- // implemented.
- builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
-
- // The ability to make manual time zone 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.
- if (disallowConfigDateTime) {
- builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- } else if (isAutoDetectionEnabled()) {
- builder.setSuggestManualTimeZone(CAPABILITY_NOT_APPLICABLE);
- } else {
- builder.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
+ private void handleConfigChangeOnHandlerThread() {
+ if (mConfigChangeListener == null) {
+ Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
}
- return builder.build();
+ mConfigChangeListener.onChange();
}
@Override
- public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
- return new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(isAutoDetectionEnabled())
- .setGeoDetectionEnabled(isGeoDetectionEnabled())
- .build();
+ public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+ mConfigChangeListener = Objects.requireNonNull(listener);
}
@Override
- public void setConfiguration(
- @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
- Objects.requireNonNull(configuration);
- if (!configuration.isComplete()) {
- throw new IllegalArgumentException("configuration=" + configuration + " not complete");
- }
-
- // Avoid writing auto detection config for devices that do not support auto time zone
- // detection: if we wrote it down then we'd set the default explicitly. That might influence
- // what happens on later releases that do support auto detection on the same hardware.
- if (isAutoDetectionSupported()) {
- final int autoEnabledValue = configuration.isAutoDetectionEnabled() ? 1 : 0;
- Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, autoEnabledValue);
-
- final boolean geoTzDetectionEnabledValue = configuration.isGeoDetectionEnabled();
- // TODO(b/149014708) Write this down to user-scoped settings once implemented.
- }
- }
-
- @Override
- public boolean isAutoDetectionEnabled() {
- // To ensure that TimeZoneConfiguration is "complete" for simplicity, devices that do not
- // support auto detection have safe, hard coded configuration values that make it look like
- // auto detection is turned off. It is therefore important that false is returned from this
- // method for devices that do not support auto time zone detection. Such devices will not
- // have a UI to turn the auto detection on/off. Returning true could prevent the user
- // entering information manually. On devices that do support auto time detection the default
- // is to turn auto detection on.
- if (isAutoDetectionSupported()) {
- return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
- }
- return false;
+ public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+ return new ConfigurationInternal.Builder(userId)
+ .setUserConfigAllowed(isUserConfigAllowed(userId))
+ .setAutoDetectionSupported(isAutoDetectionSupported())
+ .setAutoDetectionEnabled(isAutoDetectionEnabled())
+ .setLocationEnabled(isLocationEnabled(userId))
+ .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
+ .build();
}
@Override
- public boolean isGeoDetectionEnabled() {
- // TODO(b/149014708) Read this from user-scoped settings once implemented. The user's
- // location toggle will act as an override for this setting, i.e. so that the setting will
- // return false if the location toggle is disabled.
- return false;
+ public @UserIdInt int getCurrentUserId() {
+ return LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId();
}
@Override
@@ -165,8 +155,55 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
alarmManager.setTimeZone(zoneId);
}
+ @Override
+ public void storeConfiguration(TimeZoneConfiguration configuration) {
+ Objects.requireNonNull(configuration);
+
+ // Avoid writing the auto detection enabled setting for devices that do not support auto
+ // time zone 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();
+ setAutoDetectionEnabled(autoDetectionEnabled);
+
+ final int userId = configuration.getUserId();
+ final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
+ setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
+ }
+ }
+
+ private boolean isUserConfigAllowed(@UserIdInt int userId) {
+ UserHandle userHandle = UserHandle.of(userId);
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
+ }
+
private boolean isAutoDetectionSupported() {
- return deviceHasTelephonyNetwork();
+ return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
+ }
+
+ private boolean isAutoDetectionEnabled() {
+ return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
+ }
+
+ private void setAutoDetectionEnabled(boolean enabled) {
+ Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0);
+ }
+
+ private boolean isLocationEnabled(@UserIdInt int userId) {
+ return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId));
+ }
+
+ private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
+ final boolean locationEnabled = isLocationEnabled(userId);
+ return Settings.Secure.getIntForUser(mCr,
+ Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+ locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
+ }
+
+ private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
+ Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+ enabled ? 1 : 0, userId);
}
private boolean deviceHasTelephonyNetwork() {
@@ -174,4 +211,4 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
return mContext.getSystemService(ConnectivityManager.class)
.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
-}
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index fb7a73d12632..2d50390c27a9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -27,6 +27,12 @@ import android.annotation.NonNull;
*/
public interface TimeZoneDetectorInternal extends Dumpable.Container {
+ /** Adds a listener that will be invoked when time zone detection configuration is changed. */
+ void addConfigurationListener(ConfigurationChangeListener listener);
+
+ /** Returns the {@link ConfigurationInternal} for the current user. */
+ ConfigurationInternal getCurrentUserConfigurationInternal();
+
/**
* Suggests the current time zone, determined using geolocation, to the detector. The
* detector may ignore the signal based on system settings, whether better information is
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 15412a0d14a1..f0ce827cec5e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -20,8 +20,8 @@ import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
-import com.android.internal.annotations.VisibleForTesting;
-
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -34,18 +34,26 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+ @NonNull private final List<ConfigurationChangeListener> mConfigurationListeners =
+ new ArrayList<>();
- static TimeZoneDetectorInternalImpl create(@NonNull Context context, @NonNull Handler handler,
- @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
- return new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
- }
-
- @VisibleForTesting
public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
+
+ // Wire up a change listener so that any downstream listeners can be notified when
+ // the configuration changes for any reason.
+ mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
+ }
+
+ private void handleConfigurationChanged() {
+ synchronized (mConfigurationListeners) {
+ for (ConfigurationChangeListener listener : mConfigurationListeners) {
+ listener.onChange();
+ }
+ }
}
@Override
@@ -54,6 +62,19 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
}
@Override
+ public void addConfigurationListener(ConfigurationChangeListener listener) {
+ synchronized (mConfigurationListeners) {
+ mConfigurationListeners.add(Objects.requireNonNull(listener));
+ }
+ }
+
+ @Override
+ @NonNull
+ public ConfigurationInternal getCurrentUserConfigurationInternal() {
+ return mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal();
+ }
+
+ @Override
public void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
Objects.requireNonNull(timeZoneSuggestion);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index d81f949742bd..7501d9fe6a7c 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -24,32 +24,24 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.StrategyListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.Objects;
/**
@@ -65,6 +57,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
private static final String TAG = "TimeZoneDetectorService";
+ /** A compile time switch for enabling / disabling geolocation-based time zone detection. */
+ private static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
+
/**
* Handles the service lifecycle for {@link TimeZoneDetectorService} and
* {@link TimeZoneDetectorInternalImpl}.
@@ -80,18 +75,20 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
// Obtain / create the shared dependencies.
Context context = getContext();
Handler handler = FgThread.getHandler();
+
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
- TimeZoneDetectorStrategyImpl.create(context);
+ TimeZoneDetectorStrategyImpl.create(
+ context, handler, GEOLOCATION_TIME_ZONE_DETECTION_ENABLED);
// Create and publish the local service for use by internal callers.
TimeZoneDetectorInternal internal =
- TimeZoneDetectorInternalImpl.create(context, handler, timeZoneDetectorStrategy);
+ new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
publishLocalService(TimeZoneDetectorInternal.class, internal);
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
- TimeZoneDetectorService service =
- TimeZoneDetectorService.create(context, handler, timeZoneDetectorStrategy);
+ TimeZoneDetectorService service = TimeZoneDetectorService.create(
+ context, handler, timeZoneDetectorStrategy);
publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
}
}
@@ -103,79 +100,51 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
private final Handler mHandler;
@NonNull
+ private final CallerIdentityInjector mCallerIdentityInjector;
+
+ @NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
- /**
- * This sparse array acts as a map from userId to listeners running as that userId. User scoped
- * as time zone detection configuration is partially user-specific, so different users can
- * get different configuration.
- */
@GuardedBy("mConfigurationListeners")
@NonNull
- private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners =
- new SparseArray<>();
+ private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
+ new ArrayList<>();
private static TimeZoneDetectorService create(
@NonNull Context context, @NonNull Handler handler,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
- TimeZoneDetectorService service =
- new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
-
- ContentResolver contentResolver = context.getContentResolver();
- contentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
- new ContentObserver(handler) {
- public void onChange(boolean selfChange) {
- service.handleAutoTimeZoneConfigChanged();
- }
- });
- // TODO(b/149014708) Listen for changes to geolocation time zone detection enabled config.
- // This should also include listening to the current user and the current user's location
- // toggle since the config is user-scoped and the location toggle overrides the geolocation
- // time zone enabled setting.
+ CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
+ TimeZoneDetectorService service = new TimeZoneDetectorService(
+ context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
return service;
}
@VisibleForTesting
public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
+ @NonNull CallerIdentityInjector callerIdentityInjector,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
+ mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
- mTimeZoneDetectorStrategy.setStrategyListener(new StrategyListener() {
- @Override
- public void onConfigurationChanged() {
- handleConfigurationChanged();
- }
- });
- }
-
- @Override
- @NonNull
- public TimeZoneCapabilities getCapabilities() {
- enforceManageTimeZoneDetectorConfigurationPermission();
- int userId = UserHandle.getCallingUserId();
- long token = Binder.clearCallingIdentity();
- try {
- return mTimeZoneDetectorStrategy.getCapabilities(userId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+ // the configuration changes for any reason.
+ mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
}
@Override
@NonNull
- public TimeZoneConfiguration getConfiguration() {
+ public TimeZoneCapabilities getCapabilities() {
enforceManageTimeZoneDetectorConfigurationPermission();
- int userId = UserHandle.getCallingUserId();
- long token = Binder.clearCallingIdentity();
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.getConfiguration(userId);
+ return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
} finally {
- Binder.restoreCallingIdentity(token);
+ mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@@ -184,12 +153,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
enforceManageTimeZoneDetectorConfigurationPermission();
Objects.requireNonNull(configuration);
- int userId = UserHandle.getCallingUserId();
- long token = Binder.clearCallingIdentity();
+ int callingUserId = mCallerIdentityInjector.getCallingUserId();
+ if (callingUserId != configuration.getUserId()) {
+ return false;
+ }
+
+ long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+ return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
} finally {
- Binder.restoreCallingIdentity(token);
+ mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@@ -197,25 +170,17 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
enforceManageTimeZoneDetectorConfigurationPermission();
Objects.requireNonNull(listener);
- int userId = UserHandle.getCallingUserId();
synchronized (mConfigurationListeners) {
- ArrayList<ITimeZoneConfigurationListener> listeners =
- mConfigurationListeners.get(userId);
- if (listeners != null && listeners.contains(listener)) {
+ if (mConfigurationListeners.contains(listener)) {
return;
}
try {
- if (listeners == null) {
- listeners = new ArrayList<>(1);
- mConfigurationListeners.put(userId, listeners);
- }
-
// Ensure the reference to the listener will be removed if the client process dies.
listener.asBinder().linkToDeath(this, 0 /* flags */);
// Only add the listener if we can linkToDeath().
- listeners.add(listener);
+ mConfigurationListeners.add(listener);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
}
@@ -226,19 +191,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
enforceManageTimeZoneDetectorConfigurationPermission();
Objects.requireNonNull(listener);
- int userId = UserHandle.getCallingUserId();
synchronized (mConfigurationListeners) {
boolean removedListener = false;
- ArrayList<ITimeZoneConfigurationListener> userListeners =
- mConfigurationListeners.get(userId);
- if (userListeners.remove(listener)) {
+ if (mConfigurationListeners.remove(listener)) {
// Stop listening for the client process to die.
listener.asBinder().unlinkToDeath(this, 0 /* flags */);
removedListener = true;
}
if (!removedListener) {
- Slog.w(TAG, "Client asked to remove listenener=" + listener
+ Slog.w(TAG, "Client asked to remove listener=" + listener
+ ", but no listeners were removed."
+ " mConfigurationListeners=" + mConfigurationListeners);
}
@@ -259,19 +221,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
public void binderDied(IBinder who) {
synchronized (mConfigurationListeners) {
boolean removedListener = false;
- final int userCount = mConfigurationListeners.size();
- for (int i = 0; i < userCount; i++) {
- ArrayList<ITimeZoneConfigurationListener> userListeners =
- mConfigurationListeners.valueAt(i);
- Iterator<ITimeZoneConfigurationListener> userListenerIterator =
- userListeners.iterator();
- while (userListenerIterator.hasNext()) {
- ITimeZoneConfigurationListener userListener = userListenerIterator.next();
- if (userListener.asBinder().equals(who)) {
- userListenerIterator.remove();
- removedListener = true;
- break;
- }
+ final int listenerCount = mConfigurationListeners.size();
+ for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
+ ITimeZoneConfigurationListener listener =
+ mConfigurationListeners.get(listenerIndex);
+ if (listener.asBinder().equals(who)) {
+ mConfigurationListeners.remove(listenerIndex);
+ removedListener = true;
+ break;
}
}
if (!removedListener) {
@@ -283,42 +240,25 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
void handleConfigurationChanged() {
- // Note: we could trigger an async time zone detection operation here via a call to
- // handleAutoTimeZoneConfigChanged(), but that is triggered in response to the underlying
- // setting value changing so it is currently unnecessary. If we get to a point where all
- // configuration changes are guaranteed to happen in response to an updateConfiguration()
- // call, then we can remove that path and call it here instead.
-
// Configuration has changed, but each user may have a different view of the configuration.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
synchronized (mConfigurationListeners) {
- final int userCount = mConfigurationListeners.size();
- for (int userIndex = 0; userIndex < userCount; userIndex++) {
- int userId = mConfigurationListeners.keyAt(userIndex);
- TimeZoneConfiguration configuration =
- mTimeZoneDetectorStrategy.getConfiguration(userId);
-
- ArrayList<ITimeZoneConfigurationListener> listeners =
- mConfigurationListeners.valueAt(userIndex);
- final int listenerCount = listeners.size();
- for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
- ITimeZoneConfigurationListener listener = listeners.get(listenerIndex);
- try {
- listener.onChange(configuration);
- } catch (RemoteException e) {
- Slog.w(TAG, "Unable to notify listener=" + listener
- + " for userId=" + userId
- + " of updated configuration=" + configuration, e);
- }
+ final int listenerCount = mConfigurationListeners.size();
+ for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
+ ITimeZoneConfigurationListener listener =
+ mConfigurationListeners.get(listenerIndex);
+ try {
+ listener.onChange();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to notify listener=" + listener, e);
}
}
}
}
/** Provided for command-line access. This is not exposed as a binder API. */
- void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
enforceSuggestGeolocationTimeZonePermission();
Objects.requireNonNull(timeZoneSuggestion);
@@ -331,12 +271,12 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
enforceSuggestManualTimeZonePermission();
Objects.requireNonNull(timeZoneSuggestion);
- int userId = UserHandle.getCallingUserId();
- long token = Binder.clearCallingIdentity();
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ long token = mCallerIdentityInjector.clearCallingIdentity();
try {
return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
} finally {
- Binder.restoreCallingIdentity(token);
+ mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@@ -358,12 +298,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
ipw.flush();
}
- /** Internal method for handling the auto time zone configuration being changed. */
- @VisibleForTesting
- public void handleAutoTimeZoneConfigChanged() {
- mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneConfigChanged);
- }
-
private void enforceManageTimeZoneDetectorConfigurationPermission() {
// TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
mContext.enforceCallingPermission(
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index c5b7e39f4fef..f944c5638fa9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -19,51 +19,84 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
/**
- * The interface for the class that implements the time detection algorithm used by the
- * {@link TimeZoneDetectorService}.
+ * The interface for the class that is responsible for setting the time zone on a device, used by
+ * {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
*
- * <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
- * and what to set it to.
+ * <p>The strategy receives suggestions, which it may use to modify the device's time zone setting.
+ * Suggestions are acted on or ignored as needed, depending on previously received suggestions and
+ * the current user's configuration (see {@link ConfigurationInternal}).
*
- * <p>Most calls will be handled by a single thread, but that is not true for all calls. For example
- * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread concurrently
- * with other operations so implementations must still handle thread safety.
+ * <p>Devices can have zero, one or two automatic time zone detection algorithm available at any
+ * point in time.
+ *
+ * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * availability and use depends on several factors:
+ * <ul>
+ * <li>Telephony is only available on devices with a telephony stack.
+ * <li>Geolocation is also optional and configured at image creation time. When enabled on a
+ * device, its availability depends on the current user's settings, so switching between users can
+ * change the automatic algorithm used by the device.</li>
+ * </ul>
+ *
+ * <p>If there are no automatic time zone detections algorithms available then the user can usually
+ * change the device time zone manually. Under most circumstances the current user can turn
+ * automatic time zone detection on or off, or choose the algorithm via settings.
+ *
+ * <p>Telephony detection is independent of the current user. The device keeps track of the most
+ * recent telephony suggestion from each slotIndex. When telephony detection is in use, the highest
+ * scoring suggestion is used to set the device time zone based on a scoring algorithm. If several
+ * slotIndexes provide the same score then the slotIndex with the lowest numeric value "wins". If
+ * the situation changes and it is no longer possible to be confident about the time zone,
+ * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
+ * suggestion otherwise it will remain in use.
+ *
+ * <p>Geolocation detection is dependent on the current user and their settings. The device retains
+ * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
+ * user's "location toggle", but even when that is enabled the user may choose to enable / disable
+ * the use of geolocation for device time zone detection. If the current user changes to one that
+ * does not have geolocation detection enabled, or the user turns off geolocation detection, then
+ * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
+ * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
+ * will remain in use.
+ *
+ * <p>Threading:
+ *
+ * <p>Suggestion calls with a void return type may be handed off to a separate thread and handled
+ * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, and debug
+ * calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread
+ * concurrently with other operations.
*
* @hide
*/
public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
- /** A listener for strategy events. */
- interface StrategyListener {
- /**
- * Invoked when configuration has been changed.
- */
- void onConfigurationChanged();
- }
-
- /** Sets the listener that enables the strategy to communicate with the surrounding service. */
- void setStrategyListener(@NonNull StrategyListener listener);
+ /**
+ * Sets a listener that will be triggered whenever time zone detection configuration is
+ * changed.
+ */
+ void addConfigChangeListener(@NonNull ConfigurationChangeListener listener);
- /** Returns the user's time zone capabilities. */
+ /** Returns the user's time zone configuration. */
@NonNull
- TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
+ ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
/**
- * Returns the configuration that controls time zone detector behavior.
+ * Returns the configuration that controls time zone detector behavior for the current user.
*/
@NonNull
- TimeZoneConfiguration getConfiguration(@UserIdInt int userId);
+ ConfigurationInternal getCurrentUserConfigurationInternal();
/**
- * Updates the configuration settings that control time zone detector behavior.
+ * Updates the configuration properties that control a device's time zone behavior.
+ *
+ * <p>This method returns {@code true} if the configuration was changed,
+ * {@code false} otherwise.
*/
- boolean updateConfiguration(
- @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
+ boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
/**
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
@@ -85,9 +118,4 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
* suggestion.
*/
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
-
- /**
- * Called when there has been a change to the automatic time zone detection configuration.
- */
- void handleAutoTimeZoneConfigChanged();
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index d1369a289428..8a42b18b514f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -20,10 +20,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYP
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_AUTO_DETECTION_ENABLED;
-import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_GEO_DETECTION_ENABLED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,6 +30,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
+import android.os.Handler;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Slog;
@@ -45,15 +43,7 @@ import java.util.List;
import java.util.Objects;
/**
- * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
- * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
- * zone detection" setting.
- *
- * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each
- * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes
- * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation
- * changes and it is no longer possible to be confident about the time zone, slotIndexes must have
- * an empty suggestion submitted in order to "withdraw" their previous suggestion.
+ * The real implementation of {@link TimeZoneDetectorStrategy}.
*
* <p>Most public methods are marked synchronized to ensure thread safety around internal state.
*/
@@ -61,48 +51,27 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
- * / system properties. It can be faked for testing different scenarios.
+ * / system properties. It can be faked for testing.
*
* <p>Note: Because the settings / system properties-derived values can currently be modified
- * independently and from different threads (and processes!), their use are prone to race
- * conditions. That will be true until the responsibility for setting their values is moved to
- * {@link TimeZoneDetectorStrategyImpl} (which is thread safe).
+ * independently and from different threads (and processes!), their use is prone to race
+ * conditions.
*/
@VisibleForTesting
public interface Callback {
/**
- * Returns the capabilities for the user.
- */
- @NonNull
- TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
-
- /**
- * Returns the configuration for the user.
- * @param userId
+ * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
+ * changes that could affect time zone detection. This is invoked during system server
+ * setup.
*/
- @NonNull
- TimeZoneConfiguration getConfiguration(int userId);
+ void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
- /**
- * Sets the configuration for the user. This method handles storage only, the configuration
- * must have been validated by the caller and be complete.
- *
- * @throws IllegalArgumentException if {@link TimeZoneConfiguration#isComplete()}
- * returns {@code false}
- */
- void setConfiguration(@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
-
- /**
- * Returns true if automatic time zone detection is currently enabled.
- */
- boolean isAutoDetectionEnabled();
+ /** Returns the current user at the instant it is called. */
+ @UserIdInt int getCurrentUserId();
- /**
- * Returns whether geolocation can be used for time zone detection when {@link
- * #isAutoDetectionEnabled()} returns {@code true}.
- */
- boolean isGeoDetectionEnabled();
+ /** Returns the {@link ConfigurationInternal} for the specified user. */
+ ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
/**
* Returns true if the device has had an explicit time zone set.
@@ -118,6 +87,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* Sets the device's time zone.
*/
void setDeviceTimeZone(@NonNull String zoneId);
+
+ /**
+ * Stores the configuration properties contained in {@code newConfiguration}.
+ * All checks about user capabilities must be done by the caller and
+ * {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
+ */
+ void storeConfiguration(TimeZoneConfiguration newConfiguration);
}
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -189,9 +165,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@NonNull
private final Callback mCallback;
- /** Non-null after {@link #setStrategyListener(StrategyListener)} is called. */
- @Nullable
- private StrategyListener mListener;
+ @GuardedBy("this")
+ @NonNull
+ private List<ConfigurationChangeListener> mConfigChangeListeners = new ArrayList<>();
/**
* A log that records the decisions / decision metadata that affected the device's time zone.
@@ -211,7 +187,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
- * The latest geolocation suggestion received.
+ * The latest geolocation suggestion received. If the user disabled geolocation time zone
+ * detection then the latest suggestion is cleared.
*/
@GuardedBy("this")
private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
@@ -223,113 +200,120 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
- public static TimeZoneDetectorStrategyImpl create(Context context) {
- Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
- return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+ public static TimeZoneDetectorStrategyImpl create(
+ @NonNull Context context, @NonNull Handler handler,
+ boolean geolocationTimeZoneDetectionEnabled) {
+
+ TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
+ context, handler, geolocationTimeZoneDetectionEnabled);
+ return new TimeZoneDetectorStrategyImpl(callback);
}
@VisibleForTesting
- public TimeZoneDetectorStrategyImpl(Callback callback) {
+ public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
mCallback = Objects.requireNonNull(callback);
+ mCallback.setConfigChangeListener(this::handleConfigChanged);
}
/**
- * Sets a listener that allows the strategy to communicate with the surrounding service. This
- * must be called before the instance is used and must only be called once.
+ * Adds a listener that allows the strategy to communicate with the surrounding service /
+ * internal. This must be called before the instance is used.
*/
@Override
- public synchronized void setStrategyListener(@NonNull StrategyListener listener) {
- if (mListener != null) {
- throw new IllegalStateException("Strategy already has a listener");
- }
- mListener = Objects.requireNonNull(listener);
+ public synchronized void addConfigChangeListener(
+ @NonNull ConfigurationChangeListener listener) {
+ Objects.requireNonNull(listener);
+ mConfigChangeListeners.add(listener);
}
@Override
@NonNull
- public synchronized TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
- return mCallback.getCapabilities(userId);
+ public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+ return mCallback.getConfigurationInternal(userId);
}
@Override
@NonNull
- public synchronized TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
- return mCallback.getConfiguration(userId);
+ public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
+ int currentUserId = mCallback.getCurrentUserId();
+ return getConfigurationInternal(currentUserId);
}
@Override
public synchronized boolean updateConfiguration(
- @UserIdInt int userId, @NonNull TimeZoneConfiguration configurationChanges) {
- Objects.requireNonNull(configurationChanges);
-
- // Validate the requested configuration changes before applying any of them.
- TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
- boolean canManageTimeZoneDetection =
- capabilities.getConfigureAutoDetectionEnabled() >= CAPABILITY_NOT_APPLICABLE;
- if (!canManageTimeZoneDetection
- && containsAutoTimeDetectionProperties(configurationChanges)) {
+ @NonNull TimeZoneConfiguration requestedConfiguration) {
+ Objects.requireNonNull(requestedConfiguration);
+
+ int userId = requestedConfiguration.getUserId();
+ TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+
+ // Create a new configuration builder, and copy across the mutable properties users are
+ // able to modify. Other properties are therefore ignored.
+ final TimeZoneConfiguration newConfiguration =
+ capabilities.applyUpdate(requestedConfiguration);
+ if (newConfiguration == null) {
+ // The changes could not be made due to
return false;
}
- // Create a complete configuration by merging the existing and new (possibly partial)
- // configuration.
- final TimeZoneConfiguration oldConfiguration = mCallback.getConfiguration(userId);
- final TimeZoneConfiguration newConfiguration =
- new TimeZoneConfiguration.Builder(oldConfiguration)
- .mergeProperties(configurationChanges)
- .build();
-
- // Set the configuration / notify as needed.
- boolean configurationChanged = !oldConfiguration.equals(newConfiguration);
- if (configurationChanged) {
- mCallback.setConfiguration(userId, newConfiguration);
-
- String logMsg = "Configuration changed:"
- + "oldConfiguration=" + oldConfiguration
- + ", configuration=" + configurationChanges
- + ", newConfiguration=" + newConfiguration;
- mTimeZoneChangesLog.log(logMsg);
- if (DBG) {
- Slog.d(LOG_TAG, logMsg);
- }
- mListener.onConfigurationChanged();
+ // Store the configuration / notify as needed. This will cause the mCallback to invoke
+ // handleConfigChanged() asynchronously.
+ mCallback.storeConfiguration(newConfiguration);
+
+ TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
+ String logMsg = "Configuration changed:"
+ + " oldConfiguration=" + oldConfiguration
+ + ", newConfiguration=" + newConfiguration;
+ mTimeZoneChangesLog.log(logMsg);
+ if (DBG) {
+ Slog.d(LOG_TAG, logMsg);
}
return true;
}
- private static boolean containsAutoTimeDetectionProperties(
- @NonNull TimeZoneConfiguration configuration) {
- return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
- || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
- }
-
@Override
public synchronized void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion suggestion) {
+
+ int currentUserId = mCallback.getCurrentUserId();
+ ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
if (DBG) {
- Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+ Slog.d(LOG_TAG, "Geolocation suggestion received."
+ + " currentUserConfig=" + currentUserConfig
+ + " newSuggestion=" + suggestion);
}
-
Objects.requireNonNull(suggestion);
- mLatestGeoLocationSuggestion.set(suggestion);
- // Now perform auto time zone detection. The new suggestion may be used to modify the time
- // zone setting.
- if (mCallback.isGeoDetectionEnabled()) {
+ if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
+ // Only store a geolocation suggestion if geolocation detection is currently enabled.
+ mLatestGeoLocationSuggestion.set(suggestion);
+
+ // Now perform auto time zone detection. The new suggestion may be used to modify the
+ // time zone setting.
String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
- doAutoTimeZoneDetection(reason);
+ doAutoTimeZoneDetection(currentUserConfig, reason);
}
}
@Override
public synchronized boolean suggestManualTimeZone(
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
+
+ int currentUserId = mCallback.getCurrentUserId();
+ if (userId != currentUserId) {
+ 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);
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
- TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
+ TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ", capabilities=" + capabilities
@@ -345,8 +329,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@Override
public synchronized void suggestTelephonyTimeZone(
@NonNull TelephonyTimeZoneSuggestion suggestion) {
+
+ int currentUserId = mCallback.getCurrentUserId();
+ ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
if (DBG) {
- Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion);
+ Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
+ + " newSuggestion=" + suggestion);
}
Objects.requireNonNull(suggestion);
@@ -360,9 +348,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// Now perform auto time zone detection. The new suggestion may be used to modify the time
// zone setting.
- if (!mCallback.isGeoDetectionEnabled()) {
+ if (!currentUserConfig.getGeoDetectionEnabledBehavior()) {
String reason = "New telephony time zone suggested. suggestion=" + suggestion;
- doAutoTimeZoneDetection(reason);
+ doAutoTimeZoneDetection(currentUserConfig, reason);
}
}
@@ -392,15 +380,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* Performs automatic time zone detection.
*/
@GuardedBy("this")
- private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
- if (!mCallback.isAutoDetectionEnabled()) {
- // Avoid doing unnecessary work with this (race-prone) check.
+ private void doAutoTimeZoneDetection(
+ @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
+ if (!currentUserConfig.getAutoDetectionEnabledBehavior()) {
+ // Avoid doing unnecessary work.
return;
}
- // Use the right suggestions based on the current configuration. This check is potentially
- // race-prone until this value is set via a call to TimeZoneDetectorStrategy.
- if (mCallback.isGeoDetectionEnabled()) {
+ // Use the right suggestions based on the current configuration.
+ if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
doGeolocationTimeZoneDetection(detectionReason);
} else {
doTelephonyTimeZoneDetection(detectionReason);
@@ -480,35 +468,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
// zone ID.
- String newZoneId = bestTelephonySuggestion.suggestion.getZoneId();
- if (newZoneId == null) {
+ String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
+ if (zoneId == null) {
Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
+ " detectionReason=" + detectionReason);
return;
}
- String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
String cause = "Found good suggestion."
+ ", bestTelephonySuggestion=" + bestTelephonySuggestion
+ ", detectionReason=" + detectionReason;
- setAutoDeviceTimeZoneIfRequired(zoneId, cause);
- }
-
- @GuardedBy("this")
- private void setAutoDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
- Objects.requireNonNull(newZoneId);
- Objects.requireNonNull(cause);
-
- if (!mCallback.isAutoDetectionEnabled()) {
- if (DBG) {
- Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
- + ", newZoneId=" + newZoneId
- + ", cause=" + cause);
- }
- return;
- }
- setDeviceTimeZoneIfRequired(newZoneId, cause);
+ setDeviceTimeZoneIfRequired(zoneId, cause);
}
@GuardedBy("this")
@@ -582,13 +553,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return findBestTelephonySuggestion();
}
- @Override
- public synchronized void handleAutoTimeZoneConfigChanged() {
+ private synchronized void handleConfigChanged() {
if (DBG) {
- Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
+ Slog.d(LOG_TAG, "handleConfigChanged()");
+ }
+
+ clearGeolocationSuggestionIfNeeded();
+
+ for (ConfigurationChangeListener listener : mConfigChangeListeners) {
+ listener.onChange();
+ }
+ }
+
+ @GuardedBy("this")
+ private void clearGeolocationSuggestionIfNeeded() {
+ // This method is called whenever the user changes or the config for any user changes. We
+ // don't know what happened, so we capture the current user's config, check to see if we
+ // need to clear state associated with a previous user, and rerun detection.
+ int currentUserId = mCallback.getCurrentUserId();
+ ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+
+ GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
+ mLatestGeoLocationSuggestion.get();
+ if (latestGeoLocationSuggestion != null
+ && !currentUserConfig.getGeoDetectionEnabledBehavior()) {
+ // The current user's config has geodetection disabled, so clear the latest suggestion.
+ // This is done to ensure we only ever keep a geolocation suggestion if the user has
+ // said it is ok to do so.
+ mLatestGeoLocationSuggestion.set(null);
+ mTimeZoneChangesLog.log(
+ "clearGeolocationSuggestionIfNeeded: Cleared latest Geolocation suggestion.");
}
- doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
+ doAutoTimeZoneDetection(currentUserConfig, "clearGeolocationSuggestionIfNeeded()");
}
@Override
@@ -604,11 +601,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
ipw.println("TimeZoneDetectorStrategy:");
ipw.increaseIndent(); // level 1
- ipw.println("mCallback.isAutoDetectionEnabled()=" + mCallback.isAutoDetectionEnabled());
+ int currentUserId = mCallback.getCurrentUserId();
+ ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
+ ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
+ ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
+ ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
- ipw.println("mCallback.isGeoDetectionEnabled()=" + mCallback.isGeoDetectionEnabled());
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
new file mode 100644
index 000000000000..d7ed96fd5833
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 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.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.TimeZoneCapabilities;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
+ * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ */
+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 test_unrestricted() {
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ {
+ ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(true)
+ .build();
+ assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+ assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+
+ {
+ ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+ assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+ assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+ }
+
+ /** Tests when {@link ConfigurationInternal#isUserConfigAllowed()} is false */
+ @Test
+ public void test_restricted() {
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(false)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ {
+ ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(true)
+ .build();
+ assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+ assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+
+ {
+ ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+ assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+ assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+ }
+
+ /** Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is false. */
+ @Test
+ public void test_autoDetectNotSupported() {
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(false)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ {
+ ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(true)
+ .build();
+ assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+ assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+ assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+ {
+ ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+ assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+ assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+ assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+ assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+ assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index e5e931115c05..4ef20829f2dc 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -18,6 +18,7 @@ package com.android.server.timezonedetector;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -32,56 +33,64 @@ import java.util.List;
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
- private StrategyListener mListener;
+ private ConfigurationChangeListener mConfigurationChangeListener;
// Fake state
- private TimeZoneCapabilities mCapabilities;
- private TimeZoneConfiguration mConfiguration;
+ private ConfigurationInternal mConfigurationInternal;
// Call tracking.
private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
private ManualTimeZoneSuggestion mLastManualSuggestion;
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
- private boolean mHandleAutoTimeZoneConfigChangedCalled;
private boolean mDumpCalled;
private final List<Dumpable> mDumpables = new ArrayList<>();
@Override
- public void setStrategyListener(@NonNull StrategyListener listener) {
- mListener = listener;
+ public void addConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+ if (mConfigurationChangeListener != null) {
+ fail("Fake only supports one listener");
+ }
+ mConfigurationChangeListener = listener;
+ }
+
+ @Override
+ public ConfigurationInternal getConfigurationInternal(int userId) {
+ if (mConfigurationInternal.getUserId() != userId) {
+ fail("Fake only supports one user");
+ }
+ return mConfigurationInternal;
}
@Override
- public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
- return mCapabilities;
+ public ConfigurationInternal getCurrentUserConfigurationInternal() {
+ return mConfigurationInternal;
}
@Override
- public boolean updateConfiguration(
- @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
- assertNotNull(mConfiguration);
- assertNotNull(configuration);
-
- // Simulate the strategy's behavior: the new configuration will be the old configuration
- // merged with the new.
- TimeZoneConfiguration oldConfiguration = mConfiguration;
- TimeZoneConfiguration newConfiguration =
- new TimeZoneConfiguration.Builder(mConfiguration)
- .mergeProperties(configuration)
- .build();
-
- if (newConfiguration.equals(oldConfiguration)) {
+ public boolean updateConfiguration(@NonNull TimeZoneConfiguration 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.
+ TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
+ TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+ if (newConfiguration == null) {
return false;
}
- mConfiguration = newConfiguration;
- mListener.onConfigurationChanged();
+
+ if (!newConfiguration.equals(capabilities.getConfiguration())) {
+ mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
+
+ // Note: Unlike the real strategy, the listeners is invoked synchronously.
+ mConfigurationChangeListener.onChange();
+ }
return true;
}
- @Override
- @NonNull
- public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
- return mConfiguration;
+ public void simulateConfigurationChangeForTests() {
+ mConfigurationChangeListener.onChange();
}
@Override
@@ -103,11 +112,6 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
}
@Override
- public void handleAutoTimeZoneConfigChanged() {
- mHandleAutoTimeZoneConfigChangedCalled = true;
- }
-
- @Override
public void addDumpable(Dumpable dumpable) {
mDumpables.add(dumpable);
}
@@ -117,19 +121,14 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
mDumpCalled = true;
}
- void initializeConfiguration(TimeZoneConfiguration configuration) {
- mConfiguration = configuration;
- }
-
- void initializeCapabilities(TimeZoneCapabilities capabilities) {
- mCapabilities = capabilities;
+ void initializeConfiguration(ConfigurationInternal configurationInternal) {
+ mConfigurationInternal = configurationInternal;
}
void resetCallTracking() {
mLastGeolocationSuggestion = null;
mLastManualSuggestion = null;
mLastTelephonySuggestion = null;
- mHandleAutoTimeZoneConfigChangedCalled = false;
mDumpCalled = false;
}
@@ -146,10 +145,6 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
assertEquals(expectedSuggestion, mLastTelephonySuggestion);
}
- void verifyHandleAutoTimeZoneConfigChangedCalled() {
- assertTrue(mHandleAutoTimeZoneConfigChangedCalled);
- }
-
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
new file mode 100644
index 000000000000..f45b3a822f1a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.UserIdInt;
+
+/** A fake {@link CallerIdentityInjector} used in tests. */
+public class TestCallerIdentityInjector implements CallerIdentityInjector {
+
+ private long mToken = 9999L;
+ private int mCallingUserId;
+ private Integer mCurrentCallingUserId;
+
+ public void initializeCallingUserId(@UserIdInt int userId) {
+ mCallingUserId = userId;
+ mCurrentCallingUserId = userId;
+ }
+
+ @Override
+ public int getCallingUserId() {
+ assertNotNull("callingUserId has been cleared", mCurrentCallingUserId);
+ return mCurrentCallingUserId;
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ mCurrentCallingUserId = null;
+ return mToken;
+ }
+
+ @Override
+ public void restoreCallingIdentity(long token) {
+ assertEquals(token, mToken);
+ mCurrentCallingUserId = mCallingUserId;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index e9d57e52ce69..918babca677e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,6 +16,7 @@
package com.android.server.timezonedetector;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
@@ -85,6 +86,18 @@ public class TimeZoneDetectorInternalImplTest {
mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
}
+ @Test
+ public void testAddConfigurationListener() throws Exception {
+ boolean[] changeCalled = new boolean[2];
+ mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[0] = true);
+ mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[1] = true);
+
+ mFakeTimeZoneDetectorStrategy.simulateConfigurationChangeForTests();
+
+ assertTrue(changeCalled[0]);
+ assertTrue(changeCalled[1]);
+ }
+
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 3a1ec4f90d7a..27b04b6ab17d 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,8 +16,6 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -36,7 +34,6 @@ import static org.mockito.Mockito.when;
import android.app.timezonedetector.ITimeZoneConfigurationListener;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -65,6 +62,7 @@ public class TimeZoneDetectorServiceTest {
private TimeZoneDetectorService mTimeZoneDetectorService;
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
+ private TestCallerIdentityInjector mTestCallerIdentityInjector;
@Before
@@ -76,10 +74,14 @@ public class TimeZoneDetectorServiceTest {
mHandlerThread.start();
mTestHandler = new TestHandler(mHandlerThread.getLooper());
+ mTestCallerIdentityInjector = new TestCallerIdentityInjector();
+ mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
+
mFakeTimeZoneDetectorStrategy = new FakeTimeZoneDetectorStrategy();
mTimeZoneDetectorService = new TimeZoneDetectorService(
- mMockContext, mTestHandler, mFakeTimeZoneDetectorStrategy);
+ mMockContext, mTestHandler, mTestCallerIdentityInjector,
+ mFakeTimeZoneDetectorStrategy);
}
@After
@@ -107,40 +109,12 @@ public class TimeZoneDetectorServiceTest {
public void testGetCapabilities() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
- TimeZoneCapabilities capabilities = createTimeZoneCapabilities();
- mFakeTimeZoneDetectorStrategy.initializeCapabilities(capabilities);
-
- assertEquals(capabilities, mTimeZoneDetectorService.getCapabilities());
-
- verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
- anyString());
- }
-
- @Test(expected = SecurityException.class)
- public void testGetConfiguration_withoutPermission() {
- doThrow(new SecurityException("Mock"))
- .when(mMockContext).enforceCallingPermission(anyString(), any());
-
- try {
- mTimeZoneDetectorService.getConfiguration();
- fail();
- } finally {
- verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
- anyString());
- }
- }
-
- @Test
- public void testGetConfiguration() {
- doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
-
- TimeZoneConfiguration configuration =
- createTimeZoneConfiguration(false /* autoDetectionEnabled */);
+ ConfigurationInternal configuration =
+ createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
- assertEquals(configuration, mTimeZoneDetectorService.getConfiguration());
+ assertEquals(configuration.createCapabilities(),
+ mTimeZoneDetectorService.getCapabilities());
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
@@ -181,10 +155,9 @@ public class TimeZoneDetectorServiceTest {
@Test
public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
- TimeZoneConfiguration autoDetectDisabledConfiguration =
- createTimeZoneConfiguration(false /* autoDetectionEnabled */);
-
- mFakeTimeZoneDetectorStrategy.initializeConfiguration(autoDetectDisabledConfiguration);
+ ConfigurationInternal initialConfiguration =
+ createConfigurationInternal(false /* autoDetectionEnabled */);
+ mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
@@ -210,13 +183,12 @@ public class TimeZoneDetectorServiceTest {
// Simulate the configuration being changed and verify the mockListener was notified.
TimeZoneConfiguration autoDetectEnabledConfiguration =
createTimeZoneConfiguration(true /* autoDetectionEnabled */);
-
mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
anyString());
- verify(mockListener).onChange(autoDetectEnabledConfiguration);
+ verify(mockListener).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
@@ -242,12 +214,14 @@ public class TimeZoneDetectorServiceTest {
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+ TimeZoneConfiguration autoDetectDisabledConfiguration =
+ createTimeZoneConfiguration(false /* autoDetectionEnabled */);
mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
anyString());
- verify(mockListener, never()).onChange(any());
+ verify(mockListener, never()).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
@@ -379,33 +353,22 @@ public class TimeZoneDetectorServiceTest {
mFakeTimeZoneDetectorStrategy.verifyDumpCalled();
}
- @Test
- public void testHandleAutoTimeZoneConfigChanged() throws Exception {
- mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
- mTestHandler.assertTotalMessagesEnqueued(1);
- mTestHandler.waitForMessagesToBeProcessed();
- mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
-
- mFakeTimeZoneDetectorStrategy.resetCallTracking();
-
- mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
- mTestHandler.assertTotalMessagesEnqueued(2);
- mTestHandler.waitForMessagesToBeProcessed();
- mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
- }
-
- private static TimeZoneConfiguration createTimeZoneConfiguration(
- boolean autoDetectionEnabled) {
- return new TimeZoneConfiguration.Builder()
+ private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
+ return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
}
- private static TimeZoneCapabilities createTimeZoneCapabilities() {
- return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+ private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
+ // Default geo detection settings from auto detection settings - they are not important to
+ // the tests.
+ final boolean geoDetectionEnabled = autoDetectionEnabled;
+ return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setAutoDetectionSupported(true)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabled(autoDetectionEnabled)
+ .setLocationEnabled(geoDetectionEnabled)
+ .setGeoDetectionEnabled(geoDetectionEnabled)
.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index a6caa4299ef6..2bee5e5e7295 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -23,10 +23,6 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYP
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
@@ -37,9 +33,9 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -47,7 +43,6 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
@@ -61,7 +56,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -94,192 +88,149 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_HIGHEST),
};
- private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
- new TimeZoneConfiguration.Builder()
+ private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setUserConfigAllowed(false)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(false)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(false)
+ .build();
+
+ private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setUserConfigAllowed(false)
+ .setAutoDetectionSupported(true)
.setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
.build();
- private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
- new TimeZoneConfiguration.Builder()
+ private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(false)
.setAutoDetectionEnabled(false)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(false)
.build();
- private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
- new TimeZoneConfiguration.Builder()
+ private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(false)
+ .setLocationEnabled(true)
.setGeoDetectionEnabled(false)
.build();
- private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
- new TimeZoneConfiguration.Builder()
+ private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_ENABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(true)
+ .setAutoDetectionEnabled(false)
+ .setLocationEnabled(true)
.setGeoDetectionEnabled(true)
.build();
+ private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setAutoDetectionSupported(true)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(false)
+ .build();
+
+ private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
+ new ConfigurationInternal.Builder(USER_ID)
+ .setAutoDetectionSupported(true)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+
+ private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
+ createConfig(false /* autoDetection */, null);
+ private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
+ createConfig(true /* autoDetection */, null);
+ private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+ createConfig(null, true /* geoDetection */);
+ private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+ createConfig(null, false /* geoDetection */);
+
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
private FakeCallback mFakeCallback;
- private MockStrategyListener mMockStrategyListener;
+ private MockConfigChangeListener mMockConfigChangeListener;
+
@Before
public void setUp() {
mFakeCallback = new FakeCallback();
- mMockStrategyListener = new MockStrategyListener();
+ mMockConfigChangeListener = new MockConfigChangeListener();
mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeCallback);
- mFakeCallback.setStrategyForSettingsCallbacks(mTimeZoneDetectorStrategy);
- mTimeZoneDetectorStrategy.setStrategyListener(mMockStrategyListener);
- }
-
- @Test
- public void testGetCapabilities() {
- new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
+ mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
}
@Test
- public void testGetConfiguration() {
- new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
- assertTrue(expectedConfiguration.isComplete());
- assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
- }
-
- @Test
- public void testCapabilitiesTestInfra_unrestricted() {
- Script script = new Script();
-
- script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
- }
-
- script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- }
- }
-
- @Test
- public void testCapabilitiesTestInfra_restricted() {
- Script script = new Script();
-
- script.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- }
-
- script.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- }
- }
-
- @Test
- public void testCapabilitiesTestInfra_autoDetectNotSupported() {
- Script script = new Script();
-
- script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- }
-
- script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
- {
- // Check the fake test infra is doing what is expected.
- TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- }
+ public void testGetCurrentUserConfiguration() {
+ new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
+ ConfigurationInternal expectedConfiguration =
+ mFakeCallback.getConfigurationInternal(USER_ID);
+ assertEquals(expectedConfiguration,
+ mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
}
@Test
public void testUpdateConfiguration_unrestricted() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Set the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
// Nothing should have happened: it was initialized in this state.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
- script.verifyConfigurationChangedAndReset(USER_ID,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
- script.verifyConfigurationChangedAndReset(USER_ID,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
+ CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
- script.verifyConfigurationChangedAndReset(USER_ID,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
+ script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
}
@Test
public void testUpdateConfiguration_restricted() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
- // Update the configuration to enable geolocation time zone detection.
+ // Try to update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
+ CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -287,20 +238,16 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testUpdateConfiguration_autoDetectNotSupported() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -313,8 +260,7 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
createEmptySlotIndex2Suggestion();
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -359,9 +305,7 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTestCase testCase2 = newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// A low quality suggestions will not be taken: The device time zone setting is left
// uninitialized.
@@ -426,8 +370,7 @@ public class TimeZoneDetectorStrategyImplTest {
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
// Start with the device in a known state.
- script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ script.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
TelephonyTimeZoneSuggestion suggestion =
@@ -447,8 +390,7 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting on should cause the device setting to be set.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
- true /* expectedResult */);
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
// When time zone detection is already enabled the suggestion (if it scores highly
// enough) should be set immediately.
@@ -465,8 +407,7 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting should off should do nothing.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Assert internal service state.
@@ -480,8 +421,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testTelephonySuggestionsSingleSlotId() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -546,8 +486,7 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_NONE);
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
// Initialize the latest suggestions as empty so we don't need to worry about nulls
// below for the first loop.
@@ -632,9 +571,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
@Test
public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
TelephonyTestCase testCase = newTelephonyTestCase(
MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -652,40 +589,39 @@ public class TimeZoneDetectorStrategyImplTest {
// Toggling time zone detection should set the device time zone only if the current setting
// value is different from the most recent telephony suggestion.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Simulate a user turning auto detection off, a new suggestion being made while auto
// detection is off, and the user turning it on again.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
.simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
.verifyTimeZoneNotChanged();
// Latest suggestion should be used.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(newYorkSuggestion);
}
@Test
- public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
- checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+ public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoTelephony() {
+ checkManualSuggestion_unrestricted_autoDetectionEnabled(false /* geoDetectionEnabled */);
}
@Test
- public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
- checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+ public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoGeo() {
+ checkManualSuggestion_unrestricted_autoDetectionEnabled(true /* geoDetectionEnabled */);
}
- private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
- TimeZoneConfiguration geoTzEnabledConfig =
- new TimeZoneConfiguration.Builder()
+ private void checkManualSuggestion_unrestricted_autoDetectionEnabled(
+ boolean geoDetectionEnabled) {
+ ConfigurationInternal geoTzEnabledConfig =
+ new ConfigurationInternal.Builder(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.setGeoDetectionEnabled(geoDetectionEnabled)
.build();
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
+ .initializeConfig(geoTzEnabledConfig)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -697,35 +633,19 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
- // Auto time zone detection is enabled so the manual suggestion should be ignored.
+ // User is restricted so the manual suggestion should be ignored.
script.simulateManualTimeZoneSuggestion(
USER_ID, createManualSuggestion("Europe/Paris"), false /* expectedResult */)
- .verifyTimeZoneNotChanged();
- }
-
- @Test
- public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
-
- // Auto time zone detection is enabled so the manual suggestion should be ignored.
- ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
- script.simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, true /* expectedResult */)
- .verifyTimeZoneChangedAndReset(manualSuggestion);
+ .verifyTimeZoneNotChanged();
}
@Test
public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Auto time zone detection is disabled so the manual suggestion should be used.
@@ -738,8 +658,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Restricted users do not have the capability.
@@ -750,10 +669,9 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Test
- public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
+ public void testManualSuggestion_autoDetectNotSupported() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Unrestricted users have the capability.
@@ -765,9 +683,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_uncertain() {
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
@@ -783,8 +699,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_noZones() {
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
@@ -802,8 +717,7 @@ public class TimeZoneDetectorStrategyImplTest {
createGeoLocationSuggestion(list("Europe/London"));
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
script.simulateGeolocationTimeZoneSuggestion(suggestion)
@@ -828,8 +742,7 @@ public class TimeZoneDetectorStrategyImplTest {
createGeoLocationSuggestion(list("Europe/Paris"));
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
@@ -856,72 +769,27 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
}
- /**
- * Confirms that toggling the auto time zone detection enabled setting has the expected behavior
- * when the strategy is "opinionated" and "un-opinionated" when in geolocation detection is
- * enabled.
- */
@Test
- public void testTogglingAutoDetectionEnabled_autoGeo() {
- GeolocationTimeZoneSuggestion geolocationSuggestion =
+ public void testGeoSuggestion_togglingGeoDetectionClearsLastSuggestion() {
+ GeolocationTimeZoneSuggestion suggestion =
createGeoLocationSuggestion(list("Europe/London"));
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeoLocationSuggestion();
- ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
- script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion);
-
- // When time zone detection is not enabled, the time zone suggestion will not be set.
- script.verifyTimeZoneNotChanged();
-
- // Assert internal service state.
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
-
- // Toggling the time zone setting on should cause the device setting to be set.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateGeolocationTimeZoneSuggestion(suggestion)
.verifyTimeZoneChangedAndReset("Europe/London");
- // Toggling the time zone setting should off should do nothing because the device is now
- // set to that time zone.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged();
-
- // Now toggle auto time zone setting, and confirm it is opinionated.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
- .simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, true /* expectedResult */)
- .verifyTimeZoneChangedAndReset(manualSuggestion)
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
- .verifyTimeZoneChangedAndReset("Europe/London");
+ // Assert internal service state.
+ assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
- // Now withdraw the geolocation suggestion, and assert the strategy is no longer
- // opinionated.
- /* expectedResult */
- script.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
- .verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged()
- .simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, true /* expectedResult */)
- .verifyTimeZoneChangedAndReset(manualSuggestion)
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged();
+ // Turn off geo detection and verify the latest suggestion is cleared.
+ script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+ .verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Assert internal service state.
- assertEquals(uncertainGeolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
}
/**
@@ -937,88 +805,48 @@ public class TimeZoneDetectorStrategyImplTest {
"Europe/Paris");
Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Add suggestions. Nothing should happen as time zone detection is disabled.
script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
.verifyTimeZoneNotChanged();
+
+ // Geolocation suggestions are only stored when geolocation detection is enabled.
+ assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged();
- // Assert internal service state.
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // Telephony suggestions are always stored.
assertEquals(telephonySuggestion,
mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
// disabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
- // Changing the detection to enable geo detection should cause the device tz setting to
- // change to the geo suggestion.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+ // Changing the detection to enable geo detection won't cause the device tz setting to
+ // change because the geo suggestion is empty.
+ script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+ .verifyTimeZoneNotChanged()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
.verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
- script.simulateUpdateConfiguration(
- USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
- }
- /**
- * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
- * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
- * current setting.
- */
- @Test
- public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting_autoGeo() {
- GeolocationTimeZoneSuggestion losAngelesSuggestion =
- createGeoLocationSuggestion(list("America/Los_Angeles"));
- GeolocationTimeZoneSuggestion newYorkSuggestion =
- createGeoLocationSuggestion(list("America/New_York"));
-
- Script script = new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
-
- // Initialization.
- script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
- .verifyTimeZoneChangedAndReset("America/Los_Angeles");
- // Suggest it again - it should not be set because it is already set.
- script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
- .verifyTimeZoneNotChanged();
-
- // Toggling time zone detection should set the device time zone only if the current setting
- // value is different from the most recent telephony suggestion.
- /* expectedResult */
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(
- USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
- .verifyTimeZoneNotChanged();
-
- // Simulate a user turning auto detection off, a new suggestion being made while auto
- // detection is off, and the user turning it on again.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
- .simulateGeolocationTimeZoneSuggestion(newYorkSuggestion)
- .verifyTimeZoneNotChanged();
- // Latest suggestion should be used.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
- .verifyTimeZoneChangedAndReset("America/New_York");
+ assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
}
@Test
public void testAddDumpable() {
new Script()
- .initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -1069,25 +897,26 @@ public class TimeZoneDetectorStrategyImplTest {
return suggestion;
}
+ private static TimeZoneConfiguration createConfig(
+ @Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
+ TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+ if (autoDetection != null) {
+ builder.setAutoDetectionEnabled(autoDetection);
+ }
+ if (geoDetection != null) {
+ builder.setGeoDetectionEnabled(geoDetection);
+ }
+ return builder.build();
+ }
+
static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
- private TimeZoneCapabilities mCapabilities;
- private final TestState<UserConfiguration> mConfiguration = new TestState<>();
+ private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
private final TestState<String> mTimeZoneId = new TestState<>();
- private TimeZoneDetectorStrategyImpl mStrategy;
-
- void setStrategyForSettingsCallbacks(TimeZoneDetectorStrategyImpl strategy) {
- assertNotNull(strategy);
- mStrategy = strategy;
- }
+ private ConfigurationChangeListener mConfigChangeListener;
- void initializeUser(@UserIdInt int userId, TimeZoneCapabilities capabilities,
- TimeZoneConfiguration configuration) {
- assertEquals(userId, capabilities.getUserId());
- mCapabilities = capabilities;
- assertTrue("Configuration must be complete when initializing, config=" + configuration,
- configuration.isComplete());
- mConfiguration.init(new UserConfiguration(userId, configuration));
+ void initializeConfig(ConfigurationInternal configurationInternal) {
+ mConfigurationInternal.init(configurationInternal);
}
void initializeTimeZoneSetting(String zoneId) {
@@ -1095,43 +924,22 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Override
- public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
- assertEquals(userId, mCapabilities.getUserId());
- return mCapabilities;
+ public void setConfigChangeListener(ConfigurationChangeListener listener) {
+ mConfigChangeListener = listener;
}
@Override
- public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
- UserConfiguration latest = mConfiguration.getLatest();
- assertEquals(userId, latest.userId);
- return latest.configuration;
- }
-
- @Override
- public void setConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfig) {
- assertNotNull(newConfig);
- assertTrue(newConfig.isComplete());
-
- UserConfiguration latestUserConfig = mConfiguration.getLatest();
- assertEquals(userId, latestUserConfig.userId);
- TimeZoneConfiguration oldConfig = latestUserConfig.configuration;
-
- mConfiguration.set(new UserConfiguration(userId, newConfig));
-
- if (!newConfig.equals(oldConfig)) {
- // Simulate what happens when the auto detection configuration is changed.
- mStrategy.handleAutoTimeZoneConfigChanged();
+ public ConfigurationInternal getConfigurationInternal(int userId) {
+ ConfigurationInternal configuration = mConfigurationInternal.getLatest();
+ if (userId != configuration.getUserId()) {
+ fail("FakeCallback does not support multiple users.");
}
+ return configuration;
}
@Override
- public boolean isAutoDetectionEnabled() {
- return mConfiguration.getLatest().configuration.isAutoDetectionEnabled();
- }
-
- @Override
- public boolean isGeoDetectionEnabled() {
- return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+ public int getCurrentUserId() {
+ return mConfigurationInternal.getLatest().getUserId();
}
@Override
@@ -1149,9 +957,25 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneId.set(zoneId);
}
+ @Override
+ public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+ ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
+ if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+ fail("FakeCallback does not support multiple users");
+ }
+
+ ConfigurationInternal mergedConfiguration = oldConfiguration.merge(newConfiguration);
+ if (!mergedConfiguration.equals(oldConfiguration)) {
+ mConfigurationInternal.set(mergedConfiguration);
+
+ // Note: Unlike the real callback impl, the listener is invoked synchronously.
+ mConfigChangeListener.onChange();
+ }
+ }
+
void assertKnownUser(int userId) {
- assertEquals(userId, mCapabilities.getUserId());
- assertEquals(userId, mConfiguration.getLatest().userId);
+ assertEquals("FakeCallback does not support multiple users",
+ mConfigurationInternal.getLatest().getUserId(), userId);
}
void assertTimeZoneNotChanged() {
@@ -1166,43 +990,7 @@ public class TimeZoneDetectorStrategyImplTest {
void commitAllChanges() {
mTimeZoneId.commitLatest();
- mConfiguration.commitLatest();
- }
- }
-
- private static final class UserConfiguration {
- public final @UserIdInt int userId;
- public final TimeZoneConfiguration configuration;
-
- UserConfiguration(int userId, TimeZoneConfiguration configuration) {
- this.userId = userId;
- this.configuration = configuration;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- UserConfiguration that = (UserConfiguration) o;
- return userId == that.userId
- && Objects.equals(configuration, that.configuration);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userId, configuration);
- }
-
- @Override
- public String toString() {
- return "UserConfiguration{"
- + "userId=" + userId
- + ", configuration=" + configuration
- + '}';
+ mConfigurationInternal.commitLatest();
}
}
@@ -1255,64 +1043,14 @@ public class TimeZoneDetectorStrategyImplTest {
}
}
- /** Simulated user test cases. */
- enum UserCase {
- /** A catch-all for users that can set auto time zone config. */
- UNRESTRICTED,
- /** A catch-all for users that can't set auto time zone config. */
- RESTRICTED,
- /**
- * Like {@link #UNRESTRICTED}, but auto tz detection is not
- * supported on the device.
- */
- AUTO_DETECT_NOT_SUPPORTED,
- }
-
- /**
- * Creates a {@link TimeZoneCapabilities} object for a user in the specific role with the
- * supplied configuration.
- */
- private static TimeZoneCapabilities createCapabilities(
- int userId, UserCase userCase, TimeZoneConfiguration configuration) {
- switch (userCase) {
- case UNRESTRICTED: {
- int suggestManualTimeZoneCapability = configuration.isAutoDetectionEnabled()
- ? CAPABILITY_NOT_APPLICABLE : CAPABILITY_POSSESSED;
- return new TimeZoneCapabilities.Builder(userId)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(suggestManualTimeZoneCapability)
- .build();
- }
- case RESTRICTED: {
- return new TimeZoneCapabilities.Builder(userId)
- .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
- .build();
- }
- case AUTO_DETECT_NOT_SUPPORTED: {
- return new TimeZoneCapabilities.Builder(userId)
- .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
- .build();
- }
- default:
- throw new AssertionError(userCase + " not recognized");
- }
- }
-
/**
* A "fluent" class allows reuse of code in tests: initialization, simulation and verification
* logic.
*/
private class Script {
- Script initializeUser(
- @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
- TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
- mFakeCallback.initializeUser(userId, capabilities, configuration);
+ Script initializeConfig(ConfigurationInternal configuration) {
+ mFakeCallback.initializeConfig(configuration);
return this;
}
@@ -1326,10 +1064,9 @@ public class TimeZoneDetectorStrategyImplTest {
* the return value.
*/
Script simulateUpdateConfiguration(
- @UserIdInt int userId, TimeZoneConfiguration configuration,
- boolean expectedResult) {
+ TimeZoneConfiguration configuration, boolean expectedResult) {
assertEquals(expectedResult,
- mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
+ mTimeZoneDetectorStrategy.updateConfiguration(configuration));
return this;
}
@@ -1392,16 +1129,14 @@ public class TimeZoneDetectorStrategyImplTest {
/**
* Verifies that the configuration has been changed to the expected value.
*/
- Script verifyConfigurationChangedAndReset(
- @UserIdInt int userId, TimeZoneConfiguration expected) {
- mFakeCallback.mConfiguration.assertHasBeenSet();
- UserConfiguration expectedUserConfig = new UserConfiguration(userId, expected);
- assertEquals(expectedUserConfig, mFakeCallback.mConfiguration.getLatest());
+ Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
+ mFakeCallback.mConfigurationInternal.assertHasBeenSet();
+ assertEquals(expected, mFakeCallback.mConfigurationInternal.getLatest());
mFakeCallback.commitAllChanges();
// Also confirm the listener triggered.
- mMockStrategyListener.verifyOnConfigurationChangedCalled();
- mMockStrategyListener.reset();
+ mMockConfigChangeListener.verifyOnChangeCalled();
+ mMockConfigChangeListener.reset();
return this;
}
@@ -1410,10 +1145,10 @@ public class TimeZoneDetectorStrategyImplTest {
* {@link TimeZoneConfiguration} have been changed.
*/
Script verifyConfigurationNotChanged() {
- mFakeCallback.mConfiguration.assertHasNotBeenSet();
+ mFakeCallback.mConfigurationInternal.assertHasNotBeenSet();
// Also confirm the listener did not trigger.
- mMockStrategyListener.verifyOnConfigurationChangedNotCalled();
+ mMockConfigChangeListener.verifyOnChangeNotCalled();
return this;
}
@@ -1448,24 +1183,24 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
- private static class MockStrategyListener implements TimeZoneDetectorStrategy.StrategyListener {
- private boolean mOnConfigurationChangedCalled;
+ private static class MockConfigChangeListener implements ConfigurationChangeListener {
+ private boolean mOnChangeCalled;
@Override
- public void onConfigurationChanged() {
- mOnConfigurationChangedCalled = true;
+ public void onChange() {
+ mOnChangeCalled = true;
}
- void verifyOnConfigurationChangedCalled() {
- assertTrue(mOnConfigurationChangedCalled);
+ void verifyOnChangeCalled() {
+ assertTrue(mOnChangeCalled);
}
- void verifyOnConfigurationChangedNotCalled() {
- assertFalse(mOnConfigurationChangedCalled);
+ void verifyOnChangeNotCalled() {
+ assertFalse(mOnChangeCalled);
}
void reset() {
- mOnConfigurationChangedCalled = false;
+ mOnChangeCalled = false;
}
}