Add a time field to GeolocationTimeZoneSuggestion

Add a time field to GeolocationTimeZoneSuggestion for use in upcoming
fallback logic. This is should help establish ordering across
different time zone detection mechanisms.

Bug: 197624972
Test: treehugger
Test: frameworks/base: atest services/tests/servicestests/src/com/android/server/timezonedetector/
Test: cts: atest hostsidetests/time/host/
Change-Id: I824679f3eca39a69d5413d365889ad8566d3e557
This commit is contained in:
Neil Fuller
2021-10-18 16:50:56 +01:00
parent 135703feac
commit 87655fd970
9 changed files with 295 additions and 135 deletions

View File

@@ -16,9 +16,11 @@
package com.android.server.timezonedetector;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ShellCommand;
import android.os.SystemClock;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -29,27 +31,39 @@ import java.util.Objects;
import java.util.StringTokenizer;
/**
* A time zone suggestion from a geolocation source.
* A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
* service.
*
* <p> Geolocation-based suggestions have the following properties:
* <p>Geolocation-based suggestions have the following properties:
*
* <ul>
* <li>{@code effectiveFromElapsedMillis}: The time according to the elapsed realtime clock
* after which the suggestion should be considered in effect. For example, when a location fix
* used to establish the time zone is old, then the suggestion
* {@code effectiveFromElapsedMillis} should reflect this and indicates the time zone that was
* detected / correct at that time. The time_zone_detector is only expected to use the latest
* suggestion it has received, and so later suggestions always counteract previous suggestions.
* The inclusion of this information means that the time_zone_detector can take into account
* ordering when comparing suggestions from different sources.
* <br />Note: Because the times can be back-dated, time_zone_detector can be sent a sequence of
* suggestions where the {@code effectiveFromElapsedMillis} of later suggestions is before
* the {@code effectiveFromElapsedMillis} of an earlier one.</li>
* <li>{@code zoneIds}. When not {@code null}, {@code zoneIds} contains a list of suggested time
* zone IDs, e.g. ["America/Phoenix", "America/Denver"]. Usually there will be a single zoneId.
* When there are multiple, this indicates multiple answers are possible for the current
* location / accuracy, i.e. if there is a nearby time zone border. The detection logic
* receiving the suggestion is expected to use the first element in the absence of other
* information, but one of the others may be used if there is supporting evidence / preferences
* such as a device setting or corroborating signals from another source.
* location / accuracy, e.g. if there is a nearby time zone border. The time_zone_detector is
* expected to use the first element in the absence of other information, but one of the other
* zone IDs may be used if there is supporting evidence / preferences such as a device setting
* or corroborating signals from another source.
* <br />{@code zoneIds} can be empty if the current location has been determined to have no
* time zone. For example, oceans or disputed areas. This is considered a strong signal and the
* received need not look for time zone from other sources.
* <br />{@code zoneIds} can be {@code null} to indicate that the geolocation source has entered
* an "un-opinionated" state and any previous suggestion is being withdrawn. This indicates the
* source cannot provide a valid suggestion due to technical limitations. For example, a
* geolocation source may become un-opinionated if the device's location is no longer known with
* sufficient accuracy, or if the location is known but no time zone can be determined because
* no time zone mapping information is available.</li>
* time_zone_detector need not look for time zone from other sources.
* <br />{@code zoneIds} can be {@code null} to indicate that the location_time_zone_manager has
* entered an "uncertain" state and any previous suggestion is being withdrawn. This indicates
* the location_time_zone_manager cannot provide a valid suggestion. For example, the
* location_time_zone_manager may become uncertain if components further downstream cannot
* determine the device's location with sufficient accuracy, or if the location is known but no
* time zone can be determined because no time zone mapping information is available.</li>
* <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
* used to record why the suggestion exists and how it was obtained. This information exists
* only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
@@ -61,10 +75,14 @@ import java.util.StringTokenizer;
*/
public final class GeolocationTimeZoneSuggestion {
@ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
@Nullable private final List<String> mZoneIds;
@Nullable private ArrayList<String> mDebugInfo;
public GeolocationTimeZoneSuggestion(@Nullable List<String> zoneIds) {
private GeolocationTimeZoneSuggestion(
@ElapsedRealtimeLong long effectiveFromElapsedMillis,
@Nullable List<String> zoneIds) {
mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
if (zoneIds == null) {
// Unopinionated
mZoneIds = null;
@@ -73,6 +91,34 @@ public final class GeolocationTimeZoneSuggestion {
}
}
/**
* Creates a "uncertain" suggestion instance.
*/
@NonNull
public static GeolocationTimeZoneSuggestion createUncertainSuggestion(
@ElapsedRealtimeLong long effectiveFromElapsedMillis) {
return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, null);
}
/**
* Creates a "certain" suggestion instance.
*/
@NonNull
public static GeolocationTimeZoneSuggestion createCertainSuggestion(
@ElapsedRealtimeLong long effectiveFromElapsedMillis,
@NonNull List<String> zoneIds) {
return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
}
/**
* Returns the "effective from" time associated with the suggestion. See {@link
* GeolocationTimeZoneSuggestion} for details.
*/
@ElapsedRealtimeLong
public long getEffectiveFromElapsedMillis() {
return mEffectiveFromElapsedMillis;
}
/**
* Returns the zone Ids being suggested. See {@link GeolocationTimeZoneSuggestion} for details.
*/
@@ -110,18 +156,20 @@ public final class GeolocationTimeZoneSuggestion {
}
GeolocationTimeZoneSuggestion
that = (GeolocationTimeZoneSuggestion) o;
return Objects.equals(mZoneIds, that.mZoneIds);
return mEffectiveFromElapsedMillis == that.mEffectiveFromElapsedMillis
&& Objects.equals(mZoneIds, that.mZoneIds);
}
@Override
public int hashCode() {
return Objects.hash(mZoneIds);
return Objects.hash(mEffectiveFromElapsedMillis, mZoneIds);
}
@Override
public String toString() {
return "GeolocationTimeZoneSuggestion{"
+ "mZoneIds=" + mZoneIds
+ "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
+ ", mZoneIds=" + mZoneIds
+ ", mDebugInfo=" + mDebugInfo
+ '}';
}
@@ -141,8 +189,11 @@ public final class GeolocationTimeZoneSuggestion {
}
}
}
long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
GeolocationTimeZoneSuggestion suggestion =
new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
suggestion.addDebugInfo("Command line injection");
return suggestion;
}

View File

@@ -16,7 +16,9 @@
package com.android.server.timezonedetector.location;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.os.SystemClock;
import com.android.server.LocalServices;
import com.android.server.timezonedetector.ConfigurationChangeListener;
@@ -83,4 +85,9 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
Duration getProviderEventFilteringAgeThreshold() {
return mServiceConfigAccessor.getLocationTimeZoneProviderEventFilteringAgeThreshold();
}
@Override
@ElapsedRealtimeLong long elapsedRealtimeMillis() {
return SystemClock.elapsedRealtime();
}
}

View File

@@ -31,10 +31,11 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -43,7 +44,6 @@ import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
/**
@@ -184,7 +184,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
// re-started).
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
"Providers are stopping");
mEnvironment.elapsedRealtimeMillis(), "Providers are stopping");
makeSuggestion(suggestion);
}
}
@@ -272,6 +272,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
// If both providers are {perm failed} then the controller immediately
// becomes uncertain.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
mEnvironment.elapsedRealtimeMillis(),
"Providers are failed:"
+ " primary=" + mPrimaryProvider.getCurrentState()
+ " secondary=" + mPrimaryProvider.getCurrentState());
@@ -410,6 +411,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
// If both providers are now terminated, then a suggestion must be sent informing the
// time zone detector that there are no further updates coming in future.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
mEnvironment.elapsedRealtimeMillis(),
"Both providers are terminated:"
+ " primary=" + primaryCurrentState.provider
+ ", secondary=" + secondaryCurrentState.provider);
@@ -432,8 +434,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
// the loss of a binder-based provider, or initialization took too long. This is treated
// the same as explicit uncertainty, i.e. where the provider has explicitly told this
// process it is uncertain.
handleProviderUncertainty(provider, "provider=" + provider
+ ", implicit uncertainty, event=null");
long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis();
handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
"provider=" + provider + ", implicit uncertainty, event=null");
return;
}
@@ -448,18 +451,17 @@ class ControllerImpl extends LocationTimeZoneProviderController {
switch (event.getType()) {
case EVENT_TYPE_PERMANENT_FAILURE: {
// This shouldn't happen. A provider cannot be started and have this event type.
warnLog("Provider=" + provider
+ " is started, but event suggests it shouldn't be");
warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be");
break;
}
case EVENT_TYPE_UNCERTAIN: {
handleProviderUncertainty(provider, "provider=" + provider
+ ", explicit uncertainty. event=" + event);
long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis();
handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
"provider=" + provider + ", explicit uncertainty. event=" + event);
break;
}
case EVENT_TYPE_SUGGESTION: {
handleProviderSuggestion(provider, event.getSuggestion().getTimeZoneIds(),
"Event received provider=" + provider + ", event=" + event);
handleProviderSuggestion(provider, event);
break;
}
default: {
@@ -475,8 +477,8 @@ class ControllerImpl extends LocationTimeZoneProviderController {
@GuardedBy("mSharedLock")
private void handleProviderSuggestion(
@NonNull LocationTimeZoneProvider provider,
@Nullable List<String> timeZoneIds,
@NonNull String reason) {
@NonNull TimeZoneProviderEvent providerEvent) {
// By definition, the controller is now certain.
cancelUncertaintyTimeout();
@@ -484,10 +486,25 @@ class ControllerImpl extends LocationTimeZoneProviderController {
stopProviderIfStarted(mSecondaryProvider);
}
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(timeZoneIds);
suggestion.addDebugInfo(reason);
// Rely on the receiver to dedupe suggestions. It is better to over-communicate.
makeSuggestion(suggestion);
TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
// For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
// suggestion (which indicates the time when the provider detected the location used to
// establish the time zone).
//
// An alternative would be to use the current time or the providerEvent creation time, but
// this would hinder the ability for the time_zone_detector to judge which suggestions are
// based on newer information when comparing suggestions between different sources.
long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
GeolocationTimeZoneSuggestion geoSuggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
String debugInfo = "Event received provider=" + provider
+ ", providerEvent=" + providerEvent
+ ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
geoSuggestion.addDebugInfo(debugInfo);
makeSuggestion(geoSuggestion);
}
@Override
@@ -551,7 +568,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
*/
@GuardedBy("mSharedLock")
void handleProviderUncertainty(
@NonNull LocationTimeZoneProvider provider, @NonNull String reason) {
@NonNull LocationTimeZoneProvider provider,
@ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
@NonNull String reason) {
Objects.requireNonNull(provider);
// Start the uncertainty timeout if needed to ensure the controller will eventually make an
@@ -559,9 +578,11 @@ class ControllerImpl extends LocationTimeZoneProviderController {
if (!mUncertaintyTimeoutQueue.hasQueued()) {
debugLog("Starting uncertainty timeout: reason=" + reason);
Duration delay = mEnvironment.getUncertaintyDelay();
mUncertaintyTimeoutQueue.runDelayed(() -> onProviderUncertaintyTimeout(provider),
delay.toMillis());
Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
mUncertaintyTimeoutQueue.runDelayed(
() -> onProviderUncertaintyTimeout(
provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
uncertaintyDelay.toMillis());
}
if (provider == mPrimaryProvider) {
@@ -573,21 +594,45 @@ class ControllerImpl extends LocationTimeZoneProviderController {
}
}
private void onProviderUncertaintyTimeout(@NonNull LocationTimeZoneProvider provider) {
private void onProviderUncertaintyTimeout(
@NonNull LocationTimeZoneProvider provider,
@ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
@NonNull Duration uncertaintyDelay) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
// For the effectiveFromElapsedMillis suggestion property, use the
// uncertaintyStartedElapsedMillis. This is the time when the provider first reported
// uncertainty, i.e. before the uncertainty timeout.
//
// afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when
// the location_time_zone_manager finally confirms that the time zone was uncertain,
// but the suggestion property allows the information to be back-dated, which should
// help when comparing suggestions from different sources.
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
uncertaintyStartedElapsedMillis,
"Uncertainty timeout triggered for " + provider.getName() + ":"
+ " primary=" + mPrimaryProvider
+ ", secondary=" + mSecondaryProvider);
+ ", secondary=" + mSecondaryProvider
+ ", uncertaintyStarted="
+ Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ ", afterUncertaintyTimeout="
+ Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ ", uncertaintyDelay=" + uncertaintyDelay
);
makeSuggestion(suggestion);
}
}
@NonNull
private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) {
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
@ElapsedRealtimeLong long effectiveFromElapsedMillis,
@NonNull String reason) {
GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createUncertainSuggestion(
effectiveFromElapsedMillis);
suggestion.addDebugInfo(reason);
return suggestion;
}
@@ -622,19 +667,4 @@ class ControllerImpl extends LocationTimeZoneProviderController {
return builder.build();
}
}
@Nullable
private LocationTimeZoneProvider getLocationTimeZoneProvider(
@IntRange(from = 0, to = 1) int providerIndex) {
LocationTimeZoneProvider targetProvider;
if (providerIndex == 0) {
targetProvider = mPrimaryProvider;
} else if (providerIndex == 1) {
targetProvider = mSecondaryProvider;
} else {
warnLog("Bad providerIndex=" + providerIndex);
targetProvider = null;
}
return targetProvider;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.timezonedetector.location;
import android.annotation.DurationMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.os.Handler;
@@ -140,6 +141,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
* passed on.
*/
abstract Duration getUncertaintyDelay();
/**
* Returns the elapsed realtime as millis, the same as {@link
* android.os.SystemClock#elapsedRealtime()}.
*/
abstract @ElapsedRealtimeLong long elapsedRealtimeMillis();
}
/**

View File

@@ -34,26 +34,36 @@ public class GeolocationTimeZoneSuggestionTest {
@Test
public void testEquals() {
GeolocationTimeZoneSuggestion one = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS1);
assertEquals(one, one);
long time1 = 1111L;
GeolocationTimeZoneSuggestion certain1v1 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1);
assertEquals(certain1v1, certain1v1);
GeolocationTimeZoneSuggestion two = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS1);
assertEquals(one, two);
assertEquals(two, one);
GeolocationTimeZoneSuggestion nullZone = new GeolocationTimeZoneSuggestion(null);
assertNotEquals(one, nullZone);
assertNotEquals(nullZone, one);
assertEquals(nullZone, nullZone);
GeolocationTimeZoneSuggestion three =
new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS2);
assertNotEquals(one, three);
assertNotEquals(three, one);
GeolocationTimeZoneSuggestion certain1v2 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1);
assertEquals(certain1v1, certain1v2);
assertEquals(certain1v2, certain1v1);
// DebugInfo must not be considered in equals().
one.addDebugInfo("Debug info 1");
two.addDebugInfo("Debug info 2");
assertEquals(one, two);
certain1v1.addDebugInfo("Debug info 1");
certain1v2.addDebugInfo("Debug info 2");
assertEquals(certain1v1, certain1v2);
long time2 = 2222L;
GeolocationTimeZoneSuggestion certain2 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
assertNotEquals(certain1v1, certain2);
assertNotEquals(certain2, certain1v1);
GeolocationTimeZoneSuggestion uncertain =
GeolocationTimeZoneSuggestion.createUncertainSuggestion(time1);
assertNotEquals(certain1v1, uncertain);
assertNotEquals(uncertain, certain1v1);
assertEquals(uncertain, uncertain);
GeolocationTimeZoneSuggestion certain3 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS2);
assertNotEquals(certain1v1, certain3);
assertNotEquals(certain3, certain1v1);
}
}

View File

@@ -35,6 +35,7 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorInternalImplTest {
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList("TestZoneId");
private Context mMockContext;
@@ -99,6 +100,7 @@ public class TimeZoneDetectorInternalImplTest {
}
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_ZONE_IDS);
}
}

View File

@@ -50,11 +50,14 @@ import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
private static final int ARBITRARY_USER_ID = 9999;
private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private Context mMockContext;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategy;
@@ -374,7 +377,8 @@ public class TimeZoneDetectorServiceTest {
}
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
return new GeolocationTimeZoneSuggestion(Arrays.asList("TestZoneId"));
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_TIME_ZONE_IDS);
}
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {

View File

@@ -33,10 +33,13 @@ 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.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneConfiguration;
@@ -171,6 +174,8 @@ public class TimeZoneDetectorStrategyImplTest {
private FakeEnvironment mFakeEnvironment;
private MockConfigChangeListener mMockConfigChangeListener;
// A fake source of time for suggestions. This will typically be incremented after every use.
@ElapsedRealtimeLong private long mElapsedRealtimeMillis;
@Before
public void setUp() {
@@ -487,7 +492,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
// Each test case will have the same or lower score than the last.
List<TelephonyTestCase> descendingCasesByScore = list(TELEPHONY_TEST_CASES);
List<TelephonyTestCase> descendingCasesByScore = Arrays.asList(TELEPHONY_TEST_CASES);
Collections.reverse(descendingCasesByScore);
for (TelephonyTestCase testCase : descendingCasesByScore) {
@@ -750,7 +755,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
.verifyTimeZoneNotChanged();
@@ -766,7 +771,7 @@ public class TimeZoneDetectorStrategyImplTest {
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
.verifyTimeZoneNotChanged();
@@ -778,7 +783,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_oneZone() {
GeolocationTimeZoneSuggestion suggestion =
createGeoLocationSuggestion(list("Europe/London"));
createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -799,11 +804,11 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_multiZone() {
GeolocationTimeZoneSuggestion londonOnlySuggestion =
createGeoLocationSuggestion(list("Europe/London"));
createCertainGeolocationSuggestion("Europe/London");
GeolocationTimeZoneSuggestion londonOrParisSuggestion =
createGeoLocationSuggestion(list("Europe/Paris", "Europe/London"));
createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
GeolocationTimeZoneSuggestion parisOnlySuggestion =
createGeoLocationSuggestion(list("Europe/Paris"));
createCertainGeolocationSuggestion("Europe/Paris");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -836,7 +841,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_togglingGeoDetectionClearsLastSuggestion() {
GeolocationTimeZoneSuggestion suggestion =
createGeoLocationSuggestion(list("Europe/London"));
createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
@@ -863,7 +868,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testChangingGeoDetectionEnabled() {
GeolocationTimeZoneSuggestion geolocationSuggestion =
createGeoLocationSuggestion(list("Europe/London"));
createCertainGeolocationSuggestion("Europe/London");
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
"Europe/Paris");
@@ -960,7 +965,7 @@ public class TimeZoneDetectorStrategyImplTest {
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
createGeoLocationSuggestion(Arrays.asList("Zone3", "Zone2"));
createCertainGeolocationSuggestion("Zone3", "Zone2");
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged()
.simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
@@ -1052,13 +1057,18 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
private static GeolocationTimeZoneSuggestion createUncertainGeoLocationSuggestion() {
return createGeoLocationSuggestion(null);
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
mElapsedRealtimeMillis++, null);
}
private static GeolocationTimeZoneSuggestion createGeoLocationSuggestion(
@Nullable List<String> zoneIds) {
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@NonNull String... zoneIds) {
assertNotNull(zoneIds);
GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
mElapsedRealtimeMillis++, Arrays.asList(zoneIds));
suggestion.addDebugInfo("Test suggestion");
return suggestion;
}
@@ -1321,8 +1331,4 @@ public class TimeZoneDetectorStrategyImplTest {
mOnChangeCalled = false;
}
}
private static <T> List<T> list(T... values) {
return Arrays.asList(values);
}
}

View File

@@ -33,8 +33,10 @@ import static org.junit.Assert.fail;
import static java.util.Arrays.asList;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
@@ -287,8 +289,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -326,8 +328,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -366,8 +368,8 @@ public class ControllerImplTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -395,8 +397,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
@@ -416,8 +418,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -456,8 +458,8 @@ public class ControllerImplTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
@@ -479,8 +481,8 @@ public class ControllerImplTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -508,8 +510,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the primary provider. This should not
@@ -535,8 +537,8 @@ public class ControllerImplTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the secondary provider. This should not
@@ -560,7 +562,8 @@ public class ControllerImplTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertUncertainSuggestionMadeAndCommit();
mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -588,8 +591,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
@@ -613,8 +616,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@@ -682,8 +685,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -723,8 +726,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate the user change (but geo detection still enabled).
@@ -789,8 +792,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
@@ -896,8 +899,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate uncertainty from the primary. The secondary cannot be started.
@@ -1098,8 +1101,8 @@ public class ControllerImplTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds());
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Trigger destroy().
@@ -1186,6 +1189,13 @@ public class ControllerImplTest {
return UNCERTAINTY_DELAY;
}
@Override
long elapsedRealtimeMillis() {
// The properties of the real clock will also work for tests, i.e. it doesn't go
// backwards.
return SystemClock.elapsedRealtime();
}
void simulateConfigChange(ConfigurationInternal newConfig) {
ConfigurationInternal oldConfig = mConfigurationInternal;
mConfigurationInternal = Objects.requireNonNull(newConfig);
@@ -1209,19 +1219,52 @@ public class ControllerImplTest {
mLatestSuggestion.set(suggestion);
}
void assertSuggestionMadeAndCommit(@Nullable List<String> expectedZoneIds) {
mLatestSuggestion.assertHasBeenSet();
assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
mLatestSuggestion.commitLatest();
void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
TimeZoneProviderSuggestion suggestion = event.getSuggestion();
assertSuggestionMadeAndCommit(
suggestion.getElapsedRealtimeMillis(),
suggestion.getTimeZoneIds());
}
void assertNoSuggestionMade() {
mLatestSuggestion.assertHasNotBeenSet();
}
/** Asserts that an uncertain suggestion has been made from the supplied event. */
void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
}
/**
* Asserts that an uncertain suggestion has been made.
* Ignores the suggestion's effectiveFromElapsedMillis.
*/
void assertUncertainSuggestionMadeAndCommit() {
// An "uncertain" suggestion has null time zone IDs.
assertSuggestionMadeAndCommit(null);
assertSuggestionMadeAndCommit(null, null);
}
/**
* Asserts that a suggestion has been made and some properties of that suggestion.
* When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
*/
private void assertSuggestionMadeAndCommit(
@Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
@Nullable List<String> expectedZoneIds) {
mLatestSuggestion.assertHasBeenSet();
if (expectedEffectiveFromElapsedMillis != null) {
assertEquals(
expectedEffectiveFromElapsedMillis.longValue(),
mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
}
assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
mLatestSuggestion.commitLatest();
}
}