diff options
4 files changed, 577 insertions, 62 deletions
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 99414c4cdf91..d48263722d38 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -44,16 +44,22 @@ import java.util.List; import java.util.Objects; /** - * A real implementation of {@link LocationTimeZoneProviderController} that supports a single - * {@link LocationTimeZoneProvider}. + * A real implementation of {@link LocationTimeZoneProviderController} that supports a primary and a + * secondary {@link LocationTimeZoneProvider}. * - * TODO(b/152744911): This implementation currently only supports a single ("primary") provider. - * Support for a secondary provider will be added in a later commit. + * <p>The primary is used until it fails or becomes uncertain. The secondary will then be enabled. + * The controller will immediately make suggestions based on "certain" {@link + * LocationTimeZoneEvent}s, i.e. events that demonstrate the provider is certain what the time zone + * is. The controller will not make immediate suggestions based on "uncertain" events, giving + * providers time to change their mind. This also gives the secondary provider time to initialize + * when the primary becomes uncertain. */ class ControllerImpl extends LocationTimeZoneProviderController { @NonNull private final LocationTimeZoneProvider mPrimaryProvider; + @NonNull private final LocationTimeZoneProvider mSecondaryProvider; + @GuardedBy("mSharedLock") // Non-null after initialize() private ConfigurationInternal mCurrentUserConfiguration; @@ -67,7 +73,9 @@ class ControllerImpl extends LocationTimeZoneProviderController { private Callback mCallback; /** - * Used for scheduling uncertainty timeouts, i.e after the provider has reported uncertainty. + * 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 + * due to events it has received from one or other provider. */ @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue; @@ -77,10 +85,12 @@ class ControllerImpl extends LocationTimeZoneProviderController { private GeolocationTimeZoneSuggestion mLastSuggestion; ControllerImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull LocationTimeZoneProvider primaryProvider) { + @NonNull LocationTimeZoneProvider primaryProvider, + @NonNull LocationTimeZoneProvider secondaryProvider) { super(threadingDomain); mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue(); mPrimaryProvider = Objects.requireNonNull(primaryProvider); + mSecondaryProvider = Objects.requireNonNull(secondaryProvider); } @Override @@ -96,8 +106,9 @@ class ControllerImpl extends LocationTimeZoneProviderController { LocationTimeZoneProvider.ProviderListener providerListener = ControllerImpl.this::onProviderStateChange; mPrimaryProvider.initialize(providerListener); + mSecondaryProvider.initialize(providerListener); - alterProviderEnabledStateIfRequired( + alterProvidersEnabledStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); } } @@ -115,15 +126,15 @@ class ControllerImpl extends LocationTimeZoneProviderController { if (!newConfig.equals(oldConfig)) { if (newConfig.getUserId() != oldConfig.getUserId()) { - // If the user changed, disable the provider if needed. It may be re-enabled for - // the new user below if their settings allow. + // If the user changed, disable the providers if needed. They may be re-enabled + // for the new user immediately afterwards if their settings allow. debugLog("User changed. old=" + oldConfig.getUserId() - + ", new=" + newConfig.getUserId() + ": Disabling provider"); - disableProvider(); + + ", new=" + newConfig.getUserId() + ": Disabling providers"); + disableProviders(); - alterProviderEnabledStateIfRequired(null /* oldConfiguration */, newConfig); + alterProvidersEnabledStateIfRequired(null /* oldConfiguration */, newConfig); } else { - alterProviderEnabledStateIfRequired(oldConfig, newConfig); + alterProvidersEnabledStateIfRequired(oldConfig, newConfig); } } } @@ -140,10 +151,11 @@ class ControllerImpl extends LocationTimeZoneProviderController { } @GuardedBy("mSharedLock") - private void disableProvider() { + private void disableProviders() { disableProviderIfEnabled(mPrimaryProvider); + disableProviderIfEnabled(mSecondaryProvider); - // By definition, if the provider is disabled, the controller is uncertain. + // By definition, if both providers are disabled, the controller is uncertain. cancelUncertaintyTimeout(); } @@ -181,7 +193,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { } /** - * Sets the provider into the correct enabled/disabled state for the {@code newConfiguration} + * Sets the providers into the correct enabled/disabled state for the {@code newConfiguration} * and, if there is a provider state change, makes any suggestions required to inform the * downstream time zone detection code. * @@ -190,7 +202,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { * or when a new configuration has been received. */ @GuardedBy("mSharedLock") - private void alterProviderEnabledStateIfRequired( + private void alterProvidersEnabledStateIfRequired( @Nullable ConfigurationInternal oldConfiguration, @NonNull ConfigurationInternal newConfiguration) { @@ -203,21 +215,40 @@ class ControllerImpl extends LocationTimeZoneProviderController { return; } + // The check above ensures that the logic below only executes if providers are going from + // {enabled *} -> {disabled}, or {disabled} -> {enabled initializing}. If this changes in + // future and there could be {enabled *} -> {enabled *} cases, or cases where the provider + // can't be assumed to go straight to the {enabled initializing} state, then the logic below + // would need to cover extra conditions, for example: + // 1) If the primary is in {enabled uncertain}, the secondary should be enabled. + // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty + // timeout started when the primary entered {enabled uncertain} should be cancelled. + if (newGeoDetectionEnabled) { // Try to enable the primary provider. tryEnableProvider(mPrimaryProvider, newConfiguration); + // The secondary should only ever be enabled if the primary now isn't enabled (i.e. it + // couldn't become {enabled initializing} because it is {perm failed}). ProviderState newPrimaryState = mPrimaryProvider.getCurrentState(); if (!newPrimaryState.isEnabled()) { - // If the provider is perm failed then the controller is immediately considered - // uncertain. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - "Provider is failed:" - + " primary=" + mPrimaryProvider.getCurrentState()); - makeSuggestion(suggestion); + // If the primary provider is {perm failed} then the controller must try to enable + // the secondary. + tryEnableProvider(mSecondaryProvider, newConfiguration); + + ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); + if (!newSecondaryState.isEnabled()) { + // If both providers are {perm failed} then the controller immediately + // becomes uncertain. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "Providers are failed:" + + " primary=" + mPrimaryProvider.getCurrentState() + + " secondary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion); + } } } else { - disableProvider(); + disableProviders(); // There can be an uncertainty timeout set if the controller most recently received // an uncertain event. This is a no-op if there isn't a timeout set. @@ -300,35 +331,63 @@ class ControllerImpl extends LocationTimeZoneProviderController { } private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) { - if (provider != mPrimaryProvider) { + if (provider != mPrimaryProvider && provider != mSecondaryProvider) { throw new IllegalArgumentException("Unknown provider: " + provider); } } /** - * Called when the provider has reported that it has failed permanently. + * Called when a provider has reported that it has failed permanently. */ @GuardedBy("mSharedLock") private void handleProviderFailedStateChange(@NonNull ProviderState providerState) { LocationTimeZoneProvider failedProvider = providerState.provider; + ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState(); + ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState(); + + // If a provider has failed, the other may need to be enabled. + if (failedProvider == mPrimaryProvider) { + if (secondaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) { + // The primary must have failed. Try to enable the secondary. This does nothing if + // the provider is already enabled, and will leave the provider in + // {enabled initializing} if the provider is disabled. + tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration); + } + } else if (failedProvider == mSecondaryProvider) { + // No-op: The secondary will only be active if the primary is uncertain or is failed. + // So, there the primary should not need to be enabled when the secondary fails. + if (primaryCurrentState.stateEnum != PROVIDER_STATE_ENABLED_UNCERTAIN + && primaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) { + warnLog("Secondary provider unexpected reported a failure:" + + " failed provider=" + failedProvider.getName() + + ", primary provider=" + mPrimaryProvider + + ", secondary provider=" + mSecondaryProvider); + } + } - // If the provider is newly perm failed then the controller is uncertain by - // definition. - cancelUncertaintyTimeout(); + // If both providers are now failed, the controller needs to tell the next component in the + // time zone detection process. + if (primaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED + && secondaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED) { - // If the provider is now failed, then we must send a suggestion informing the time - // zone detector that there are no further updates coming in future. + // If both providers are newly failed then the controller is uncertain by definition + // and it will never recover so it can send a suggestion immediately. + cancelUncertaintyTimeout(); - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - "The provider is permanently failed:" - + " provider=" + failedProvider); - makeSuggestion(suggestion); + // If both providers are now failed, then a suggestion must be sent informing the time + // zone detector that there are no further updates coming in future. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "Both providers are permanently failed:" + + " primary=" + primaryCurrentState.provider + + ", secondary=" + secondaryCurrentState.provider); + makeSuggestion(suggestion); + } } /** * Called when a provider has changed state but just moved from one enabled state to another * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received. - * However, there are rare cases where the event can be null. + * However, there are rare cases where the event can also be null. */ @GuardedBy("mSharedLock") private void handleProviderEnabledStateChange(@NonNull ProviderState providerState) { @@ -395,6 +454,10 @@ class ControllerImpl extends LocationTimeZoneProviderController { // By definition, the controller is now certain. cancelUncertaintyTimeout(); + if (provider == mPrimaryProvider) { + disableProviderIfEnabled(mSecondaryProvider); + } + GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(timeZoneIds); suggestion.addDebugInfo(reason); @@ -421,6 +484,11 @@ class ControllerImpl extends LocationTimeZoneProviderController { mPrimaryProvider.dump(ipw, args); ipw.decreaseIndent(); // level 2 + ipw.println("Secondary Provider:"); + ipw.increaseIndent(); // level 2 + mSecondaryProvider.dump(ipw, args); + ipw.decreaseIndent(); // level 2 + ipw.decreaseIndent(); // level 1 } } @@ -470,6 +538,14 @@ class ControllerImpl extends LocationTimeZoneProviderController { mUncertaintyTimeoutQueue.runDelayed(() -> onProviderUncertaintyTimeout(provider), delay.toMillis()); } + + if (provider == mPrimaryProvider) { + // (Try to) enable the secondary. It could already be enabled, 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. + tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration); + } } private void onProviderUncertaintyTimeout(@NonNull LocationTimeZoneProvider provider) { @@ -478,7 +554,8 @@ class ControllerImpl extends LocationTimeZoneProviderController { synchronized (mSharedLock) { GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( "Uncertainty timeout triggered for " + provider.getName() + ":" - + " primary=" + mPrimaryProvider); + + " primary=" + mPrimaryProvider + + ", secondary=" + mSecondaryProvider); makeSuggestion(suggestion); } } @@ -498,6 +575,8 @@ class ControllerImpl extends LocationTimeZoneProviderController { LocationTimeZoneProvider targetProvider; if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) { targetProvider = mPrimaryProvider; + } else if (Objects.equals(mSecondaryProvider.getName(), targetProviderName)) { + targetProvider = mSecondaryProvider; } else { warnLog("Unable to process simulated binder provider event," + " unknown providerName in event=" + event); diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java index 97d3b826f3ff..a8589d43116d 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java @@ -44,8 +44,8 @@ import java.util.Objects; * are made to the {@link TimeZoneDetectorInternal}, and the {@link LocationTimeZoneProvider}s that * offer {@link android.location.timezone.LocationTimeZoneEvent}s. * - * TODO(b/152744911): This implementation currently only supports a primary provider. Support for a - * secondary provider must be added in a later commit. + * <p>For details of the time zone suggestion behavior, see {@link + * LocationTimeZoneProviderController}. * * <p>Implementation details: * @@ -109,6 +109,7 @@ public class LocationTimeZoneManagerService extends Binder { static final String TAG = "LocationTZDetector"; static final String PRIMARY_PROVIDER_NAME = "primary"; + static final String SECONDARY_PROVIDER_NAME = "secondary"; private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX = "persist.sys.location_tz_simulation_mode."; @@ -117,6 +118,8 @@ public class LocationTimeZoneManagerService extends Binder { private static final String PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION = "com.android.location.timezone.service.v1.PrimaryLocationTimeZoneProvider"; + private static final String SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION = + "com.android.location.timezone.service.v1.SecondaryLocationTimeZoneProvider"; @NonNull private final Context mContext; @@ -160,8 +163,9 @@ public class LocationTimeZoneManagerService extends Binder { // Called on an arbitrary thread during initialization. synchronized (mSharedLock) { LocationTimeZoneProvider primary = createPrimaryProvider(); + LocationTimeZoneProvider secondary = createSecondaryProvider(); mLocationTimeZoneDetectorController = - new ControllerImpl(mThreadingDomain, primary); + new ControllerImpl(mThreadingDomain, primary, secondary); ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( mThreadingDomain, mLocationTimeZoneDetectorController); @@ -189,6 +193,27 @@ public class LocationTimeZoneManagerService extends Binder { return createLocationTimeZoneProvider(PRIMARY_PROVIDER_NAME, proxy); } + private LocationTimeZoneProvider createSecondaryProvider() { + LocationTimeZoneProviderProxy proxy; + if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) { + proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); + } else { + // TODO Uncomment this code in a later commit. + throw new UnsupportedOperationException("Not implemented"); + /* + proxy = RealLocationTimeZoneProviderProxy.createAndRegister( + mContext, + mThreadingDomain, + SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION, + com.android.internal.R.bool.config_enableSecondaryLocationTimeZoneOverlay, + com.android.internal.R.string + .config_secondaryLocationTimeZoneProviderPackageName + ); + */ + } + return createLocationTimeZoneProvider(SECONDARY_PROVIDER_NAME, proxy); + } + private boolean isInSimulationMode(String providerName) { return SystemProperties.getBoolean( SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false); diff --git a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java index ef2e349fceaa..f1d37237872b 100644 --- a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java +++ b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java @@ -21,6 +21,7 @@ import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME; +import static com.android.server.location.timezone.LocationTimeZoneManagerService.SECONDARY_PROVIDER_NAME; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,7 +43,8 @@ import java.util.Objects; */ final class SimulatedBinderProviderEvent { - private static final List<String> VALID_PROVIDER_NAMES = Arrays.asList(PRIMARY_PROVIDER_NAME); + private static final List<String> VALID_PROVIDER_NAMES = + Arrays.asList(PRIMARY_PROVIDER_NAME, SECONDARY_PROVIDER_NAME); static final int INJECTED_EVENT_TYPE_ON_BIND = 1; static final int INJECTED_EVENT_TYPE_ON_UNBIND = 2; 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 9ee9259a23c2..292b7c6bf5ad 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 @@ -77,6 +77,7 @@ public class ControllerImplTest { private TestThreadingDomain mTestThreadingDomain; private TestCallback mTestCallback; private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; + private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider; @Before public void setUp() { @@ -87,26 +88,30 @@ public class ControllerImplTest { mTestCallback = new TestCallback(mTestThreadingDomain); mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary"); + mTestSecondaryLocationTimeZoneProvider = + new TestLocationTimeZoneProvider(mTestThreadingDomain, "secondary"); } @Test public void initialState_enabled() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); - // Initialize. After initialization the provider must be initialized and should be + // Initialize. After initialization the providers must be initialized and one should be // enabled. controllerImpl.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -114,17 +119,19 @@ public class ControllerImplTest { @Test public void initialState_disabled() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); - // Initialize. After initialization the provider must be initialized but should not be + // Initialize. After initialization the providers must be initialized but neither should be // enabled. controllerImpl.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -132,7 +139,7 @@ public class ControllerImplTest { @Test public void enabled_uncertaintySuggestionSentIfNoEventReceived() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -141,6 +148,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -148,9 +156,24 @@ public class ControllerImplTest { mTestThreadingDomain.executeNext(); // The primary should have reported uncertainty, which should trigger the controller to - // start the uncertainty timeout. + // start the uncertainty timeout and enable the secondary. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate time passing with no provider event being received from either the primary or + // secondary. + mTestThreadingDomain.executeNext(); + + // Now both initialization timeouts should have triggered. The uncertainty timeout should + // still not be triggered. + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); @@ -160,6 +183,8 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -167,7 +192,7 @@ public class ControllerImplTest { @Test public void enabled_eventReceivedBeforeInitializationTimeout() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -176,6 +201,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -186,6 +212,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -194,7 +221,7 @@ public class ControllerImplTest { @Test public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -203,6 +230,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -211,16 +239,59 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); // Simulate a location event being received from the primary provider. This should cause a - // suggestion to be made. + // suggestion to be made and the secondary to be shut down. mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void enabled_eventReceivedFromSecondaryAfterInitializationTimeout() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate time passing with no provider event being received from the primary. + mTestThreadingDomain.executeNext(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate a location event being received from the secondary provider. This should cause a + // suggestion to be made. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -229,7 +300,7 @@ public class ControllerImplTest { @Test public void enabled_repeatedPrimaryCertainty() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -238,6 +309,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -248,6 +320,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -258,6 +331,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -267,6 +341,70 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void enabled_repeatedSecondaryCertainty() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate time passing with no provider event being received from the primary. + mTestThreadingDomain.executeNext(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate a location event being received from the secondary provider. This should cause a + // suggestion to be made. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // A second, identical event should not cause another suggestion. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // And a third, different event should cause another suggestion. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -275,7 +413,7 @@ public class ControllerImplTest { @Test public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -284,6 +422,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -294,18 +433,48 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Simulate an uncertain event being received from the primary provider. This should not // cause a suggestion to be made straight away, but the uncertainty timeout should be - // started. + // started and the secondary should be enabled. mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate a location event being received from the secondary provider. This should cause a + // suggestion to be made, cancel the uncertainty timeout and ensure the secondary is + // considered initialized. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate an uncertain event being received from the secondary provider. This should not + // cause a suggestion to be made straight away, but the uncertainty timeout should be + // started. Both providers are now enabled, with no initialization timeout set. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); @@ -315,6 +484,8 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -322,7 +493,7 @@ public class ControllerImplTest { @Test public void enabled_briefUncertaintyTriggersNoSuggestion() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -331,6 +502,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -341,27 +513,32 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty - // timeout should be started. + // timeout should be started and the secondary should be enabled. mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); // And a success event from the primary provider should cause the controller to make another - // suggestion, the uncertainty timeout should be cancelled. + // suggestion, the uncertainty timeout should be cancelled and the secondary should be + // disabled again. mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -370,7 +547,7 @@ public class ControllerImplTest { @Test public void configChanges_enableAndDisableWithNoPreviousSuggestion() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); @@ -378,6 +555,7 @@ public class ControllerImplTest { controllerImpl.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -386,6 +564,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -393,6 +572,7 @@ public class ControllerImplTest { testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -400,7 +580,7 @@ public class ControllerImplTest { @Test public void configChanges_enableAndDisableWithPreviousSuggestion() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); @@ -408,6 +588,7 @@ public class ControllerImplTest { controllerImpl.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -416,6 +597,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -425,6 +607,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -436,6 +619,7 @@ public class ControllerImplTest { testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @@ -443,7 +627,7 @@ public class ControllerImplTest { @Test public void configChanges_userSwitch_enabledToEnabled() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -452,6 +636,7 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -463,6 +648,7 @@ public class ControllerImplTest { // and also clear the scheduled uncertainty suggestion. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -477,13 +663,74 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig( PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test + public void primaryPermFailure_secondaryEventsReceived() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate a failure location event being received from the primary provider. This should + // cause the secondary to be enabled. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate uncertainty from the secondary. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // And a success event from the secondary provider should cause the controller to make + // another suggestion, the uncertainty timeout should be cancelled. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate uncertainty from the secondary. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + } + + @Test public void primaryPermFailure_disableAndEnable() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider); + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -492,22 +739,26 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Simulate a failure location event being received from the primary provider. This should - // cause an uncertain suggestion to be made. + // cause the secondary to be enabled. mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); @@ -515,6 +766,164 @@ public class ControllerImplTest { testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void secondaryPermFailure_primaryEventsReceived() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate an uncertain event from the primary. This will enable the secondary, which will + // give this test the opportunity to simulate its failure. Then it will be possible to + // demonstrate controller behavior with only the primary working. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate failure event from the secondary. This should just affect the secondary's state. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // And a success event from the primary provider should cause the controller to make + // a suggestion, the uncertainty timeout should be cancelled. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate uncertainty from the primary. The secondary cannot be enabled. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + } + + @Test + public void secondaryPermFailure_disableAndEnable() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate an uncertain event from the primary. This will enable the secondary, which will + // give this test the opportunity to simulate its failure. Then it will be possible to + // demonstrate controller behavior with only the primary working. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate failure event from the secondary. This should just affect the secondary's state. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Now signal a config change so that geo detection is disabled. + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Now signal a config change so that geo detection is enabled. Only the primary can be + // enabled. + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void bothPermFailure_disableAndEnable() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate a failure event from the primary. This will enable the secondary. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate failure event from the secondary. + mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } |