Loading services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java +69 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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: * * <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 Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -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 + '}'; } Loading @@ -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; } Loading services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -83,4 +85,9 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir Duration getProviderEventFilteringAgeThreshold() { return mServiceConfigAccessor.getLocationTimeZoneProviderEventFilteringAgeThreshold(); } @Override @ElapsedRealtimeLong long elapsedRealtimeMillis() { return SystemClock.elapsedRealtime(); } } services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +70 −40 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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); } } Loading Loading @@ -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()); Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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: { Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; } } services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } /** Loading services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java +27 −17 Original line number Diff line number Diff line Loading @@ -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 certain1v2 = GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1); assertEquals(certain1v1, certain1v2); assertEquals(certain1v2, certain1v1); GeolocationTimeZoneSuggestion nullZone = new GeolocationTimeZoneSuggestion(null); assertNotEquals(one, nullZone); assertNotEquals(nullZone, one); assertEquals(nullZone, nullZone); // DebugInfo must not be considered in equals(). certain1v1.addDebugInfo("Debug info 1"); certain1v2.addDebugInfo("Debug info 2"); assertEquals(certain1v1, certain1v2); GeolocationTimeZoneSuggestion three = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS2); assertNotEquals(one, three); assertNotEquals(three, one); long time2 = 2222L; GeolocationTimeZoneSuggestion certain2 = GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1); assertNotEquals(certain1v1, certain2); assertNotEquals(certain2, certain1v1); // DebugInfo must not be considered in equals(). one.addDebugInfo("Debug info 1"); two.addDebugInfo("Debug info 2"); assertEquals(one, two); 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); } } Loading
services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java +69 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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: * * <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 Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -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 + '}'; } Loading @@ -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; } Loading
services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -83,4 +85,9 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir Duration getProviderEventFilteringAgeThreshold() { return mServiceConfigAccessor.getLocationTimeZoneProviderEventFilteringAgeThreshold(); } @Override @ElapsedRealtimeLong long elapsedRealtimeMillis() { return SystemClock.elapsedRealtime(); } }
services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +70 −40 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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); } } Loading Loading @@ -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()); Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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: { Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; } }
services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } /** Loading
services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java +27 −17 Original line number Diff line number Diff line Loading @@ -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 certain1v2 = GeolocationTimeZoneSuggestion.createCertainSuggestion(time1, ARBITRARY_ZONE_IDS1); assertEquals(certain1v1, certain1v2); assertEquals(certain1v2, certain1v1); GeolocationTimeZoneSuggestion nullZone = new GeolocationTimeZoneSuggestion(null); assertNotEquals(one, nullZone); assertNotEquals(nullZone, one); assertEquals(nullZone, nullZone); // DebugInfo must not be considered in equals(). certain1v1.addDebugInfo("Debug info 1"); certain1v2.addDebugInfo("Debug info 2"); assertEquals(certain1v1, certain1v2); GeolocationTimeZoneSuggestion three = new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS2); assertNotEquals(one, three); assertNotEquals(three, one); long time2 = 2222L; GeolocationTimeZoneSuggestion certain2 = GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1); assertNotEquals(certain1v1, certain2); assertNotEquals(certain2, certain1v1); // DebugInfo must not be considered in equals(). one.addDebugInfo("Debug info 1"); two.addDebugInfo("Debug info 2"); assertEquals(one, two); 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); } }