summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Neil Fuller <nfuller@google.com> 2020-08-11 15:42:55 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-08-11 15:42:55 +0000
commit103d2b3787129adfdb1c60fe4ce2ba159fcdb4c5 (patch)
tree6a1ab462d3999f3de1a095bbf9d2b572e4df1357
parentdee8006160a66810d6cb47f7801296bd9bb6a3cc (diff)
parent8164e67726c6ae438c85a1c60cced8c1e3c15f40 (diff)
Merge "Change TimeZoneDetectorStrategyImpl to allow geo"
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneCapabilities.java33
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneConfiguration.java35
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java20
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java49
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java20
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java3
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java1
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java7
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java6
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java134
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java512
14 files changed, 754 insertions, 127 deletions
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index 236b0064763e..cc0af3f97e49 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -91,11 +91,13 @@ public final class TimeZoneCapabilities implements Parcelable {
private final @UserIdInt int mUserId;
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.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
+ this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
}
@@ -103,6 +105,7 @@ public final class TimeZoneCapabilities implements Parcelable {
private static TimeZoneCapabilities createFromParcel(Parcel in) {
return new TimeZoneCapabilities.Builder(in.readInt())
.setConfigureAutoDetectionEnabled(in.readInt())
+ .setConfigureGeoDetectionEnabled(in.readInt())
.setSuggestManualTimeZone(in.readInt())
.build();
}
@@ -111,6 +114,7 @@ public final class TimeZoneCapabilities implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mUserId);
dest.writeInt(mConfigureAutoDetectionEnabled);
+ dest.writeInt(mConfigureGeoDetectionEnabled);
dest.writeInt(mSuggestManualTimeZone);
}
@@ -120,8 +124,8 @@ public final class TimeZoneCapabilities implements Parcelable {
}
/**
- * Returns the user's capability state for controlling automatic time zone detection via
- * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
+ * Returns the user's capability state for controlling whether automatic time zone detection is
+ * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
* TimeZoneConfiguration#isAutoDetectionEnabled()}.
*/
@CapabilityState
@@ -130,6 +134,16 @@ 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()}.
+ */
+ @CapabilityState
+ public int getConfigureGeoDetectionEnabled() {
+ return mConfigureGeoDetectionEnabled;
+ }
+
+ /**
* Returns the user's capability state for manually setting the time zone on a device via
* {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
*
@@ -157,12 +171,16 @@ public final class TimeZoneCapabilities implements Parcelable {
TimeZoneCapabilities that = (TimeZoneCapabilities) o;
return mUserId == that.mUserId
&& mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
+ && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
&& mSuggestManualTimeZone == that.mSuggestManualTimeZone;
}
@Override
public int hashCode() {
- return Objects.hash(mUserId, mConfigureAutoDetectionEnabled, mSuggestManualTimeZone);
+ return Objects.hash(mUserId,
+ mConfigureAutoDetectionEnabled,
+ mConfigureGeoDetectionEnabled,
+ mSuggestManualTimeZone);
}
@Override
@@ -170,6 +188,7 @@ public final class TimeZoneCapabilities implements Parcelable {
return "TimeZoneDetectorCapabilities{"
+ "mUserId=" + mUserId
+ ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
+ + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
+ ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
+ '}';
}
@@ -179,6 +198,7 @@ public final class TimeZoneCapabilities implements Parcelable {
private final @UserIdInt int mUserId;
private @CapabilityState int mConfigureAutoDetectionEnabled;
+ private @CapabilityState int mConfigureGeoDetectionEnabled;
private @CapabilityState int mSuggestManualTimeZone;
/**
@@ -194,6 +214,12 @@ public final class TimeZoneCapabilities implements Parcelable {
return this;
}
+ /** Sets the state for the geolocation time zone detection enabled config. */
+ public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
+ this.mConfigureGeoDetectionEnabled = value;
+ return this;
+ }
+
/** Sets the state for the suggestManualTimeZone action. */
public Builder setSuggestManualTimeZone(@CapabilityState int value) {
this.mSuggestManualTimeZone = value;
@@ -204,6 +230,7 @@ public final class TimeZoneCapabilities implements Parcelable {
@NonNull
public TimeZoneCapabilities build() {
verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
+ verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
return new TimeZoneCapabilities(this);
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 047d3493d5dd..6f84ee22a985 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -67,6 +67,10 @@ public final class TimeZoneConfiguration implements Parcelable {
@Property
public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+ /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
+ @Property
+ public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+
private final Bundle mBundle;
private TimeZoneConfiguration(Builder builder) {
@@ -86,7 +90,8 @@ public final class TimeZoneConfiguration implements Parcelable {
/** Returns {@code true} if all known properties are set. */
public boolean isComplete() {
- return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+ return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+ && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
}
/** Returns true if the specified property is set. */
@@ -108,6 +113,28 @@ public final class TimeZoneConfiguration implements Parcelable {
return mBundle.getBoolean(PROPERTY_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.
+ *
+ * @throws IllegalStateException if the field 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();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -174,6 +201,12 @@ public final class TimeZoneConfiguration implements Parcelable {
return this;
}
+ /** Sets the desired state of the geolocation time zone detection enabled property. */
+ public Builder setGeoDetectionEnabled(boolean enabled) {
+ this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+ return this;
+ }
+
/** Returns the {@link TimeZoneConfiguration}. */
@NonNull
public TimeZoneConfiguration build() {
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 8a36f1dc057b..72391f4d7dec 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -33,9 +33,11 @@ public class TimeZoneCapabilitiesTest {
public void testEquals() {
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
{
TimeZoneCapabilities one = builder1.build();
@@ -57,6 +59,20 @@ public class TimeZoneCapabilitiesTest {
assertEquals(one, two);
}
+ builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
@@ -76,12 +92,16 @@ public class TimeZoneCapabilitiesTest {
public void testParcelable() {
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
+ builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+
builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index ac7e9c437b63..00dc73ed269f 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -29,8 +29,9 @@ public class TimeZoneConfigurationTest {
@Test
public void testBuilder_copyConstructor() {
- TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder().setAutoDetectionEnabled(true);
+ TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true);
TimeZoneConfiguration configuration1 = builder1.build();
TimeZoneConfiguration configuration2 =
@@ -41,16 +42,15 @@ public class TimeZoneConfigurationTest {
@Test
public void testIsComplete() {
- TimeZoneConfiguration incompleteConfiguration =
- new TimeZoneConfiguration.Builder()
- .build();
- assertFalse(incompleteConfiguration.isComplete());
+ TimeZoneConfiguration.Builder builder =
+ new TimeZoneConfiguration.Builder();
+ assertFalse(builder.build().isComplete());
- TimeZoneConfiguration completeConfiguration =
- new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(true)
- .build();
- assertTrue(completeConfiguration.isComplete());
+ builder.setAutoDetectionEnabled(true);
+ assertFalse(builder.build().isComplete());
+
+ builder.setGeoDetectionEnabled(true);
+ assertTrue(builder.build().isComplete());
}
@Test
@@ -122,6 +122,27 @@ public class TimeZoneConfigurationTest {
TimeZoneConfiguration two = builder2.build();
assertEquals(one, two);
}
+
+ builder1.setGeoDetectionEnabled(true);
+ {
+ TimeZoneConfiguration one = builder1.build();
+ TimeZoneConfiguration two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder2.setGeoDetectionEnabled(false);
+ {
+ TimeZoneConfiguration one = builder1.build();
+ TimeZoneConfiguration two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setGeoDetectionEnabled(false);
+ {
+ TimeZoneConfiguration one = builder1.build();
+ TimeZoneConfiguration two = builder2.build();
+ assertEquals(one, two);
+ }
}
@Test
@@ -135,5 +156,11 @@ public class TimeZoneConfigurationTest {
builder.setAutoDetectionEnabled(false);
assertRoundTripParcelable(builder.build());
+
+ builder.setGeoDetectionEnabled(false);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setGeoDetectionEnabled(true);
+ assertRoundTripParcelable(builder.build());
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index c2b0d1067e38..0ca36e0fc258 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -72,6 +72,10 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
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
@@ -90,6 +94,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(isAutoDetectionEnabled())
+ .setGeoDetectionEnabled(isGeoDetectionEnabled())
.build();
}
@@ -105,8 +110,11 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
// 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 value = configuration.isAutoDetectionEnabled() ? 1 : 0;
- Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, value);
+ 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.
}
}
@@ -126,6 +134,14 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
}
@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;
+ }
+
+ @Override
public boolean isDeviceTimeZoneInitialized() {
// timezone.equals("GMT") will be true and only true if the time zone was
// set to a default value by the system server (when starting, system server
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 3d9ec6475a8b..fb7a73d12632 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
* The internal (in-process) system server API for the {@link
* com.android.server.timezonedetector.TimeZoneDetectorService}.
*
+ * <p>The methods on this class can be called from any thread.
* @hide
*/
public interface TimeZoneDetectorInternal extends Dumpable.Container {
@@ -29,7 +30,7 @@ public interface TimeZoneDetectorInternal extends Dumpable.Container {
/**
* 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
- * available, and so on.
+ * available, and so on. This method may be implemented asynchronously.
*/
void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 4464f7d136e3..15412a0d14a1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -58,6 +58,7 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
Objects.requireNonNull(timeZoneSuggestion);
+ // All strategy calls must take place on the mHandler thread.
mHandler.post(
() -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 7467439905eb..d81f949742bd 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -130,6 +130,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
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.
return service;
}
@@ -280,7 +284,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
void handleConfigurationChanged() {
// Note: we could trigger an async time zone detection operation here via a call to
- // handleAutoTimeZoneDetectionChanged(), but that is triggered in response to the underlying
+ // 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.
@@ -288,7 +292,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
// 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++) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f947a6554412..c5b7e39f4fef 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -30,9 +30,9 @@ import android.util.IndentingPrintWriter;
* <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
* and what to set it to.
*
- * <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 so
- * implementations mustvhandle thread safety.
+ * <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.
*
* @hide
*/
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 9c36c3921e3e..d1369a289428 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -23,6 +23,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_S
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;
@@ -98,6 +99,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
boolean isAutoDetectionEnabled();
/**
+ * Returns whether geolocation can be used for time zone detection when {@link
+ * #isAutoDetectionEnabled()} returns {@code true}.
+ */
+ boolean isGeoDetectionEnabled();
+
+ /**
* Returns true if the device has had an explicit time zone set.
*/
boolean isDeviceTimeZoneInitialized();
@@ -200,7 +207,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
*/
@GuardedBy("this")
private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion>
- mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ mTelephonySuggestionsBySlotIndex =
+ new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
+ /**
+ * The latest geolocation suggestion received.
+ */
+ @GuardedBy("this")
+ private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+ new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
@GuardedBy("this")
private final List<Dumpable> mDumpables = new ArrayList<>();
@@ -284,17 +299,26 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private static boolean containsAutoTimeDetectionProperties(
@NonNull TimeZoneConfiguration configuration) {
- return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+ return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+ || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
}
@Override
public synchronized void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+ }
+
Objects.requireNonNull(suggestion);
+ mLatestGeoLocationSuggestion.set(suggestion);
- // TODO Implement this.
- throw new UnsupportedOperationException(
- "Geo-location time zone detection is not currently implemented");
+ // Now perform auto time zone detection. The new suggestion may be used to modify the time
+ // zone setting.
+ if (mCallback.isGeoDetectionEnabled()) {
+ String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+ doAutoTimeZoneDetection(reason);
+ }
}
@Override
@@ -332,12 +356,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
new QualifiedTelephonyTimeZoneSuggestion(suggestion, score);
// Store the suggestion against the correct slotIndex.
- mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
+ mTelephonySuggestionsBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
// Now perform auto time zone detection. The new suggestion may be used to modify the time
// zone setting.
- String reason = "New telephony time suggested. suggestion=" + suggestion;
- doAutoTimeZoneDetection(reason);
+ if (!mCallback.isGeoDetectionEnabled()) {
+ String reason = "New telephony time zone suggested. suggestion=" + suggestion;
+ doAutoTimeZoneDetection(reason);
+ }
}
private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) {
@@ -363,9 +389,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
/**
- * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
- * quality and automatic time zone detection is enabled then it will be set on the device. The
- * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+ * Performs automatic time zone detection.
*/
@GuardedBy("this")
private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
@@ -374,6 +398,62 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
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()) {
+ doGeolocationTimeZoneDetection(detectionReason);
+ } else {
+ doTelephonyTimeZoneDetection(detectionReason);
+ }
+ }
+
+ /**
+ * Detects the time zone using the latest available geolocation time zone suggestion, if one is
+ * available. The outcome can be that this strategy becomes / remains un-opinionated and nothing
+ * is set.
+ */
+ @GuardedBy("this")
+ private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
+ GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
+ mLatestGeoLocationSuggestion.get();
+ if (latestGeolocationSuggestion == null) {
+ return;
+ }
+
+ List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+ if (zoneIds == null || zoneIds.isEmpty()) {
+ // This means the client has become uncertain about the time zone or it is certain there
+ // is no known zone. In either case we must leave the existing time zone setting as it
+ // is.
+ return;
+ }
+
+ // GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
+ // reliable.
+ String zoneId;
+
+ // Introduce bias towards the device's current zone when there are multiple zone suggested.
+ String deviceTimeZone = mCallback.getDeviceTimeZone();
+ if (zoneIds.contains(deviceTimeZone)) {
+ if (DBG) {
+ Slog.d(LOG_TAG,
+ "Geo tz suggestion contains current device time zone. Applying bias.");
+ }
+ zoneId = deviceTimeZone;
+ } else {
+ zoneId = zoneIds.get(0);
+ }
+ setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+ }
+
+ /**
+ * Detects the time zone using the latest available telephony time zone suggestions.
+ * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
+ * quality and automatic time zone detection is enabled then it will be set on the device. The
+ * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+ */
+ @GuardedBy("this")
+ private void doTelephonyTimeZoneDetection(@NonNull String detectionReason) {
QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion =
findBestTelephonySuggestion();
@@ -468,9 +548,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// slotIndex and find the best. Note that we deliberately do not look at age: the caller can
// rate-limit so age is not a strong indicator of confidence. Instead, the callers are
// expected to withdraw suggestions they no longer have confidence in.
- for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+ for (int i = 0; i < mTelephonySuggestionsBySlotIndex.size(); i++) {
QualifiedTelephonyTimeZoneSuggestion candidateSuggestion =
- mSuggestionBySlotIndex.valueAt(i);
+ mTelephonySuggestionsBySlotIndex.valueAt(i);
if (candidateSuggestion == null) {
// Unexpected
continue;
@@ -505,14 +585,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@Override
public synchronized void handleAutoTimeZoneConfigChanged() {
if (DBG) {
- Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
- }
- if (mCallback.isAutoDetectionEnabled()) {
- // When the user enabled time zone detection, run the time zone detection and change the
- // device time zone if possible.
- String reason = "Auto time zone detection setting enabled.";
- doAutoTimeZoneDetection(reason);
+ Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
}
+
+ doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
}
@Override
@@ -532,15 +608,21 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
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
mTimeZoneChangesLog.dump(ipw);
ipw.decreaseIndent(); // level 2
+ ipw.println("Geolocation suggestion history:");
+ ipw.increaseIndent(); // level 2
+ mLatestGeoLocationSuggestion.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
ipw.println("Telephony suggestion history:");
ipw.increaseIndent(); // level 2
- mSuggestionBySlotIndex.dump(ipw);
+ mTelephonySuggestionsBySlotIndex.dump(ipw);
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
@@ -555,7 +637,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@VisibleForTesting
public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
int slotIndex) {
- return mSuggestionBySlotIndex.get(slotIndex);
+ return mTelephonySuggestionsBySlotIndex.get(slotIndex);
+ }
+
+ /**
+ * A method used to inspect strategy state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+ return mLatestGeoLocationSuggestion.get();
}
/**
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 dcf319058ca2..e5e931115c05 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -27,6 +27,9 @@ import android.app.timezonedetector.TimeZoneCapabilities;
import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
private StrategyListener mListener;
@@ -41,6 +44,7 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
private boolean mHandleAutoTimeZoneConfigChangedCalled;
private boolean mDumpCalled;
+ private final List<Dumpable> mDumpables = new ArrayList<>();
@Override
public void setStrategyListener(@NonNull StrategyListener listener) {
@@ -105,7 +109,7 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@Override
public void addDumpable(Dumpable dumpable) {
- // Stubbed
+ mDumpables.add(dumpable);
}
@Override
@@ -149,4 +153,8 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
+
+ void verifyHasDumpable(Dumpable expected) {
+ assertTrue(mDumpables.contains(expected));
+ }
}
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 0e2c22756097..e9d57e52ce69 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -75,6 +75,16 @@ public class TimeZoneDetectorInternalImplTest {
mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
}
+ @Test
+ public void testAddDumpable() throws Exception {
+ Dumpable stubbedDumpable = mock(Dumpable.class);
+
+ mTimeZoneDetectorInternal.addDumpable(stubbedDumpable);
+ mTestHandler.assertTotalMessagesEnqueued(0);
+
+ mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
+ }
+
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 8034cacc6923..3a1ec4f90d7a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -52,6 +52,7 @@ import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
@@ -253,6 +254,39 @@ public class TimeZoneDetectorServiceTest {
}
@Test(expected = SecurityException.class)
+ public void testSuggestGeolocationTimeZone_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+ GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+ try {
+ mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+ fail();
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME_ZONE),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testSuggestGeolocationTimeZone() throws Exception {
+ doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+ GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+ mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+ mTestHandler.assertTotalMessagesEnqueued(1);
+
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME_ZONE),
+ anyString());
+
+ mTestHandler.waitForMessagesToBeProcessed();
+ mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
+ }
+
+ @Test(expected = SecurityException.class)
public void testSuggestManualTimeZone_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
@@ -346,7 +380,7 @@ public class TimeZoneDetectorServiceTest {
}
@Test
- public void testAutoTimeZoneDetectionChanged() throws Exception {
+ public void testHandleAutoTimeZoneConfigChanged() throws Exception {
mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
mTestHandler.assertTotalMessagesEnqueued(1);
mTestHandler.waitForMessagesToBeProcessed();
@@ -370,10 +404,15 @@ public class TimeZoneDetectorServiceTest {
private static TimeZoneCapabilities createTimeZoneCapabilities() {
return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
.setSuggestManualTimeZone(CAPABILITY_POSSESSED)
.build();
}
+ private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
+ return new GeolocationTimeZoneSuggestion(Arrays.asList("TestZoneId"));
+ }
+
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
return new ManualTimeZoneSuggestion("TestZoneId");
}
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 68554451e43a..a6caa4299ef6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -41,6 +41,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -56,10 +57,10 @@ import org.junit.Before;
import org.junit.Test;
import java.io.StringWriter;
-import java.util.ArrayList;
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;
@@ -93,16 +94,26 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_HIGHEST),
};
- private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED =
+ private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.build();
- private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED =
+ private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(false)
.build();
+ private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+ new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(false)
+ .build();
+
+ private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+ new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(true)
+ .build();
+
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
private FakeCallback mFakeCallback;
private MockStrategyListener mMockStrategyListener;
@@ -120,7 +131,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testGetCapabilities() {
new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
}
@@ -129,7 +140,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testGetConfiguration() {
new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
assertTrue(expectedConfiguration.isComplete());
assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
@@ -140,20 +151,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script();
script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ 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_TIME_ZONE_DETECTION_DISABLED);
+ 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());
}
}
@@ -163,20 +176,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script();
script.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ 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_TIME_ZONE_DETECTION_DISABLED);
+ 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());
}
}
@@ -186,20 +201,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script();
script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ 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_TIME_ZONE_DETECTION_DISABLED);
+ 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());
}
}
@@ -208,42 +225,61 @@ public class TimeZoneDetectorStrategyImplTest {
public void testUpdateConfiguration_unrestricted() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
// Set the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ script.simulateUpdateConfiguration(USER_ID, 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_TIME_ZONE_DETECTION_DISABLED);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
- script.verifyConfigurationChangedAndReset(
- USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+ script.verifyConfigurationChangedAndReset(USER_ID,
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
- script.verifyConfigurationChangedAndReset(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ script.verifyConfigurationChangedAndReset(USER_ID,
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+
+ // Update the configuration to enable geolocation time zone detection.
+ script.simulateUpdateConfiguration(
+ USER_ID, 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));
}
@Test
public void testUpdateConfiguration_restricted() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+ script.simulateUpdateConfiguration(
+ USER_ID, 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_TIME_ZONE_DETECTION_ENABLED);
+ script.simulateUpdateConfiguration(
+ USER_ID, 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.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -253,16 +289,18 @@ public class TimeZoneDetectorStrategyImplTest {
public void testUpdateConfiguration_autoDetectNotSupported() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+ script.simulateUpdateConfiguration(
+ USER_ID, 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_TIME_ZONE_DETECTION_ENABLED);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -276,7 +314,7 @@ public class TimeZoneDetectorStrategyImplTest {
createEmptySlotIndex2Suggestion();
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -308,6 +346,10 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
+ /**
+ * Telephony suggestions have quality metadata. Ordinarily, low scoring suggestions are not
+ * used, but this is not true if the device's time zone setting is uninitialized.
+ */
@Test
public void testTelephonySuggestionsWhenTimeZoneUninitialized() {
assertTrue(TELEPHONY_SCORE_LOW < TELEPHONY_SCORE_USAGE_THRESHOLD);
@@ -319,7 +361,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
// A low quality suggestions will not be taken: The device time zone setting is left
// uninitialized.
@@ -376,16 +418,16 @@ public class TimeZoneDetectorStrategyImplTest {
/**
* Confirms that toggling the auto time zone detection setting has the expected behavior when
- * the strategy is "opinionated".
+ * the strategy is "opinionated" when using telephony auto detection.
*/
@Test
- public void testTogglingAutoTimeZoneDetection() {
+ public void testTogglingAutoDetection_autoTelephony() {
Script script = new Script();
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
// Start with the device in a known state.
script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
TelephonyTimeZoneSuggestion suggestion =
@@ -405,7 +447,8 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting on should cause the device setting to be set.
- script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
+ true /* expectedResult */);
// When time zone detection is already enabled the suggestion (if it scores highly
// enough) should be set immediately.
@@ -422,7 +465,8 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting should off should do nothing.
- script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Assert internal service state.
@@ -437,7 +481,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testTelephonySuggestionsSingleSlotId() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -451,8 +495,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
// Each test case will have the same or lower score than the last.
- ArrayList<TelephonyTestCase> descendingCasesByScore =
- new ArrayList<>(Arrays.asList(TELEPHONY_TEST_CASES));
+ List<TelephonyTestCase> descendingCasesByScore = list(TELEPHONY_TEST_CASES);
Collections.reverse(descendingCasesByScore);
for (TelephonyTestCase testCase : descendingCasesByScore) {
@@ -504,7 +547,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_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.
@@ -583,15 +626,15 @@ public class TimeZoneDetectorStrategyImplTest {
}
/**
- * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing
- * the time zone is actually necessary. This test proves that the service doesn't assume it
- * knows the current setting.
+ * 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 settings.
*/
@Test
- public void testTelephonySuggestionTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+ public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
TelephonyTestCase testCase = newTelephonyTestCase(
MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -609,26 +652,40 @@ 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.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
- .simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+ .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.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
.verifyTimeZoneNotChanged();
// Latest suggestion should be used.
- script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(newYorkSuggestion);
}
@Test
- public void testManualSuggestion_unrestricted_simulateAutoTimeZoneEnabled() {
+ public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
+ checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+ }
+
+ @Test
+ public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
+ checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+ }
+
+ private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
+ TimeZoneConfiguration geoTzEnabledConfig =
+ new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(geoDetectionEnabled)
+ .build();
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -641,7 +698,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ 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.
@@ -654,7 +711,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+ 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.
@@ -668,7 +725,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Auto time zone detection is disabled so the manual suggestion should be used.
@@ -682,7 +739,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.RESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Restricted users do not have the capability.
@@ -696,7 +753,7 @@ public class TimeZoneDetectorStrategyImplTest {
public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
// Unrestricted users have the capability.
@@ -707,10 +764,261 @@ 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))
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
+
+ script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
+ .verifyTimeZoneNotChanged();
+
+ // Assert internal service state.
+ assertEquals(uncertainSuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ }
+
+ @Test
+ public void testGeoSuggestion_noZones() {
+ Script script = new Script()
+ .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
+
+ script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+ .verifyTimeZoneNotChanged();
+
+ // Assert internal service state.
+ assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ }
+
+ @Test
+ public void testGeoSuggestion_oneZone() {
+ GeolocationTimeZoneSuggestion suggestion =
+ createGeoLocationSuggestion(list("Europe/London"));
+
+ Script script = new Script()
+ .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ script.simulateGeolocationTimeZoneSuggestion(suggestion)
+ .verifyTimeZoneChangedAndReset("Europe/London");
+
+ // Assert internal service state.
+ assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ }
+
+ /**
+ * In the current implementation, the first zone ID is always used unless the device is set to
+ * one of the other options. This is "stickiness" - the device favors the zone it is currently
+ * set to until that unambiguously can't be correct.
+ */
+ @Test
+ public void testGeoSuggestion_multiZone() {
+ GeolocationTimeZoneSuggestion londonOnlySuggestion =
+ createGeoLocationSuggestion(list("Europe/London"));
+ GeolocationTimeZoneSuggestion londonOrParisSuggestion =
+ createGeoLocationSuggestion(list("Europe/Paris", "Europe/London"));
+ GeolocationTimeZoneSuggestion parisOnlySuggestion =
+ createGeoLocationSuggestion(list("Europe/Paris"));
+
+ Script script = new Script()
+ .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+ CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
+ .verifyTimeZoneChangedAndReset("Europe/London");
+ assertEquals(londonOnlySuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+ // Confirm bias towards the current device zone when there's multiple zones to choose from.
+ script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+ .verifyTimeZoneNotChanged();
+ assertEquals(londonOrParisSuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+ script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
+ .verifyTimeZoneChangedAndReset("Europe/Paris");
+ assertEquals(parisOnlySuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+ // Now the suggestion that previously left the device on Europe/London will leave the device
+ // on Europe/Paris.
+ script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+ .verifyTimeZoneNotChanged();
+ assertEquals(londonOrParisSuggestion,
+ 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 =
+ 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))
+ .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 */)
+ .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");
+
+ // 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();
+
+ // Assert internal service state.
+ assertEquals(uncertainGeolocationSuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ }
+
+ /**
+ * Confirms that changing the geolocation time zone detection enabled setting has the expected
+ * behavior, i.e. immediately recompute the detected time zone using different signals.
+ */
+ @Test
+ public void testChangingGeoDetectionEnabled() {
+ GeolocationTimeZoneSuggestion geolocationSuggestion =
+ createGeoLocationSuggestion(list("Europe/London"));
+ TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+ SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+ "Europe/Paris");
+
+ Script script = new Script()
+ .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ // Add suggestions. Nothing should happen as time zone detection is disabled.
+ script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .verifyTimeZoneNotChanged();
+ script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+ .verifyTimeZoneNotChanged();
+
+ // Assert internal service state.
+ assertEquals(geolocationSuggestion,
+ mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ 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 */)
+ .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 */)
+ .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 */)
+ .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");
+ }
+
+ @Test
public void testAddDumpable() {
new Script()
.initializeUser(USER_ID, UserCase.UNRESTRICTED,
- CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+ CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -733,6 +1041,15 @@ public class TimeZoneDetectorStrategyImplTest {
return new ManualTimeZoneSuggestion(zoneId);
}
+ private static TelephonyTimeZoneSuggestion createTelephonySuggestion(
+ int slotIndex, @MatchType int matchType, @Quality int quality, String zoneId) {
+ return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+ .setMatchType(matchType)
+ .setQuality(quality)
+ .setZoneId(zoneId)
+ .build();
+ }
+
private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() {
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build();
}
@@ -741,6 +1058,17 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
+ private static GeolocationTimeZoneSuggestion createUncertainGeoLocationSuggestion() {
+ return createGeoLocationSuggestion(null);
+ }
+
+ private static GeolocationTimeZoneSuggestion createGeoLocationSuggestion(
+ @Nullable List<String> zoneIds) {
+ GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
+ suggestion.addDebugInfo("Test suggestion");
+ return suggestion;
+ }
+
static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
private TimeZoneCapabilities mCapabilities;
@@ -757,7 +1085,8 @@ public class TimeZoneDetectorStrategyImplTest {
TimeZoneConfiguration configuration) {
assertEquals(userId, capabilities.getUserId());
mCapabilities = capabilities;
- assertTrue(configuration.isComplete());
+ assertTrue("Configuration must be complete when initializing, config=" + configuration,
+ configuration.isComplete());
mConfiguration.init(new UserConfiguration(userId, configuration));
}
@@ -790,11 +1119,8 @@ public class TimeZoneDetectorStrategyImplTest {
mConfiguration.set(new UserConfiguration(userId, newConfig));
if (!newConfig.equals(oldConfig)) {
- if (oldConfig.isAutoDetectionEnabled() != newConfig.isAutoDetectionEnabled()) {
- // Simulate what happens when the auto detection enabled configuration is
- // changed.
- mStrategy.handleAutoTimeZoneConfigChanged();
- }
+ // Simulate what happens when the auto detection configuration is changed.
+ mStrategy.handleAutoTimeZoneConfigChanged();
}
}
@@ -804,6 +1130,11 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Override
+ public boolean isGeoDetectionEnabled() {
+ return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+ }
+
+ @Override
public boolean isDeviceTimeZoneInitialized() {
return mTimeZoneId.getLatest() != null;
}
@@ -942,32 +1273,33 @@ public class TimeZoneDetectorStrategyImplTest {
* supplied configuration.
*/
private static TimeZoneCapabilities createCapabilities(
- int userId, UserCase userRole, TimeZoneConfiguration configuration) {
- switch (userRole) {
+ 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(userRole + " not recognized");
+ throw new AssertionError(userCase + " not recognized");
}
}
@@ -978,8 +1310,8 @@ public class TimeZoneDetectorStrategyImplTest {
private class Script {
Script initializeUser(
- @UserIdInt int userId, UserCase userRole, TimeZoneConfiguration configuration) {
- TimeZoneCapabilities capabilities = createCapabilities(userId, userRole, configuration);
+ @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
+ TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
mFakeCallback.initializeUser(userId, capabilities, configuration);
return this;
}
@@ -989,27 +1321,24 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
- Script simulateAutoTimeZoneDetectionEnabled(@UserIdInt int userId, boolean enabled) {
- TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(enabled)
- .build();
- return simulateUpdateConfiguration(userId, configuration);
- }
-
/**
- * Simulates the time zone detection strategy receiving an updated configuration.
+ * Simulates the time zone detection strategy receiving an updated configuration and checks
+ * the return value.
*/
Script simulateUpdateConfiguration(
- @UserIdInt int userId, TimeZoneConfiguration configuration) {
- mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+ @UserIdInt int userId, TimeZoneConfiguration configuration,
+ boolean expectedResult) {
+ assertEquals(expectedResult,
+ mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
return this;
}
/**
- * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+ * Simulates the time zone detection strategy receiving a geolocation-originated
+ * suggestion.
*/
- Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
- mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+ Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
+ mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
return this;
}
@@ -1024,13 +1353,26 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ /**
+ * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+ */
+ Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+ mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+ return this;
+ }
+
+ /**
+ * Confirms that the device's time zone has not been set by previous actions since the test
+ * state was last reset.
+ */
Script verifyTimeZoneNotChanged() {
mFakeCallback.assertTimeZoneNotChanged();
return this;
}
- Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
- mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+ /** Verifies the device's time zone has been set and clears change tracking history. */
+ Script verifyTimeZoneChangedAndReset(String zoneId) {
+ mFakeCallback.assertTimeZoneChangedTo(zoneId);
mFakeCallback.commitAllChanges();
return this;
}
@@ -1041,6 +1383,12 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
+ mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+ mFakeCallback.commitAllChanges();
+ return this;
+ }
+
/**
* Verifies that the configuration has been changed to the expected value.
*/
@@ -1120,4 +1468,8 @@ public class TimeZoneDetectorStrategyImplTest {
mOnConfigurationChangedCalled = false;
}
}
+
+ private static <T> List<T> list(T... values) {
+ return Arrays.asList(values);
+ }
}