Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 503ba60b authored by Neil Fuller's avatar Neil Fuller
Browse files

Handle repeated uncertainty better

When a primary LTZP reports it is uncertain, the uncertainty timer is
started in the location_time_zone_manager before the uncertainty is
passed on.  This gives the primary LTZP a period to "change its mind"
and also the secondard is started. Once the timer completes without the
uncertainty being contradicted the uncertainty is passed on to the
time_zone_detector. "Certain" reports are always passed on immediately
and cause the timer to be cancelled.

The old logic would repeat this timeout logic even if the
location_time_zone_manager had already completed the uncertainty timeout
and reported it was uncertain.

The new logic will pass on uncertainty immediately if the
location_time_zone_manager is already uncertain. There's no obvious
benefit to starting the timeout again and it looks confusing in the
logs.

Bug: 268162169
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
Change-Id: Id596de4067dc41cb1e9d46a920e7610a0eb06933
parent 46c8b8b2
Loading
Loading
Loading
Loading
+25 −11
Original line number Diff line number Diff line
@@ -303,8 +303,7 @@ class LocationTimeZoneProviderController implements Dumpable {
    private void reportSuggestionEvent(
            @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
        LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
                algorithmStatus, suggestion);
        LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
        event.addDebugInfo(reason);
        reportEvent(event);
    }
@@ -728,6 +727,20 @@ class LocationTimeZoneProviderController implements Dumpable {
        // Start the uncertainty timeout if needed to ensure the controller will eventually make an
        // uncertain suggestion if no success event arrives in time to counteract it.
        if (!mUncertaintyTimeoutQueue.hasQueued()) {
            if (STATE_UNCERTAIN.equals(mState.get())) {
                // If the controller is already uncertain, there's no reason to start a timeout;
                // just forward the suggestion immediately to make it obvious in the logs what has
                // happened. Making a new suggestion potentially captures new LTZP status info.
                GeolocationTimeZoneSuggestion suggestion =
                        GeolocationTimeZoneSuggestion.createUncertainSuggestion(
                                uncertaintyStartedElapsedMillis);
                String debugInfo = "Uncertainty received from " + provider.getName() + ":"
                        + " primary=" + mPrimaryProvider
                        + ", secondary=" + mSecondaryProvider
                        + ", uncertaintyStarted="
                        + Duration.ofMillis(uncertaintyStartedElapsedMillis);
                reportSuggestionEvent(suggestion, debugInfo);
            } else {
                debugLog("Starting uncertainty timeout: reason=" + reason);

                Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
@@ -736,12 +749,13 @@ class LocationTimeZoneProviderController implements Dumpable {
                                provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
                        uncertaintyDelay.toMillis());
            }
        }

        if (provider == mPrimaryProvider) {
            // (Try to) start the secondary. It could already be started, or enabling might not
            // succeed if the provider has previously reported it is perm failed. The uncertainty
            // timeout (set above) is used to ensure that an uncertain suggestion will be made if
            // the secondary cannot generate a success event in time.
            // timeout (may be set above) is used to ensure that an uncertain suggestion will be
            // made if the secondary cannot generate a success event in time.
            tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
        }
    }
+163 −18
Original line number Diff line number Diff line
@@ -88,18 +88,21 @@ public class LocationTimeZoneProviderControllerTest {
    private static final long ARBITRARY_TIME_MILLIS = 12345L;

    private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1 =
            createSuggestionEvent(asList("Europe/London"));
            createSuggestionEvent(ARBITRARY_TIME_MILLIS, asList("Europe/London"));
    private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
            createSuggestionEvent(asList("Europe/Paris"));
            createSuggestionEvent(ARBITRARY_TIME_MILLIS + 1, asList("Europe/Paris"));
    private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
            new TimeZoneProviderStatus.Builder()
                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
                    .build();
    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1 =
            TimeZoneProviderEvent.createUncertainEvent(
                    ARBITRARY_TIME_MILLIS, UNCERTAIN_PROVIDER_STATUS);
    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2 =
            TimeZoneProviderEvent.createUncertainEvent(
                    ARBITRARY_TIME_MILLIS + 1, UNCERTAIN_PROVIDER_STATUS);
    private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
            TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test");

@@ -328,7 +331,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Finally, the uncertainty timeout should cause the controller to make an uncertain
        // suggestion.
        mTestThreadingDomain.executeNext();
        mTestThreadingDomain.executeAll();

        assertControllerState(controller, STATE_UNCERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -649,7 +652,7 @@ public class LocationTimeZoneProviderControllerTest {
        // cause a suggestion to be made straight away, but the uncertainty timeout should be
        // started and the secondary should be started.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -680,7 +683,7 @@ public class LocationTimeZoneProviderControllerTest {
        // cause a suggestion to be made straight away, but the uncertainty timeout should be
        // started. Both providers are now started, with no initialization timeout set.
        mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -693,7 +696,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
        // suggestion should be made.
        mTestThreadingDomain.executeNext();
        mTestThreadingDomain.executeAll();

        assertControllerState(controller, STATE_UNCERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -702,7 +705,7 @@ public class LocationTimeZoneProviderControllerTest {
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
        assertFalse(controller.isUncertaintyTimeoutSet());
    }

@@ -744,7 +747,7 @@ public class LocationTimeZoneProviderControllerTest {
        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
        // timeout should be started and the secondary should be started.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -771,6 +774,147 @@ public class LocationTimeZoneProviderControllerTest {
        assertFalse(controller.isUncertaintyTimeoutSet());
    }

    @Test
    public void enabled_uncertaintyDuringUncertaintyTimeoutTriggersNoSuggestion() {
        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
                mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
                mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
        TestEnvironment testEnvironment = new TestEnvironment(
                mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);

        // Initialize and check initial state.
        controller.initialize(testEnvironment, mTestCallback);

        assertControllerState(controller, STATE_INITIALIZING);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
        mTestMetricsLogger.assertStateChangesAndCommit(
                STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
                DETECTION_ALGORITHM_STATUS_RUNNING);
        assertFalse(controller.isUncertaintyTimeoutSet());

        // Simulate a location event being received from the primary provider. This should cause a
        // suggestion to be made.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
        mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
        assertFalse(controller.isUncertaintyTimeoutSet());

        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
        // timeout should be started and the secondary should be started.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit();
        mTestCallback.assertNoEventReported();
        assertUncertaintyTimeoutSet(testEnvironment, controller);

        // Another uncertain suggestion from the primary during the uncertainty timeout should have
        // no effect.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit();
        mTestCallback.assertNoEventReported();
        assertUncertaintyTimeoutSet(testEnvironment, controller);
    }

    @Test
    public void enabled_uncertaintyAfterUncertaintyTimeoutTriggersImmediateSuggestion() {
        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
                mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
                mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
        TestEnvironment testEnvironment = new TestEnvironment(
                mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);

        // Initialize and check initial state.
        controller.initialize(testEnvironment, mTestCallback);

        assertControllerState(controller, STATE_INITIALIZING);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
        mTestMetricsLogger.assertStateChangesAndCommit(
                STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
                DETECTION_ALGORITHM_STATUS_RUNNING);
        assertFalse(controller.isUncertaintyTimeoutSet());

        // Simulate a location event being received from the primary provider. This should cause a
        // suggestion to be made.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
        mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
        assertFalse(controller.isUncertaintyTimeoutSet());

        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
        // timeout should be started and the secondary should be started.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit();
        mTestCallback.assertNoEventReported();
        assertUncertaintyTimeoutSet(testEnvironment, controller);

        // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
        // suggestion should be made.
        mTestThreadingDomain.executeAll();

        assertControllerState(controller, STATE_UNCERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
        assertFalse(controller.isUncertaintyTimeoutSet());

        // Another uncertain suggestion from the primary should cause an immediate suggestion.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2);

        assertControllerState(controller, STATE_UNCERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
        mTestMetricsLogger.assertStateChangesAndCommit();
        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2);
        assertFalse(controller.isUncertaintyTimeoutSet());
    }

    @Test
    public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
@@ -965,7 +1109,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Simulate uncertainty from the secondary.
        mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_INITIALIZING);
        mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
@@ -991,7 +1135,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Simulate uncertainty from the secondary.
        mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
@@ -1085,7 +1229,7 @@ public class LocationTimeZoneProviderControllerTest {
        // give this test the opportunity to simulate its failure. Then it will be possible to
        // demonstrate controller behavior with only the primary working.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_INITIALIZING);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1124,7 +1268,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Simulate uncertainty from the primary. The secondary cannot be started.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_CERTAIN);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1160,7 +1304,7 @@ public class LocationTimeZoneProviderControllerTest {
        // give this test the opportunity to simulate its failure. Then it will be possible to
        // demonstrate controller behavior with only the primary working.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        assertControllerState(controller, STATE_INITIALIZING);
        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1282,7 +1426,7 @@ public class LocationTimeZoneProviderControllerTest {

        // Simulate an uncertain event from the primary. This will start the secondary.
        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);

        {
            LocationTimeZoneManagerServiceState state = controller.getStateForTests();
@@ -1471,18 +1615,19 @@ public class LocationTimeZoneProviderControllerTest {
                controller.getUncertaintyTimeoutDelayMillis());
    }

    private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
    private static TimeZoneProviderEvent createSuggestionEvent(
            long elapsedRealtimeMillis, @NonNull List<String> timeZoneIds) {
        TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                .build();
        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
                .setElapsedRealtimeMillis(elapsedRealtimeMillis)
                .setTimeZoneIds(timeZoneIds)
                .build();
        return TimeZoneProviderEvent.createSuggestionEvent(
                ARBITRARY_TIME_MILLIS, suggestion, providerStatus);
                elapsedRealtimeMillis, suggestion, providerStatus);
    }

    private static void assertControllerState(LocationTimeZoneProviderController controller,
+6 −0
Original line number Diff line number Diff line
@@ -142,4 +142,10 @@ class TestThreadingDomain extends ThreadingDomain {
        mCurrentTimeMillis = queued.executionTimeMillis;
        queued.runnable.run();
    }

    void executeAll() {
        while (!mQueue.isEmpty()) {
            executeNext();
        }
    }
}