From 135703feac593f1aed4af3130a083c00c85d675d Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Wed, 13 Oct 2021 10:31:36 +0100 Subject: [PATCH] Adding selective filtering to LTZP events This commit introduces a time threshold for passing on "equivalent" events for location time zone providers. When TimeZoneProviderEvent instances are created, they are assigned a time from the elapsed realtime clock, which will enable comparison with other types of events generated by other flows. Events that have a sufficiently different time will be passed to the system server. The time will be in the order of a few minutes, the intention being to filter out high frequency events that would cause the system server to do work unnecessarily, while keeping a coarse idea of how recently the LTZP reported the state. This commit adds the time to the TimeZoneProviderEvent and plumbs through the configuration for the filter threshold from the system server to the client code. The threshold can be set with server flags, which are mostly intended to support the test logic. Changes made at the same time: 1) Fix an issue in TimeZoneProviderRequest.equals() with an incorrect reference equality check. 2) Rename some long-winded constant names - LOCATION_TIME_ZONE_PROVIDER_ -> LTZP_ 3) Change some server flag strings so that "ltzp_" is spelt correctly (which I do not anticipate using, but does mean that S and >S devices respond to different flag strings). Bug: 197624972 Bug: 200710190 Test: atest core/tests/coretests/src/android/service/timezone/ Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/ Test: See associated cts/ change Change-Id: Id682d420be21b9215b92f54bad599517c322fb6a --- .../service/timezone/ITimeZoneProvider.aidl | 3 +- .../timezone/TimeZoneProviderEvent.java | 42 +++++++--- .../timezone/TimeZoneProviderService.java | 51 +++++++++--- .../timezone/TimeZoneProviderSuggestion.java | 19 +++++ .../timezone/TimeZoneProviderEventTest.java | 42 +++++++--- .../TimeZoneProviderSuggestionTest.java | 81 +++++++++++++++++++ .../server/timedetector/ServerFlags.java | 31 +++---- .../ServiceConfigAccessor.java | 44 ++++++---- .../BinderLocationTimeZoneProvider.java | 7 +- .../location/ControllerEnvironmentImpl.java | 5 ++ .../location/ControllerImpl.java | 4 +- .../LocationTimeZoneManagerShellCommand.java | 20 +++-- .../location/LocationTimeZoneProvider.java | 11 ++- .../LocationTimeZoneProviderController.java | 6 ++ .../NullLocationTimeZoneProviderProxy.java | 3 +- .../RealLocationTimeZoneProviderProxy.java | 4 +- .../location/TimeZoneProviderRequest.java | 37 +++++++-- ...foDbTimeZoneProviderEventPreProcessor.java | 2 +- .../location/ControllerImplTest.java | 19 +++-- ...FakeTimeZoneProviderEventPreProcessor.java | 3 +- .../LocationTimeZoneProviderTest.java | 35 +++++--- ...TimeZoneProviderEventPreProcessorTest.java | 5 +- 22 files changed, 362 insertions(+), 112 deletions(-) create mode 100644 core/tests/coretests/src/android/service/timezone/TimeZoneProviderSuggestionTest.java 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 timeZoneIds) { return TimeZoneProviderEvent.createSuggestionEvent( + ARBITRARY_TIME_MILLIS, new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS) .setTimeZoneIds(timeZoneIds) @@ -1137,10 +1138,13 @@ public class ControllerImplTest { // (initialization timeout * 2) < uncertainty delay // // That makes the order of initialization timeout Vs uncertainty delay deterministic. - static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); - static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); + private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); + private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15); + private static final Duration PROVIDER_EVENT_FILTERING_AGE_THRESHOLD = + Duration.ofMinutes(3); + private final LocationTimeZoneProviderController mController; private ConfigurationInternal mConfigurationInternal; @@ -1172,6 +1176,11 @@ public class ControllerImplTest { return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ; } + @Override + Duration getProviderEventFilteringAgeThreshold() { + return PROVIDER_EVENT_FILTERING_AGE_THRESHOLD; + } + @Override Duration getUncertaintyDelay() { return UNCERTAINTY_DELAY; @@ -1255,7 +1264,7 @@ public class ControllerImplTest { } @Override - void onStartUpdates(Duration initializationTimeout) { + void onStartUpdates(Duration initializationTimeout, Duration eventFilteringAgeThreshold) { // Nothing needed for tests. } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java index e552dbee7fd22..52e9d3a06fe28 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java @@ -30,7 +30,8 @@ public final class FakeTimeZoneProviderEventPreProcessor @Override public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) { if (mIsUncertain) { - return TimeZoneProviderEvent.createUncertainEvent(); + return TimeZoneProviderEvent.createUncertainEvent( + timeZoneProviderEvent.getCreationElapsedMillis()); } return timeZoneProviderEvent; } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java index ba7d3036a4a8d..cb2905d2266a0 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java @@ -92,10 +92,12 @@ public class LocationTimeZoneProviderTest { ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3); provider.startUpdates(config, arbitraryInitializationTimeout, - arbitraryInitializationTimeoutFuzz); + arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold); - provider.assertOnStartCalled(arbitraryInitializationTimeout); + provider.assertOnStartCalled( + arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold); currentState = assertAndReturnProviderState( provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING); @@ -118,7 +120,8 @@ public class LocationTimeZoneProviderTest { .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) .setTimeZoneIds(Arrays.asList("Europe/London")) .build(); - TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); + TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent( + ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion); provider.simulateProviderEventReceived(event); currentState = assertAndReturnProviderState( @@ -130,7 +133,7 @@ public class LocationTimeZoneProviderTest { mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN); // Simulate an uncertain event being received. - event = TimeZoneProviderEvent.createUncertainEvent(); + event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS); provider.simulateProviderEventReceived(event); currentState = assertAndReturnProviderState( @@ -179,8 +182,9 @@ public class LocationTimeZoneProviderTest { ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + Duration eventFilteringAgeThreshold = Duration.ofMinutes(3); provider.startUpdates(config, arbitraryInitializationTimeout, - arbitraryInitializationTimeoutFuzz); + arbitraryInitializationTimeoutFuzz, eventFilteringAgeThreshold); provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING); // Simulate a suggestion event being received. @@ -188,12 +192,13 @@ public class LocationTimeZoneProviderTest { .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) .setTimeZoneIds(Arrays.asList("Europe/London")) .build(); - TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); + TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent( + ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion); provider.simulateProviderEventReceived(event); provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN); // Simulate an uncertain event being received. - event = TimeZoneProviderEvent.createUncertainEvent(); + event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS); provider.simulateProviderEventReceived(event); provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN); @@ -221,16 +226,17 @@ public class LocationTimeZoneProviderTest { ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + Duration eventFilteringAgeThreshold = Duration.ofMinutes(3); provider.startUpdates(config, arbitraryInitializationTimeout, - arbitraryInitializationTimeoutFuzz); + arbitraryInitializationTimeoutFuzz, eventFilteringAgeThreshold); List invalidTimeZoneIds = asList("Atlantic/Atlantis"); TimeZoneProviderSuggestion invalidIdSuggestion = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) .setTimeZoneIds(invalidTimeZoneIds) .build(); - TimeZoneProviderEvent event = - TimeZoneProviderEvent.createSuggestionEvent(invalidIdSuggestion); + TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent( + ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion); provider.simulateProviderEventReceived(event); provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN); } @@ -278,6 +284,7 @@ public class LocationTimeZoneProviderTest { private boolean mOnDestroyCalled; private boolean mOnStartUpdatesCalled; private Duration mInitializationTimeout; + private Duration mEventFilteringAgeThreshold; private boolean mOnStopUpdatesCalled; /** Creates the instance. */ @@ -301,9 +308,11 @@ public class LocationTimeZoneProviderTest { } @Override - void onStartUpdates(@NonNull Duration initializationTimeout) { + void onStartUpdates(@NonNull Duration initializationTimeout, + @NonNull Duration eventFilteringAgeThreshold) { mOnStartUpdatesCalled = true; mInitializationTimeout = initializationTimeout; + mEventFilteringAgeThreshold = eventFilteringAgeThreshold; } @Override @@ -320,9 +329,11 @@ public class LocationTimeZoneProviderTest { assertTrue(mOnInitializeCalled); } - void assertOnStartCalled(Duration expectedInitializationTimeout) { + void assertOnStartCalled(Duration expectedInitializationTimeout, + Duration eventFilteringAgeThreshold) { assertTrue(mOnStartUpdatesCalled); assertEquals(expectedInitializationTimeout, mInitializationTimeout); + assertEquals(eventFilteringAgeThreshold, mEventFilteringAgeThreshold); } void simulateProviderEventReceived(TimeZoneProviderEvent event) { diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java index f0ae9de360aa0..ab4fe2938bcf5 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java @@ -54,14 +54,17 @@ public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest { for (String timeZone : nonExistingTimeZones) { TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone); + TimeZoneProviderEvent expectedResultEvent = + TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis()); assertWithMessage(timeZone + " is not a valid time zone") .that(mPreProcessor.preProcess(event)) - .isEqualTo(TimeZoneProviderEvent.createUncertainEvent()); + .isEqualTo(expectedResultEvent); } } private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) { return TimeZoneProviderEvent.createSuggestionEvent( + ARBITRARY_TIME_MILLIS, new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Arrays.asList(timeZoneIds)) .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)