diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java index 2b3c0bff8548c..0d284fc2de4fe 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -73,6 +73,10 @@ class ControllerImpl extends LocationTimeZoneProviderController { // Non-null after initialize() private Callback mCallback; + /** Indicates both providers have completed initialization. */ + @GuardedBy("mSharedLock") + private boolean mProvidersInitialized; + /** * Used for scheduling uncertainty timeouts, i.e after a provider has reported uncertainty. * This timeout is not provider-specific: it is started when the controller becomes uncertain @@ -108,6 +112,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { ControllerImpl.this::onProviderStateChange; mPrimaryProvider.initialize(providerListener); mSecondaryProvider.initialize(providerListener); + mProvidersInitialized = true; alterProvidersStartedStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); @@ -322,6 +327,16 @@ class ControllerImpl extends LocationTimeZoneProviderController { assertProviderKnown(provider); synchronized (mSharedLock) { + // Ignore provider state changes during initialization. e.g. if the primary provider + // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not + // be ready to take over yet. + if (!mProvidersInitialized) { + warnLog("onProviderStateChange: Ignoring provider state change because both" + + " providers have not yet completed initialization." + + " providerState=" + providerState); + return; + } + switch (providerState.stateEnum) { case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STOPPED: diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java index 5cff2081e59e9..972b3bb8b4593 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java @@ -88,6 +88,81 @@ public class ControllerImplTest { new TestLocationTimeZoneProvider(mTestThreadingDomain, "secondary"); } + @Test + public void initializationFailure_primary() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() + .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); + + mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void initializationFailure_secondary() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() + .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); + + mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void initializationFailure_both() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); + mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + @Test public void initialState_started() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, @@ -1097,6 +1172,7 @@ public class ControllerImplTest { /** Used to track historic provider states for tests. */ private final TestState mTestProviderState = new TestState<>(); + private boolean mFailDuringInitialization; private boolean mInitialized; private boolean mDestroyed; @@ -1107,9 +1183,16 @@ public class ControllerImplTest { super(threadingDomain, providerName); } + public void setFailDuringInitialization(boolean failInitialization) { + mFailDuringInitialization = failInitialization; + } + @Override void onInitialize() { mInitialized = true; + if (mFailDuringInitialization) { + throw new RuntimeException("Simulated initialization failure"); + } } @Override