diff --git a/core/java/android/service/timezone/ITimeZoneProvider.aidl b/core/java/android/service/timezone/ITimeZoneProvider.aidl index 793bcc6054a98..4a404cfaa7df2 100644 --- a/core/java/android/service/timezone/ITimeZoneProvider.aidl +++ b/core/java/android/service/timezone/ITimeZoneProvider.aidl @@ -22,6 +22,7 @@ import android.service.timezone.ITimeZoneProviderManager; * @hide */ oneway interface ITimeZoneProvider { - void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis); + void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis, + in long eventFilteringAgeThresholdMillis); void stopUpdates(); } diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java index 0f1fcf589381c..700528116a8f0 100644 --- a/core/java/android/service/timezone/TimeZoneProviderEvent.java +++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java @@ -16,6 +16,7 @@ package android.service.timezone; +import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,6 +27,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.time.Duration; import java.util.Objects; /** @@ -59,11 +61,11 @@ public final class TimeZoneProviderEvent implements Parcelable { */ public static final @EventType int EVENT_TYPE_UNCERTAIN = 3; - private static final TimeZoneProviderEvent UNCERTAIN_EVENT = - new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, null, null); - private final @EventType int mType; + @ElapsedRealtimeLong + private final long mCreationElapsedMillis; + @Nullable private final TimeZoneProviderSuggestion mSuggestion; @@ -71,28 +73,34 @@ public final class TimeZoneProviderEvent implements Parcelable { private final String mFailureCause; private TimeZoneProviderEvent(@EventType int type, + @ElapsedRealtimeLong long creationElapsedMillis, @Nullable TimeZoneProviderSuggestion suggestion, @Nullable String failureCause) { mType = type; + mCreationElapsedMillis = creationElapsedMillis; mSuggestion = suggestion; mFailureCause = failureCause; } /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */ public static TimeZoneProviderEvent createSuggestionEvent( + @ElapsedRealtimeLong long creationElapsedMillis, @NonNull TimeZoneProviderSuggestion suggestion) { - return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, + return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis, Objects.requireNonNull(suggestion), null); } /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */ - public static TimeZoneProviderEvent createUncertainEvent() { - return UNCERTAIN_EVENT; + public static TimeZoneProviderEvent createUncertainEvent( + @ElapsedRealtimeLong long creationElapsedMillis) { + return new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null); } /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */ - public static TimeZoneProviderEvent createPermanentFailureEvent(@NonNull String cause) { - return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, null, + public static TimeZoneProviderEvent createPermanentFailureEvent( + @ElapsedRealtimeLong long creationElapsedMillis, + @NonNull String cause) { + return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null, Objects.requireNonNull(cause)); } @@ -103,6 +111,12 @@ public final class TimeZoneProviderEvent implements Parcelable { return mType; } + /** Returns the time according to the elapsed realtime clock when the event was created. */ + @ElapsedRealtimeLong + public long getCreationElapsedMillis() { + return mCreationElapsedMillis; + } + /** * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}. */ @@ -125,10 +139,12 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public TimeZoneProviderEvent createFromParcel(Parcel in) { int type = in.readInt(); + long creationElapsedMillis = in.readLong(); TimeZoneProviderSuggestion suggestion = in.readParcelable(getClass().getClassLoader()); String failureCause = in.readString8(); - return new TimeZoneProviderEvent(type, suggestion, failureCause); + return new TimeZoneProviderEvent( + type, creationElapsedMillis, suggestion, failureCause); } @Override @@ -145,6 +161,7 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(mType); + parcel.writeLong(mCreationElapsedMillis); parcel.writeParcelable(mSuggestion, 0); parcel.writeString8(mFailureCause); } @@ -153,6 +170,7 @@ public final class TimeZoneProviderEvent implements Parcelable { public String toString() { return "TimeZoneProviderEvent{" + "mType=" + mType + + ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString() + ", mSuggestion=" + mSuggestion + ", mFailureCause=" + mFailureCause + '}'; @@ -173,8 +191,7 @@ public final class TimeZoneProviderEvent implements Parcelable { return false; } if (mType == EVENT_TYPE_SUGGESTION) { - // Only check the time zone IDs. The times will be different, but we don't mind. - return mSuggestion.getTimeZoneIds().equals(other.getSuggestion().getTimeZoneIds()); + return mSuggestion.isEquivalentTo(other.getSuggestion()); } return true; } @@ -189,12 +206,13 @@ public final class TimeZoneProviderEvent implements Parcelable { } TimeZoneProviderEvent that = (TimeZoneProviderEvent) o; return mType == that.mType + && mCreationElapsedMillis == that.mCreationElapsedMillis && Objects.equals(mSuggestion, that.mSuggestion) && Objects.equals(mFailureCause, that.mFailureCause); } @Override public int hashCode() { - return Objects.hash(mType, mSuggestion, mFailureCause); + return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause); } } diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java index 7dfc2e07e81bc..0d215f6d56f16 100644 --- a/core/java/android/service/timezone/TimeZoneProviderService.java +++ b/core/java/android/service/timezone/TimeZoneProviderService.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -178,6 +179,10 @@ public abstract class TimeZoneProviderService extends Service { @Nullable private ITimeZoneProviderManager mManager; + /** Set by {@link #mHandler} thread. */ + @GuardedBy("mLock") + private long mEventFilteringAgeThresholdMillis; + /** * The type of the last suggestion sent to the system server. Used to de-dupe suggestions client * side and avoid calling into the system server unnecessarily. {@code null} means no previous @@ -206,8 +211,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = - TimeZoneProviderEvent.createSuggestionEvent(suggestion); - if (!thisEvent.isEquivalentTo(mLastEventSent)) { + TimeZoneProviderEvent.createSuggestionEvent( + SystemClock.elapsedRealtime(), suggestion); + if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } @@ -231,8 +237,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = - TimeZoneProviderEvent.createUncertainEvent(); - if (!thisEvent.isEquivalentTo(mLastEventSent)) { + TimeZoneProviderEvent.createUncertainEvent( + SystemClock.elapsedRealtime()); + if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } @@ -258,8 +265,9 @@ public abstract class TimeZoneProviderService extends Service { try { String causeString = cause.getMessage(); TimeZoneProviderEvent thisEvent = - TimeZoneProviderEvent.createPermanentFailureEvent(causeString); - if (!thisEvent.isEquivalentTo(mLastEventSent)) { + TimeZoneProviderEvent.createPermanentFailureEvent( + SystemClock.elapsedRealtime(), causeString); + if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } @@ -271,10 +279,33 @@ public abstract class TimeZoneProviderService extends Service { }); } + @GuardedBy("mLock") + private boolean shouldSendEvent(TimeZoneProviderEvent newEvent) { + // Always send an event if it indicates a state or suggestion change. + if (!newEvent.isEquivalentTo(mLastEventSent)) { + return true; + } + + // Guard against implementations that generate a lot of uninteresting events in a short + // space of time and would cause the time_zone_detector to evaluate time zone suggestions + // too frequently. + // + // If the new event and last event sent are equivalent, the client will still send an update + // if their creation times are sufficiently different. This enables the time_zone_detector + // to better understand how recently the location time zone provider was certain / + // uncertain, which can be useful when working out ordering of events, e.g. to work out + // whether a suggestion was generated before or after a device left airplane mode. + long timeSinceLastEventMillis = + newEvent.getCreationElapsedMillis() - mLastEventSent.getCreationElapsedMillis(); + return timeSinceLastEventMillis > mEventFilteringAgeThresholdMillis; + } + private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, - @DurationMillisLong long initializationTimeoutMillis) { + @DurationMillisLong long initializationTimeoutMillis, + @DurationMillisLong long eventFilteringAgeThresholdMillis) { synchronized (mLock) { mManager = manager; + mEventFilteringAgeThresholdMillis = eventFilteringAgeThresholdMillis; mLastEventSent = null; onStartUpdates(initializationTimeoutMillis); } @@ -332,9 +363,11 @@ public abstract class TimeZoneProviderService extends Service { private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { public void startUpdates(@NonNull ITimeZoneProviderManager manager, - @DurationMillisLong long initializationTimeoutMillis) { + @DurationMillisLong long initializationTimeoutMillis, + @DurationMillisLong long eventFilteringAgeThresholdMillis) { Objects.requireNonNull(manager); - mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis)); + mHandler.post(() -> onStartUpdatesInternal( + manager, initializationTimeoutMillis, eventFilteringAgeThresholdMillis)); } public void stopUpdates() { diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java index cf299a738fbc9..229fa268a47c5 100644 --- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java +++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java @@ -18,6 +18,7 @@ package android.service.timezone; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -121,6 +122,24 @@ public final class TimeZoneProviderSuggestion implements Parcelable { parcel.writeLong(mElapsedRealtimeMillis); } + /** + * Similar to {@link #equals} except this methods checks for equivalence, not equality. + * i.e. two suggestions are equivalent if they suggest the same time zones. + * + * @hide + */ + @SuppressWarnings("ReferenceEquality") + public boolean isEquivalentTo(@Nullable TimeZoneProviderSuggestion other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + // Only check the time zone IDs. The times can be different, but we don't mind. + return mTimeZoneIds.equals(other.mTimeZoneIds); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java index 7dd19704b551c..c8de190b30b0b 100644 --- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java +++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java @@ -33,7 +33,8 @@ public class TimeZoneProviderEventTest { @Test public void isEquivalentToAndEquals() { - TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent("one"); + TimeZoneProviderEvent fail1v1 = + TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v1); assertIsEquivalentTo(fail1v1, fail1v1); assertNotEquals(fail1v1, null); @@ -41,58 +42,74 @@ public class TimeZoneProviderEventTest { { TimeZoneProviderEvent fail1v2 = - TimeZoneProviderEvent.createPermanentFailureEvent("one"); + TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v2); assertIsEquivalentTo(fail1v1, fail1v2); - TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent("two"); + TimeZoneProviderEvent fail2 = + TimeZoneProviderEvent.createPermanentFailureEvent(2222L, "two"); assertNotEquals(fail1v1, fail2); assertIsEquivalentTo(fail1v1, fail2); } - TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(); + TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v1); assertIsEquivalentTo(uncertain1v1, uncertain1v1); assertNotEquals(uncertain1v1, null); assertNotEquivalentTo(uncertain1v1, null); { - TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(); + TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v2); assertIsEquivalentTo(uncertain1v1, uncertain1v2); + + TimeZoneProviderEvent uncertain2 = TimeZoneProviderEvent.createUncertainEvent(2222L); + assertNotEquals(uncertain1v1, uncertain2); + assertIsEquivalentTo(uncertain1v1, uncertain2); } TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(1111L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); - TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1); + TimeZoneProviderEvent certain1v1 = + TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v1); assertIsEquivalentTo(certain1v1, certain1v1); assertNotEquals(certain1v1, null); assertNotEquivalentTo(certain1v1, null); { + // Same suggestion, same time. TimeZoneProviderEvent certain1v2 = - TimeZoneProviderEvent.createSuggestionEvent(suggestion1); + TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v2); assertIsEquivalentTo(certain1v1, certain1v2); + // Same suggestion, different time. + TimeZoneProviderEvent certain1v3 = + TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1); + assertNotEquals(certain1v1, certain1v3); + assertIsEquivalentTo(certain1v1, certain1v3); + + // suggestion1 is equivalent to suggestion2, but not equal TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(2222L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); assertNotEquals(suggestion1, suggestion2); + TimeZoneProviderSuggestionTest.assertIsEquivalentTo(suggestion1, suggestion2); TimeZoneProviderEvent certain2 = - TimeZoneProviderEvent.createSuggestionEvent(suggestion2); + TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2); assertNotEquals(certain1v1, certain2); assertIsEquivalentTo(certain1v1, certain2); + // suggestion3 is not equivalent to suggestion1 TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Collections.singletonList("Europe/Paris")) .build(); TimeZoneProviderEvent certain3 = - TimeZoneProviderEvent.createSuggestionEvent(suggestion3); + TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3); assertNotEquals(certain1v1, certain3); assertNotEquivalentTo(certain1v1, certain3); } @@ -107,13 +124,13 @@ public class TimeZoneProviderEventTest { @Test public void testParcelable_failureEvent() { TimeZoneProviderEvent event = - TimeZoneProviderEvent.createPermanentFailureEvent("failure reason"); + TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "failure reason"); assertRoundTripParcelable(event); } @Test public void testParcelable_uncertain() { - TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(); + TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L); assertRoundTripParcelable(event); } @@ -122,7 +139,8 @@ public class TimeZoneProviderEventTest { TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris")) .build(); - TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); + TimeZoneProviderEvent event = + TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion); assertRoundTripParcelable(event); } diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java new file mode 100644 index 0000000000000..e8007390bd7a9 --- /dev/null +++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.timezone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import java.util.Collections; + +/** Tests for non-API methods */ +public class TimeZoneProviderSuggestionTest { + + @Test + public void isEquivalentToAndEquals() { + TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(1111L) + .setTimeZoneIds(Collections.singletonList("Europe/London")) + .build(); + assertEquals(suggestion1, suggestion1); + assertIsEquivalentTo(suggestion1, suggestion1); + assertNotEquals(suggestion1, null); + assertNotEquivalentTo(suggestion1, null); + + // Same time zone IDs, different time. + TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(2222L) + .setTimeZoneIds(Collections.singletonList("Europe/London")) + .build(); + assertNotEquals(suggestion1, suggestion2); + assertIsEquivalentTo(suggestion1, suggestion2); + + // Different time zone IDs. + TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(1111L) + .setTimeZoneIds(Collections.singletonList("Europe/Paris")) + .build(); + assertNotEquals(suggestion1, suggestion3); + assertNotEquivalentTo(suggestion1, suggestion3); + } + + static void assertNotEquivalentTo( + TimeZoneProviderSuggestion one, TimeZoneProviderSuggestion two) { + if (one == null && two == null) { + fail("null arguments"); + } + if (one != null) { + assertFalse("one=" + one + ", two=" + two, one.isEquivalentTo(two)); + } + if (two != null) { + assertFalse("one=" + one + ", two=" + two, two.isEquivalentTo(one)); + } + } + + static void assertIsEquivalentTo( + TimeZoneProviderSuggestion one, TimeZoneProviderSuggestion two) { + if (one == null || two == null) { + fail("null arguments"); + } + assertTrue("one=" + one + ", two=" + two, one.isEquivalentTo(two)); + assertTrue("one=" + one + ", two=" + two, two.isEquivalentTo(one)); + } +} diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java index ac2d76e89d762..477ebf6402528 100644 --- a/services/core/java/com/android/server/timedetector/ServerFlags.java +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -55,10 +55,11 @@ public final class ServerFlags { */ @StringDef(prefix = "KEY_", value = { KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, - KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE, - KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + KEY_PRIMARY_LTZP_MODE_OVERRIDE, + KEY_SECONDARY_LTZP_MODE_OVERRIDE, + KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, + KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, @@ -82,16 +83,14 @@ public final class ServerFlags { * The key for the server flag that can override the device config for whether the primary * location time zone provider is enabled, disabled, or (for testing) in simulation mode. */ - public static final @DeviceConfigKey String - KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE = + public static final @DeviceConfigKey String KEY_PRIMARY_LTZP_MODE_OVERRIDE = "primary_location_time_zone_provider_mode_override"; /** * The key for the server flag that can override the device config for whether the secondary * location time zone provider is enabled or disabled, or (for testing) in simulation mode. */ - public static final @DeviceConfigKey String - KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE = + public static final @DeviceConfigKey String KEY_SECONDARY_LTZP_MODE_OVERRIDE = "secondary_location_time_zone_provider_mode_override"; /** @@ -106,18 +105,20 @@ public final class ServerFlags { * The key for the timeout passed to a location time zone provider that tells it how long it has * to provide an explicit first suggestion without being declared uncertain. */ - public static final @DeviceConfigKey String - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = - "ltpz_init_timeout_millis"; + public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS = + "ltzp_init_timeout_millis"; /** * The key for the extra time added to {@link - * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone + * #KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone * manager before the location time zone provider will actually be declared uncertain. */ - public static final @DeviceConfigKey String - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = - "ltpz_init_timeout_fuzz_millis"; + public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = + "ltzp_init_timeout_fuzz_millis"; + + /** The key for the setting that controls rate limiting of provider events. */ + public static final @DeviceConfigKey String KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS = + "ltzp_event_filtering_age_threshold_millis"; /** * The key for the server flag that can override location time zone detection being enabled for diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index 58281306c0859..20f4fa17ad67e 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -72,17 +72,18 @@ public final class ServiceConfigAccessor { ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, - ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE, - ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE, - ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE, + ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE, + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS })); - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = - Duration.ofMinutes(1); - private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); + private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); + private static final Duration DEFAULT_LTZP_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + private static final Duration DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD = Duration.ofMinutes(1); private static final Object SLOCK = new Object(); @@ -326,8 +327,7 @@ public final class ServiceConfigAccessor { // In test mode: use the test setting value. return mTestPrimaryLocationTimeZoneProviderMode; } - return mServerFlags.getOptionalString( - ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE) + return mServerFlags.getOptionalString(ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE) .orElse(getPrimaryLocationTimeZoneProviderModeFromConfig()); } @@ -346,8 +346,7 @@ public final class ServiceConfigAccessor { // In test mode: use the test setting value. return mTestSecondaryLocationTimeZoneProviderMode; } - return mServerFlags.getOptionalString( - ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE) + return mServerFlags.getOptionalString(ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE) .orElse(getSecondaryLocationTimeZoneProviderModeFromConfig()); } @@ -385,8 +384,8 @@ public final class ServiceConfigAccessor { @NonNull public Duration getLocationTimeZoneProviderInitializationTimeout() { return mServerFlags.getDurationFromMillis( - ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, + DEFAULT_LTZP_INITIALIZATION_TIMEOUT); } /** @@ -396,8 +395,8 @@ public final class ServiceConfigAccessor { @NonNull public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() { return mServerFlags.getDurationFromMillis( - ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ); } /** @@ -408,7 +407,18 @@ public final class ServiceConfigAccessor { public Duration getLocationTimeZoneUncertaintyDelay() { return mServerFlags.getDurationFromMillis( ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + DEFAULT_LTZP_UNCERTAINTY_DELAY); + } + + /** + * Returns the time between equivalent events before the provider process will send the event + * to the system server. + */ + @NonNull + public Duration getLocationTimeZoneProviderEventFilteringAgeThreshold() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, + DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD); } /** Clears all in-memory test config. */ diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java index 435a4603639f5..a1de2941808e3 100644 --- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java @@ -112,11 +112,12 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { } @Override - void onStartUpdates(@NonNull Duration initializationTimeout) { + void onStartUpdates(@NonNull Duration initializationTimeout, + @NonNull Duration eventFilteringAgeThreshold) { // Set a request on the proxy - it will be sent immediately if the service is bound, // or will be sent as soon as the service becomes bound. - TimeZoneProviderRequest request = - TimeZoneProviderRequest.createStartUpdatesRequest(initializationTimeout); + TimeZoneProviderRequest request = TimeZoneProviderRequest.createStartUpdatesRequest( + initializationTimeout, eventFilteringAgeThreshold); mProxy.setRequest(request); } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index 98e984d2c3aca..551a05950b27c 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -78,4 +78,9 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir Duration getUncertaintyDelay() { return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay(); } + + @Override + Duration getProviderEventFilteringAgeThreshold() { + return mServiceConfigAccessor.getLocationTimeZoneProviderEventFilteringAgeThreshold(); + } } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java index a8d9ceea3922c..1d58cea1be3d5 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java @@ -283,6 +283,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { } } + @GuardedBy("mSharedLock") private void tryStartProvider(@NonNull LocationTimeZoneProvider provider, @NonNull ConfigurationInternal configuration) { ProviderState providerState = provider.getCurrentState(); @@ -291,7 +292,8 @@ class ControllerImpl extends LocationTimeZoneProviderController { debugLog("Enabling " + provider); provider.startUpdates(configuration, mEnvironment.getProviderInitializationTimeout(), - mEnvironment.getProviderInitializationTimeoutFuzz()); + mEnvironment.getProviderInitializationTimeoutFuzz(), + mEnvironment.getProviderEventFilteringAgeThreshold()); break; } case PROVIDER_STATE_STARTED_INITIALIZING: diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 3488956af5715..6c9e174b0fc59 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -26,10 +26,11 @@ import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP; import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS; -import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS; -import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS; -import static com.android.server.timedetector.ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE; -import static com.android.server.timedetector.ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE; +import static com.android.server.timedetector.ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS; +import static com.android.server.timedetector.ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS; +import static com.android.server.timedetector.ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS; +import static com.android.server.timedetector.ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE; +import static com.android.server.timedetector.ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE; import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_DISABLED; import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_ENABLED; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; @@ -126,20 +127,23 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { pw.println(); pw.printf("This service is also affected by the following device_config flags in the" + " %s namespace:\n", NAMESPACE_SYSTEM_TIME); - pw.printf(" %s\n", KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE); + pw.printf(" %s\n", KEY_PRIMARY_LTZP_MODE_OVERRIDE); pw.printf(" Overrides the mode of the primary provider. Values=%s|%s\n", PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED); - pw.printf(" %s\n", KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE); + pw.printf(" %s\n", KEY_SECONDARY_LTZP_MODE_OVERRIDE); pw.printf(" Overrides the mode of the secondary provider. Values=%s|%s\n", PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED); pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS); pw.printf(" Sets the amount of time the service waits when uncertain before making an" + " 'uncertain' suggestion to the time zone detector.\n"); - pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS); + pw.printf(" %s\n", KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS); pw.printf(" Sets the initialization time passed to the providers.\n"); - pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS); + pw.printf(" %s\n", KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS); pw.printf(" Sets the amount of extra time added to the providers' initialization time." + "\n"); + pw.printf(" %s\n", KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS); + pw.printf(" Sets the amount of time that must pass between equivalent LTZP events before" + + " they will be reported to the system server.\n"); pw.println(); pw.printf("Typically, use '%s' to stop the service before setting individual" + " flags and '%s' after to restart it.\n", diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java index 3dcf36b25d862..b52dcd083e9d3 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java @@ -532,7 +532,8 @@ abstract class LocationTimeZoneProvider implements Dumpable { * called using the handler thread from the {@link ThreadingDomain}. */ final void startUpdates(@NonNull ConfigurationInternal currentUserConfiguration, - @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) { + @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz, + @NonNull Duration eventFilteringAgeThreshold) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { @@ -548,7 +549,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { mInitializationTimeoutQueue.runDelayed( this::handleInitializationTimeout, delay.toMillis()); - onStartUpdates(initializationTimeout); + onStartUpdates(initializationTimeout, eventFilteringAgeThreshold); } } @@ -574,9 +575,11 @@ abstract class LocationTimeZoneProvider implements Dumpable { * Implemented by subclasses to do work during {@link #startUpdates}. This is where the logic * to start the real provider should be implemented. * - * @param initializationTimeout the initialization timeout to pass to the real provider + * @param initializationTimeout the initialization timeout to pass to the provider + * @param eventFilteringAgeThreshold the event filtering age threshold to pass to the provider */ - abstract void onStartUpdates(@NonNull Duration initializationTimeout); + abstract void onStartUpdates(@NonNull Duration initializationTimeout, + @NonNull Duration eventFilteringAgeThreshold); /** * Stops the provider. It is an error to call this method except when the {@link diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index b4aff3e005ad0..d2fd9a9f307e3 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -129,6 +129,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable { */ abstract Duration getProviderInitializationTimeoutFuzz(); + /** + * Returns the value passed to LocationTimeZoneProviders to control rate limiting of + * equivalent events. + */ + abstract Duration getProviderEventFilteringAgeThreshold(); + /** * Returns the delay allowed after receiving uncertainty from a provider before it should be * passed on. diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java index 09d81d6a82a25..9cb1813df6db0 100644 --- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java @@ -19,6 +19,7 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.os.SystemClock; import android.service.timezone.TimeZoneProviderEvent; import android.util.IndentingPrintWriter; @@ -59,7 +60,7 @@ class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { void setRequest(@NonNull TimeZoneProviderRequest request) { if (request.sendUpdates()) { TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent( - "Provider is disabled"); + SystemClock.elapsedRealtime(), "Provider is disabled"); handleTimeZoneProviderEvent(event); } } diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index a7a7ae9bd1dae..f54fe4872f490 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -152,7 +152,9 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy im mServiceWatcher.runOnBinder(binder -> { ITimeZoneProvider service = ITimeZoneProvider.Stub.asInterface(binder); if (request.sendUpdates()) { - service.startUpdates(managerProxy, request.getInitializationTimeout().toMillis()); + service.startUpdates(managerProxy, + request.getInitializationTimeout().toMillis(), + request.getEventFilteringAgeThreshold().toMillis()); } else { service.stopUpdates(); } diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java index e8386bc22403c..a9d94dd2fb445 100644 --- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java @@ -31,23 +31,32 @@ final class TimeZoneProviderRequest { private static final TimeZoneProviderRequest STOP_UPDATES = new TimeZoneProviderRequest( false /* sendUpdates */, - null /* initializationTimeout */); + null /* initializationTimeout */, + null /* eventFilteringAgeThreshold */); private final boolean mSendUpdates; @Nullable private final Duration mInitializationTimeout; + @Nullable + private final Duration mEventFilteringAgeThreshold; + private TimeZoneProviderRequest( - boolean sendUpdates, @Nullable Duration initializationTimeout) { + boolean sendUpdates, @Nullable Duration initializationTimeout, + @Nullable Duration eventFilteringAgeThreshold) { mSendUpdates = sendUpdates; mInitializationTimeout = initializationTimeout; + mEventFilteringAgeThreshold = eventFilteringAgeThreshold; } /** Creates a request to start updates with the specified timeout. */ public static TimeZoneProviderRequest createStartUpdatesRequest( - @NonNull Duration initializationTimeout) { - return new TimeZoneProviderRequest(true, Objects.requireNonNull(initializationTimeout)); + @NonNull Duration initializationTimeout, + @NonNull Duration eventFilteringAgeThreshold) { + return new TimeZoneProviderRequest(true, + Objects.requireNonNull(initializationTimeout), + Objects.requireNonNull(eventFilteringAgeThreshold)); } /** Creates a request to stop updates. */ @@ -74,6 +83,17 @@ final class TimeZoneProviderRequest { return mInitializationTimeout; } + /** + * Returns the threshold the remote process is to use to filter equivalent events. Only valid + * when {@link #sendUpdates()} is {@code true}. + * + *
Guaranteed to be set when {@link #sendUpdates()} returns {@code true}.
+ */
+ @NonNull
+ public Duration getEventFilteringAgeThreshold() {
+ return mEventFilteringAgeThreshold;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -82,15 +102,15 @@ final class TimeZoneProviderRequest {
if (o == null || getClass() != o.getClass()) {
return false;
}
- TimeZoneProviderRequest
- that = (TimeZoneProviderRequest) o;
+ TimeZoneProviderRequest that = (TimeZoneProviderRequest) o;
return mSendUpdates == that.mSendUpdates
- && mInitializationTimeout == that.mInitializationTimeout;
+ && Objects.equals(mInitializationTimeout, that.mInitializationTimeout)
+ && Objects.equals(mEventFilteringAgeThreshold, that.mEventFilteringAgeThreshold);
}
@Override
public int hashCode() {
- return Objects.hash(mSendUpdates, mInitializationTimeout);
+ return Objects.hash(mSendUpdates, mInitializationTimeout, mEventFilteringAgeThreshold);
}
@Override
@@ -98,6 +118,7 @@ final class TimeZoneProviderRequest {
return "TimeZoneProviderRequest{"
+ "mSendUpdates=" + mSendUpdates
+ ", mInitializationTimeout=" + mInitializationTimeout
+ + ", mEventFilteringAgeThreshold=" + mEventFilteringAgeThreshold
+ "}";
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index 24cc1d4dfbf62..ff0529f35057b 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -53,7 +53,7 @@ public class ZoneInfoDbTimeZoneProviderEventPreProcessor
// enables immediate failover to a secondary provider, one that might provide valid IDs for
// the same location, which should provide better behavior than just ignoring the event.
if (hasInvalidZones(event)) {
- return TimeZoneProviderEvent.createUncertainEvent();
+ return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis());
}
return event;
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 77b1a32d41a3c..27d95461ee618 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -66,9 +66,9 @@ public class ControllerImplTest {
private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
createSuggestionEvent(asList("Europe/Paris"));
private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
- TimeZoneProviderEvent.createUncertainEvent();
+ TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_TIME_MILLIS);
private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
- TimeZoneProviderEvent.createPermanentFailureEvent("Test");
+ TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test");
private TestThreadingDomain mTestThreadingDomain;
private TestCallback mTestCallback;
@@ -1125,6 +1125,7 @@ public class ControllerImplTest {
private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List