Merge "Refactoring to make adding a secondary easier"
This commit is contained in:
@@ -18,7 +18,9 @@ package com.android.server.location.timezone;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -78,16 +80,19 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
synchronized (mSharedLock) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
switch (currentState.stateEnum) {
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN: {
|
||||
// Losing a remote provider is treated as becoming uncertain.
|
||||
String msg = "handleProviderLost reason=" + reason
|
||||
+ ", mProviderName=" + mProviderName
|
||||
+ ", currentState=" + currentState;
|
||||
debugLog(msg);
|
||||
// This is an unusual PROVIDER_STATE_ENABLED state because event == null
|
||||
// This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because
|
||||
// event == null
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
|
||||
msg);
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, null,
|
||||
currentState.currentUserConfiguration, msg);
|
||||
setCurrentState(newState, true);
|
||||
break;
|
||||
}
|
||||
@@ -118,7 +123,9 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
synchronized (mSharedLock) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
switch (currentState.stateEnum) {
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
debugLog("handleOnProviderBound mProviderName=" + mProviderName
|
||||
+ ", currentState=" + currentState + ": Provider is enabled.");
|
||||
break;
|
||||
|
||||
@@ -24,7 +24,9 @@ import static com.android.server.location.timezone.LocationTimeZoneManagerServic
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -38,6 +40,7 @@ import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -49,8 +52,7 @@ import java.util.Objects;
|
||||
*/
|
||||
class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
|
||||
@NonNull private final LocationTimeZoneProvider mProvider;
|
||||
@NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
|
||||
@NonNull private final LocationTimeZoneProvider mPrimaryProvider;
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
// Non-null after initialize()
|
||||
@@ -65,12 +67,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
private Callback mCallback;
|
||||
|
||||
/**
|
||||
* Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is
|
||||
* one.
|
||||
* Used for scheduling uncertainty timeouts, i.e after the provider has reported uncertainty.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
@Nullable
|
||||
private GeolocationTimeZoneSuggestion mPendingSuggestion;
|
||||
@NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
|
||||
|
||||
/** Contains the last suggestion actually made, if there is one. */
|
||||
@GuardedBy("mSharedLock")
|
||||
@@ -78,10 +77,10 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
private GeolocationTimeZoneSuggestion mLastSuggestion;
|
||||
|
||||
ControllerImpl(@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull LocationTimeZoneProvider provider) {
|
||||
@NonNull LocationTimeZoneProvider primaryProvider) {
|
||||
super(threadingDomain);
|
||||
mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue();
|
||||
mProvider = Objects.requireNonNull(provider);
|
||||
mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
|
||||
mPrimaryProvider = Objects.requireNonNull(primaryProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,8 +93,12 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
mCallback = Objects.requireNonNull(callback);
|
||||
mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
|
||||
|
||||
mProvider.initialize(ControllerImpl.this::onProviderStateChange);
|
||||
enableOrDisableProvider(mCurrentUserConfiguration);
|
||||
LocationTimeZoneProvider.ProviderListener providerListener =
|
||||
ControllerImpl.this::onProviderStateChange;
|
||||
mPrimaryProvider.initialize(providerListener);
|
||||
|
||||
alterProviderEnabledStateIfRequired(
|
||||
null /* oldConfiguration */, mCurrentUserConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,92 +118,148 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
// If the user changed, disable the provider if needed. It may be re-enabled for
|
||||
// the new user below if their settings allow.
|
||||
debugLog("User changed. old=" + oldConfig.getUserId()
|
||||
+ ", new=" + newConfig.getUserId());
|
||||
debugLog("Disabling LocationTimeZoneProviders as needed");
|
||||
if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) {
|
||||
mProvider.disable();
|
||||
}
|
||||
}
|
||||
+ ", new=" + newConfig.getUserId() + ": Disabling provider");
|
||||
disableProvider();
|
||||
|
||||
enableOrDisableProvider(newConfig);
|
||||
alterProviderEnabledStateIfRequired(null /* oldConfiguration */, newConfig);
|
||||
} else {
|
||||
alterProviderEnabledStateIfRequired(oldConfig, newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUncertaintyTimeoutSet() {
|
||||
return mUncertaintyTimeoutQueue.hasQueued();
|
||||
}
|
||||
|
||||
@Override
|
||||
long getUncertaintyTimeoutDelayMillis() {
|
||||
return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
|
||||
}
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
private void enableOrDisableProvider(@NonNull ConfigurationInternal configuration) {
|
||||
ProviderState providerState = mProvider.getCurrentState();
|
||||
boolean geoDetectionEnabled = configuration.getGeoDetectionEnabledBehavior();
|
||||
boolean providerWasEnabled = providerState.stateEnum == PROVIDER_STATE_ENABLED;
|
||||
if (geoDetectionEnabled) {
|
||||
switch (providerState.stateEnum) {
|
||||
case PROVIDER_STATE_DISABLED: {
|
||||
debugLog("Enabling " + mProvider);
|
||||
mProvider.enable(
|
||||
configuration, mEnvironment.getProviderInitializationTimeout());
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
debugLog("No need to enable " + mProvider + ": already enabled");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to enable " + mProvider + ": it is perm failed");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
warnLog("Unknown provider state: " + mProvider);
|
||||
break;
|
||||
private void disableProvider() {
|
||||
disableProviderIfEnabled(mPrimaryProvider);
|
||||
|
||||
// By definition, if the provider is disabled, the controller is uncertain.
|
||||
cancelUncertaintyTimeout();
|
||||
}
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
private void disableProviderIfEnabled(LocationTimeZoneProvider provider) {
|
||||
if (provider.getCurrentState().isEnabled()) {
|
||||
disableProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
private void disableProvider(LocationTimeZoneProvider provider) {
|
||||
ProviderState providerState = provider.getCurrentState();
|
||||
switch (providerState.stateEnum) {
|
||||
case PROVIDER_STATE_DISABLED: {
|
||||
debugLog("No need to disable " + provider + ": already disabled");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (providerState.stateEnum) {
|
||||
case PROVIDER_STATE_DISABLED: {
|
||||
debugLog("No need to disable " + mProvider + ": already enabled");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
debugLog("Disabling " + mProvider);
|
||||
mProvider.disable();
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to disable " + mProvider + ": it is perm failed");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
warnLog("Unknown provider state: " + mProvider);
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
debugLog("Disabling " + provider);
|
||||
provider.disable();
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to disable " + provider + ": it is perm failed");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
warnLog("Unknown provider state: " + provider);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isProviderEnabled =
|
||||
mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED;
|
||||
/**
|
||||
* Sets the provider 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.
|
||||
*
|
||||
* <p>This is a utility method that exists to avoid duplicated logic for the various cases when
|
||||
* provider enabled / disabled state may need to be set or changed, e.g. during initialization
|
||||
* or when a new configuration has been received.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
private void alterProviderEnabledStateIfRequired(
|
||||
@Nullable ConfigurationInternal oldConfiguration,
|
||||
@NonNull ConfigurationInternal newConfiguration) {
|
||||
|
||||
if (isProviderEnabled) {
|
||||
if (!providerWasEnabled) {
|
||||
// When a provider has first been enabled, we allow it some time for it to
|
||||
// initialize before sending its first event.
|
||||
Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout()
|
||||
.plus(mEnvironment.getProviderInitializationTimeoutFuzz());
|
||||
// This sets up an empty suggestion to trigger if no explicit "certain" or
|
||||
// "uncertain" suggestion preempts it within initializationTimeout. If, for some
|
||||
// reason, the provider does not produce any events then this scheduled suggestion
|
||||
// will ensure the controller makes at least an "uncertain" suggestion.
|
||||
suggestDelayed(createEmptySuggestion("No event received from provider in"
|
||||
+ " initializationTimeout=" + initializationTimeout),
|
||||
initializationTimeout);
|
||||
// Provider enabled / disabled states only need to be changed if geoDetectionEnabled has
|
||||
// changed.
|
||||
boolean oldGeoDetectionEnabled = oldConfiguration != null
|
||||
&& oldConfiguration.getGeoDetectionEnabledBehavior();
|
||||
boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
|
||||
if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newGeoDetectionEnabled) {
|
||||
// Try to enable the primary provider.
|
||||
tryEnableProvider(mPrimaryProvider, newConfiguration);
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// Clear any queued suggestions.
|
||||
clearDelayedSuggestion();
|
||||
disableProvider();
|
||||
|
||||
// If the provider is now not enabled, and a previous "certain" suggestion has been
|
||||
// made, then a new "uncertain" suggestion must be made to indicate the provider no
|
||||
// longer has an opinion and will not be sending updates.
|
||||
// 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.
|
||||
cancelUncertaintyTimeout();
|
||||
|
||||
// If a previous "certain" suggestion has been made, then a new "uncertain"
|
||||
// suggestion must now be made to indicate the controller {does not / no longer has}
|
||||
// an opinion and will not be sending further updates (until at least the config
|
||||
// changes again and providers are re-enabled).
|
||||
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
|
||||
suggestImmediate(createEmptySuggestion(
|
||||
"Provider disabled, clearing previous suggestion"));
|
||||
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
|
||||
"Provider is disabled:"
|
||||
+ " primary=" + mPrimaryProvider.getCurrentState());
|
||||
makeSuggestion(suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider,
|
||||
@NonNull ConfigurationInternal configuration) {
|
||||
ProviderState providerState = provider.getCurrentState();
|
||||
switch (providerState.stateEnum) {
|
||||
case PROVIDER_STATE_DISABLED: {
|
||||
debugLog("Enabling " + provider);
|
||||
provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(),
|
||||
mEnvironment.getProviderInitializationTimeoutFuzz());
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
debugLog("No need to enable " + provider + ": already enabled");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to enable " + provider + ": it is perm failed");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Unknown provider state:"
|
||||
+ " provider=" + provider
|
||||
+ ", state=" + providerState.stateEnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +276,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
+ " providerState=" + providerState);
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
// Entering enabled does not trigger an event, so this only happens if an event
|
||||
// is received while the provider is enabled.
|
||||
debugLog("onProviderStateChange: Received notification of an event while"
|
||||
@@ -228,10 +289,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Received notification of permanent failure for"
|
||||
+ " provider=" + providerState);
|
||||
GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
|
||||
"provider=" + providerState.provider
|
||||
+ " permanently failed: " + providerState);
|
||||
suggestImmediate(suggestion);
|
||||
providerFailedProcessEvent();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -242,24 +300,46 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
}
|
||||
|
||||
private void assertProviderKnown(LocationTimeZoneProvider provider) {
|
||||
if (provider != mProvider) {
|
||||
if (provider != mPrimaryProvider) {
|
||||
throw new IllegalArgumentException("Unknown provider: " + provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a provider has changed state but just moved from a PROVIDER_STATE_ENABLED state
|
||||
* to another PROVIDER_STATE_ENABLED state, usually as a result of a new {@link
|
||||
* LocationTimeZoneEvent} being received. There are some cases where event can be null.
|
||||
* Called when the provider has reported that it has failed permanently.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
private void providerFailedProcessEvent() {
|
||||
// If the provider is newly perm failed then the controller is uncertain by
|
||||
// definition.
|
||||
cancelUncertaintyTimeout();
|
||||
|
||||
// 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.
|
||||
|
||||
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
|
||||
"The provider is permanently failed:"
|
||||
+ " primary=" + mPrimaryProvider.getCurrentState());
|
||||
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.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
private void providerEnabledProcessEvent(@NonNull ProviderState providerState) {
|
||||
LocationTimeZoneProvider provider = providerState.provider;
|
||||
LocationTimeZoneEvent event = providerState.event;
|
||||
if (event == null) {
|
||||
// Implicit uncertainty, i.e. where the provider is enabled, but a problem has been
|
||||
// detected without having received an event. For example, if the process has detected
|
||||
// the loss of a binder-based provider. This is treated like explicit uncertainty, i.e.
|
||||
// where the provider has explicitly told this process it is uncertain.
|
||||
scheduleUncertainSuggestionIfNeeded(null);
|
||||
// the loss of a binder-based provider, or initialization took too long. This is treated
|
||||
// the same as explicit uncertainty, i.e. where the provider has explicitly told this
|
||||
// process it is uncertain.
|
||||
handleProviderUncertainty(provider, "provider=" + provider
|
||||
+ ", implicit uncertainty, event=null");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,22 +359,19 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
|
||||
switch (event.getEventType()) {
|
||||
case EVENT_TYPE_PERMANENT_FAILURE: {
|
||||
// This shouldn't happen. Providers cannot be enabled and have this event.
|
||||
// This shouldn't happen. A provider cannot be enabled and have this event.
|
||||
warnLog("Provider=" + providerState
|
||||
+ " is enabled, but event suggests it shouldn't be");
|
||||
break;
|
||||
}
|
||||
case EVENT_TYPE_UNCERTAIN: {
|
||||
scheduleUncertainSuggestionIfNeeded(event);
|
||||
handleProviderUncertainty(provider, "provider=" + provider
|
||||
+ ", explicit uncertainty. event=" + event);
|
||||
break;
|
||||
}
|
||||
case EVENT_TYPE_SUCCESS: {
|
||||
GeolocationTimeZoneSuggestion suggestion =
|
||||
new GeolocationTimeZoneSuggestion(event.getTimeZoneIds());
|
||||
suggestion.addDebugInfo("Event received provider=" + mProvider.getName()
|
||||
+ ", event=" + event);
|
||||
// Rely on the receiver to dedupe events. It is better to over-communicate.
|
||||
suggestImmediate(suggestion);
|
||||
handleProviderCertainty(provider, event.getTimeZoneIds(),
|
||||
"Event received provider=" + provider.getName() + ", event=" + event);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -304,30 +381,19 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates a provider has become uncertain with the event (if any) received that indicates
|
||||
* that.
|
||||
*
|
||||
* <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
|
||||
* this enables the most flexibility for the controller to enable other providers when there are
|
||||
* multiple ones available. The controller is therefore responsible for deciding when to make a
|
||||
* "uncertain" suggestion.
|
||||
*
|
||||
* <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
|
||||
* made later if nothing else preempts it. It can be preempted if the provider becomes certain
|
||||
* (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
|
||||
* within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
|
||||
* "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
|
||||
* the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
|
||||
* reset each time).
|
||||
*/
|
||||
private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
|
||||
if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
|
||||
GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
|
||||
"provider=" + mProvider + " became uncertain, event=" + event);
|
||||
// Only send the empty suggestion after the uncertainty delay.
|
||||
suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay());
|
||||
}
|
||||
@GuardedBy("mSharedLock")
|
||||
private void handleProviderCertainty(
|
||||
@NonNull LocationTimeZoneProvider provider,
|
||||
@Nullable List<String> timeZoneIds,
|
||||
@NonNull String reason) {
|
||||
// By definition, the controller is now certain.
|
||||
cancelUncertaintyTimeout();
|
||||
|
||||
GeolocationTimeZoneSuggestion suggestion =
|
||||
new GeolocationTimeZoneSuggestion(timeZoneIds);
|
||||
suggestion.addDebugInfo(reason);
|
||||
// Rely on the receiver to dedupe events. It is better to over-communicate.
|
||||
makeSuggestion(suggestion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -342,66 +408,74 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
ipw.println("providerInitializationTimeoutFuzz="
|
||||
+ mEnvironment.getProviderInitializationTimeoutFuzz());
|
||||
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
|
||||
ipw.println("mPendingSuggestion=" + mPendingSuggestion);
|
||||
ipw.println("mLastSuggestion=" + mLastSuggestion);
|
||||
|
||||
ipw.println("Provider:");
|
||||
ipw.println("Primary Provider:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
mProvider.dump(ipw, args);
|
||||
mPrimaryProvider.dump(ipw, args);
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.decreaseIndent(); // level 1
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends an immediate suggestion, cancelling any pending suggestion. */
|
||||
/** Sends an immediate suggestion, updating mLastSuggestion. */
|
||||
@GuardedBy("mSharedLock")
|
||||
private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) {
|
||||
debugLog("suggestImmediate: Executing suggestion=" + suggestion);
|
||||
mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion));
|
||||
mPendingSuggestion = null;
|
||||
private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
|
||||
debugLog("makeSuggestion: suggestion=" + suggestion);
|
||||
mCallback.suggest(suggestion);
|
||||
mLastSuggestion = suggestion;
|
||||
}
|
||||
|
||||
/** Clears any pending suggestion. */
|
||||
/** Clears the uncertainty timeout. */
|
||||
@GuardedBy("mSharedLock")
|
||||
private void clearDelayedSuggestion() {
|
||||
mDelayedSuggestionQueue.cancel();
|
||||
mPendingSuggestion = null;
|
||||
private void cancelUncertaintyTimeout() {
|
||||
mUncertaintyTimeoutQueue.cancel();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a delayed suggestion. There can only be one delayed suggestion at a time.
|
||||
* If there is a pending scheduled suggestion equal to the one passed, it will not be replaced.
|
||||
* Replacing a previous delayed suggestion has the effect of cancelling the timeout associated
|
||||
* with that previous suggestion.
|
||||
* Indicates a provider has become uncertain with the event (if any) received that indicates
|
||||
* that.
|
||||
*
|
||||
* <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
|
||||
* this enables the most flexibility for the controller to enable other providers when there are
|
||||
* multiple ones available. The controller is therefore responsible for deciding when to make a
|
||||
* "uncertain" suggestion.
|
||||
*
|
||||
* <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
|
||||
* made later if nothing else preempts it. It can be preempted if the provider becomes certain
|
||||
* (or does anything else that calls {@link #makeSuggestion(GeolocationTimeZoneSuggestion)})
|
||||
* within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
|
||||
* "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
|
||||
* the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
|
||||
* reset each time).
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
private void suggestDelayed(@NonNull GeolocationTimeZoneSuggestion suggestion,
|
||||
@NonNull Duration delay) {
|
||||
Objects.requireNonNull(suggestion);
|
||||
Objects.requireNonNull(delay);
|
||||
void handleProviderUncertainty(@NonNull LocationTimeZoneProvider provider, String reason) {
|
||||
Objects.requireNonNull(provider);
|
||||
|
||||
if (Objects.equals(mPendingSuggestion, suggestion)) {
|
||||
// Do not reset the timer.
|
||||
debugLog("suggestDelayed: Suggestion=" + suggestion + " is equal to existing."
|
||||
+ " Not scheduled.");
|
||||
return;
|
||||
// Start the uncertainty timeout if needed.
|
||||
if (!mUncertaintyTimeoutQueue.hasQueued()) {
|
||||
debugLog("Starting uncertainty timeout: reason=" + reason);
|
||||
|
||||
Duration delay = mEnvironment.getUncertaintyDelay();
|
||||
mUncertaintyTimeoutQueue.runDelayed(
|
||||
this::onProviderUncertaintyTimeout, delay.toMillis());
|
||||
}
|
||||
|
||||
debugLog("suggestDelayed: Scheduling suggestion=" + suggestion);
|
||||
mPendingSuggestion = suggestion;
|
||||
|
||||
mDelayedSuggestionQueue.runDelayed(() -> {
|
||||
debugLog("suggestDelayed: Executing suggestion=" + suggestion);
|
||||
mCallback.suggest(suggestion);
|
||||
mPendingSuggestion = null;
|
||||
mLastSuggestion = suggestion;
|
||||
}, delay.toMillis());
|
||||
}
|
||||
|
||||
private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) {
|
||||
private void onProviderUncertaintyTimeout() {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
|
||||
"Uncertainty timeout triggered:"
|
||||
+ " primary=" + mPrimaryProvider.getCurrentState());
|
||||
makeSuggestion(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
private static GeolocationTimeZoneSuggestion createUncertainSuggestion(String reason) {
|
||||
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
|
||||
suggestion.addDebugInfo(reason);
|
||||
return suggestion;
|
||||
@@ -412,17 +486,22 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
* If the provider name does not match a known provider, then the event is logged and discarded.
|
||||
*/
|
||||
void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
|
||||
if (!Objects.equals(mProvider.getName(), event.getProviderName())) {
|
||||
String targetProviderName = event.getProviderName();
|
||||
LocationTimeZoneProvider targetProvider;
|
||||
if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) {
|
||||
targetProvider = mPrimaryProvider;
|
||||
} else {
|
||||
warnLog("Unable to process simulated binder provider event,"
|
||||
+ " unknown providerName in event=" + event);
|
||||
return;
|
||||
}
|
||||
if (!(mProvider instanceof BinderLocationTimeZoneProvider)) {
|
||||
if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) {
|
||||
warnLog("Unable to process simulated binder provider event,"
|
||||
+ " provider is not a " + BinderLocationTimeZoneProvider.class
|
||||
+ " provider=" + targetProvider
|
||||
+ " is not a " + BinderLocationTimeZoneProvider.class
|
||||
+ ", event=" + event);
|
||||
return;
|
||||
}
|
||||
((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event);
|
||||
((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,8 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
// Called on an arbitrary thread during initialization.
|
||||
synchronized (mSharedLock) {
|
||||
LocationTimeZoneProvider primary = createPrimaryProvider();
|
||||
mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary);
|
||||
mLocationTimeZoneDetectorController =
|
||||
new ControllerImpl(mThreadingDomain, primary);
|
||||
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
|
||||
ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
|
||||
mThreadingDomain, mLocationTimeZoneDetectorController);
|
||||
|
||||
@@ -22,7 +22,9 @@ import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTA
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
@@ -33,6 +35,9 @@ import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
|
||||
import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.Dumpable;
|
||||
import com.android.server.timezonedetector.ReferenceWithHistory;
|
||||
@@ -73,8 +78,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
*/
|
||||
static class ProviderState {
|
||||
|
||||
@IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED,
|
||||
PROVIDER_STATE_PERM_FAILED })
|
||||
@IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING,
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN,
|
||||
PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED })
|
||||
@interface ProviderStateEnum {}
|
||||
|
||||
/**
|
||||
@@ -83,22 +89,33 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
static final int PROVIDER_STATE_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* The provider is currently enabled.
|
||||
* The provider is enabled and has not reported its first event.
|
||||
*/
|
||||
static final int PROVIDER_STATE_ENABLED = 1;
|
||||
static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1;
|
||||
|
||||
/**
|
||||
* The provider is currently disabled.
|
||||
* The provider is enabled and most recently reported a "success" event.
|
||||
*/
|
||||
static final int PROVIDER_STATE_ENABLED_CERTAIN = 2;
|
||||
|
||||
/**
|
||||
* The provider is enabled and most recently reported an "uncertain" event.
|
||||
*/
|
||||
static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3;
|
||||
|
||||
/**
|
||||
* The provider is disabled.
|
||||
*
|
||||
* This is the state after {@link #initialize} is called.
|
||||
*/
|
||||
static final int PROVIDER_STATE_DISABLED = 2;
|
||||
static final int PROVIDER_STATE_DISABLED = 4;
|
||||
|
||||
/**
|
||||
* The provider has failed and cannot be re-enabled.
|
||||
*
|
||||
* Providers may enter this state after a provider is enabled.
|
||||
*/
|
||||
static final int PROVIDER_STATE_PERM_FAILED = 3;
|
||||
static final int PROVIDER_STATE_PERM_FAILED = 5;
|
||||
|
||||
/** The {@link LocationTimeZoneProvider} the state is for. */
|
||||
public final @NonNull LocationTimeZoneProvider provider;
|
||||
@@ -108,14 +125,15 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
|
||||
/**
|
||||
* The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum}
|
||||
* is {@link #PROVIDER_STATE_ENABLED}, but it can be {@code null} then too if no event has
|
||||
* is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link
|
||||
* #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has
|
||||
* yet been received.
|
||||
*/
|
||||
@Nullable public final LocationTimeZoneEvent event;
|
||||
|
||||
/**
|
||||
* The user configuration associated with the current state. Only and always present when
|
||||
* {@link #stateEnum} is {@link #PROVIDER_STATE_ENABLED}.
|
||||
* {@link #stateEnum} is one of the enabled states.
|
||||
*/
|
||||
@Nullable public final ConfigurationInternal currentUserConfiguration;
|
||||
|
||||
@@ -133,7 +151,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
|
||||
|
||||
private ProviderState(@NonNull LocationTimeZoneProvider provider,
|
||||
@ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event,
|
||||
@ProviderStateEnum int stateEnum,
|
||||
@Nullable LocationTimeZoneEvent event,
|
||||
@Nullable ConfigurationInternal currentUserConfiguration,
|
||||
@Nullable String debugInfo) {
|
||||
this.provider = Objects.requireNonNull(provider);
|
||||
@@ -172,7 +191,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_DISABLED:
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
// These can go to each other or PROVIDER_STATE_PERM_FAILED.
|
||||
break;
|
||||
}
|
||||
@@ -200,7 +221,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
if (currentUserConfig == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Enabled state: currentUserConfig must not be null");
|
||||
@@ -223,6 +246,13 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */
|
||||
boolean isEnabled() {
|
||||
return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING
|
||||
|| stateEnum == PROVIDER_STATE_ENABLED_CERTAIN
|
||||
|| stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "State{"
|
||||
@@ -257,8 +287,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
switch (state) {
|
||||
case PROVIDER_STATE_DISABLED:
|
||||
return "Disabled (" + PROVIDER_STATE_DISABLED + ")";
|
||||
case PROVIDER_STATE_ENABLED:
|
||||
return "Enabled (" + PROVIDER_STATE_ENABLED + ")";
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")";
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")";
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN:
|
||||
return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")";
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")";
|
||||
case PROVIDER_STATE_UNKNOWN:
|
||||
@@ -279,6 +313,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
final ReferenceWithHistory<ProviderState> mCurrentState =
|
||||
new ReferenceWithHistory<>(10);
|
||||
|
||||
/**
|
||||
* Used for scheduling initialization timeouts, i.e. for providers that have just been enabled.
|
||||
*/
|
||||
@NonNull private final SingleRunnableQueue mInitializationTimeoutQueue;
|
||||
|
||||
// Non-null and effectively final after initialize() is called.
|
||||
ProviderListener mProviderListener;
|
||||
|
||||
@@ -286,6 +325,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName) {
|
||||
mThreadingDomain = Objects.requireNonNull(threadingDomain);
|
||||
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
|
||||
mSharedLock = threadingDomain.getLockObject();
|
||||
mProviderName = Objects.requireNonNull(providerName);
|
||||
}
|
||||
@@ -303,7 +343,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
mProviderListener = Objects.requireNonNull(providerListener);
|
||||
ProviderState currentState = ProviderState.createStartingState(this);
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_DISABLED, null, null, "initialize() called");
|
||||
PROVIDER_STATE_DISABLED, null, null,
|
||||
"initialize() called");
|
||||
setCurrentState(newState, false);
|
||||
|
||||
onInitialize();
|
||||
@@ -370,20 +411,41 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
* called using the handler thread from the {@link ThreadingDomain}.
|
||||
*/
|
||||
final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
|
||||
@NonNull Duration initializationTimeout) {
|
||||
@NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
assertCurrentState(PROVIDER_STATE_DISABLED);
|
||||
|
||||
ProviderState currentState = getCurrentState();
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */,
|
||||
currentUserConfiguration, "enable() called");
|
||||
setCurrentState(newState, false);
|
||||
|
||||
Duration delay = initializationTimeout.plus(initializationTimeoutFuzz);
|
||||
mInitializationTimeoutQueue.runDelayed(
|
||||
this::handleInitializationTimeout, delay.toMillis());
|
||||
|
||||
onEnable(initializationTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInitializationTimeout() {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) {
|
||||
// On initialization timeout the provider becomes uncertain.
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */,
|
||||
currentState.currentUserConfiguration, "initialization timeout");
|
||||
setCurrentState(newState, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by subclasses to do work during {@link #enable}.
|
||||
*/
|
||||
@@ -391,20 +453,24 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
|
||||
/**
|
||||
* Disables the provider. It is an error* to call this method except when the {@link
|
||||
* #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_ENABLED}. This method must be
|
||||
* #getCurrentState()} is one of the enabled states. This method must be
|
||||
* called using the handler thread from the {@link ThreadingDomain}.
|
||||
*/
|
||||
final void disable() {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
assertCurrentState(PROVIDER_STATE_ENABLED);
|
||||
assertIsEnabled();
|
||||
|
||||
ProviderState currentState = getCurrentState();
|
||||
ProviderState newState =
|
||||
currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called");
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_DISABLED, null, null, "disable() called");
|
||||
setCurrentState(newState, false);
|
||||
|
||||
if (mInitializationTimeoutQueue.hasQueued()) {
|
||||
mInitializationTimeoutQueue.cancel();
|
||||
}
|
||||
|
||||
onDisable();
|
||||
}
|
||||
}
|
||||
@@ -424,7 +490,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName
|
||||
+ ", locationTimeZoneEvent=" + locationTimeZoneEvent);
|
||||
|
||||
ProviderState currentState = getCurrentState();
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
int eventType = locationTimeZoneEvent.getEventType();
|
||||
switch (currentState.stateEnum) {
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
@@ -445,6 +511,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_PERM_FAILED, null, null, msg);
|
||||
setCurrentState(newState, true);
|
||||
if (mInitializationTimeoutQueue.hasQueued()) {
|
||||
mInitializationTimeoutQueue.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case EVENT_TYPE_SUCCESS:
|
||||
@@ -464,7 +533,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
}
|
||||
}
|
||||
}
|
||||
case PROVIDER_STATE_ENABLED: {
|
||||
case PROVIDER_STATE_ENABLED_INITIALIZING:
|
||||
case PROVIDER_STATE_ENABLED_CERTAIN:
|
||||
case PROVIDER_STATE_ENABLED_UNCERTAIN: {
|
||||
switch (eventType) {
|
||||
case EVENT_TYPE_PERMANENT_FAILURE: {
|
||||
String msg = "handleLocationTimeZoneEvent:"
|
||||
@@ -475,14 +546,27 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
ProviderState newState = currentState.newState(
|
||||
PROVIDER_STATE_PERM_FAILED, null, null, msg);
|
||||
setCurrentState(newState, true);
|
||||
if (mInitializationTimeoutQueue.hasQueued()) {
|
||||
mInitializationTimeoutQueue.cancel();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case EVENT_TYPE_UNCERTAIN:
|
||||
case EVENT_TYPE_SUCCESS: {
|
||||
ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED,
|
||||
@ProviderStateEnum int providerStateEnum;
|
||||
if (eventType == EVENT_TYPE_UNCERTAIN) {
|
||||
providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
} else {
|
||||
providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN;
|
||||
}
|
||||
ProviderState newState = currentState.newState(providerStateEnum,
|
||||
locationTimeZoneEvent, currentState.currentUserConfiguration,
|
||||
"handleLocationTimeZoneEvent() when enabled");
|
||||
setCurrentState(newState, true);
|
||||
if (mInitializationTimeoutQueue.hasQueued()) {
|
||||
mInitializationTimeoutQueue.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
@@ -503,11 +587,34 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
*/
|
||||
abstract void logWarn(String msg);
|
||||
|
||||
private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) {
|
||||
ProviderState currentState = getCurrentState();
|
||||
@GuardedBy("mSharedLock")
|
||||
private void assertIsEnabled() {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
if (!currentState.isEnabled()) {
|
||||
throw new IllegalStateException("Required an enabled state, but was " + currentState);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
private void assertCurrentState(@ProviderStateEnum int requiredState) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
if (currentState.stateEnum != requiredState) {
|
||||
throw new IllegalStateException(
|
||||
"Required stateEnum=" + requiredState + ", but was " + currentState);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isInitializationTimeoutSet() {
|
||||
synchronized (mSharedLock) {
|
||||
return mInitializationTimeoutQueue.hasQueued();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Duration getInitializationTimeoutDelay() {
|
||||
synchronized (mSharedLock) {
|
||||
return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.location.timezone;
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.Dumpable;
|
||||
@@ -85,6 +86,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
|
||||
*/
|
||||
abstract void onConfigChanged();
|
||||
|
||||
@VisibleForTesting
|
||||
abstract boolean isUncertaintyTimeoutSet();
|
||||
|
||||
@VisibleForTesting
|
||||
abstract long getUncertaintyTimeoutDelayMillis();
|
||||
|
||||
/**
|
||||
* Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
|
||||
* service. It can easily be faked for tests.
|
||||
|
||||
@@ -83,21 +83,14 @@ abstract class ThreadingDomain {
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that allows up to one {@link Runnable} to be queued on the handler, i.e. calling any
|
||||
* of the methods will cancel the execution of any previously queued / delayed runnable. All
|
||||
* A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link
|
||||
* #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All
|
||||
* methods must be called from the {@link ThreadingDomain}'s thread.
|
||||
*/
|
||||
final class SingleRunnableQueue {
|
||||
|
||||
/**
|
||||
* Runs the supplied {@link Runnable} synchronously on the threading domain's thread,
|
||||
* cancelling any queued but not-yet-executed {@link Runnable} previously added by this.
|
||||
* This method must be called from the threading domain's thread.
|
||||
*/
|
||||
void runSynchronously(Runnable r) {
|
||||
cancel();
|
||||
r.run();
|
||||
}
|
||||
private boolean mIsQueued;
|
||||
private long mDelayMillis;
|
||||
|
||||
/**
|
||||
* Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
|
||||
@@ -106,15 +99,48 @@ abstract class ThreadingDomain {
|
||||
*/
|
||||
void runDelayed(Runnable r, long delayMillis) {
|
||||
cancel();
|
||||
ThreadingDomain.this.postDelayed(r, this, delayMillis);
|
||||
mIsQueued = true;
|
||||
mDelayMillis = delayMillis;
|
||||
ThreadingDomain.this.postDelayed(() -> {
|
||||
mIsQueued = false;
|
||||
mDelayMillis = -2;
|
||||
r.run();
|
||||
}, this, delayMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if there is an item current queued. This method must be called from
|
||||
* the threading domain's thread.
|
||||
*/
|
||||
boolean hasQueued() {
|
||||
assertCurrentThread();
|
||||
return mIsQueued;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delay in milliseconds for the currently queued item. Throws {@link
|
||||
* IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}.
|
||||
* This method must be called from the threading domain's thread.
|
||||
*/
|
||||
long getQueuedDelayMillis() {
|
||||
assertCurrentThread();
|
||||
if (!mIsQueued) {
|
||||
throw new IllegalStateException("No item queued");
|
||||
}
|
||||
return mDelayMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
|
||||
* This method must be called from the threading domain's thread.
|
||||
*/
|
||||
public void cancel() {
|
||||
assertCurrentThread();
|
||||
removeQueuedRunnables(this);
|
||||
if (mIsQueued) {
|
||||
removeQueuedRunnables(this);
|
||||
}
|
||||
mIsQueued = false;
|
||||
mDelayMillis = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,22 @@
|
||||
*/
|
||||
package com.android.server.location.timezone;
|
||||
|
||||
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
|
||||
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.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_ID;
|
||||
import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@@ -39,6 +44,7 @@ import android.os.UserHandle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
|
||||
import com.android.server.timezonedetector.TestState;
|
||||
@@ -65,10 +71,12 @@ public class ControllerImplTest {
|
||||
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/Paris"));
|
||||
private static final LocationTimeZoneEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
|
||||
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_UNCERTAIN, null);
|
||||
private static final LocationTimeZoneEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
|
||||
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_PERMANENT_FAILURE, null);
|
||||
|
||||
private TestThreadingDomain mTestThreadingDomain;
|
||||
private TestCallback mTestCallback;
|
||||
private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider;
|
||||
private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -77,276 +85,446 @@ public class ControllerImplTest {
|
||||
// will never get a chance to execute.
|
||||
mTestThreadingDomain = new TestThreadingDomain();
|
||||
mTestCallback = new TestCallback(mTestThreadingDomain);
|
||||
mTestLocationTimeZoneProvider =
|
||||
mTestPrimaryLocationTimeZoneProvider =
|
||||
new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialState_enabled() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
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
|
||||
// enabled.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertInitialized();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialState_disabled() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
// Initialize. After initialization the provider must be initialized but should not be
|
||||
// enabled.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertInitialized();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsDisabled();
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate time passing with no event being received.
|
||||
// Simulate time passing with no provider event being received from the primary.
|
||||
mTestThreadingDomain.executeNext();
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
// The primary should have reported uncertainty, which should trigger the controller to
|
||||
// start the uncertainty timeout.
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
|
||||
|
||||
// Finally, the uncertainty timeout should cause the controller to make an uncertain
|
||||
// suggestion.
|
||||
mTestThreadingDomain.executeNext();
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertUncertainSuggestionMadeAndCommit();
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_uncertaintySuggestionCancelledIfEventReceived() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
public void enabled_eventReceivedBeforeInitializationTimeout() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received by the provider. This should cause a suggestion
|
||||
// to be made, and the timeout to be cleared.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// Simulate a location event being received from the primary provider. This should cause a
|
||||
// suggestion to be made.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_repeatedCertainty() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received by the provider. This should cause a suggestion
|
||||
// to be made, and the timeout to be cleared.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// 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);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
|
||||
|
||||
// Simulate a location event being received from the primary provider. This should cause a
|
||||
// suggestion to be made.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_repeatedPrimaryCertainty() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
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);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received from the primary provider. This should cause a
|
||||
// suggestion to be made.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.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.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// And a third, different event should cause another suggestion.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
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);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received from the primary provider. This should cause a
|
||||
// suggestion to be made and ensure the primary is considered initialized.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
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.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
|
||||
|
||||
// Simulate time passing. This means the uncertainty timeout should fire and the uncertain
|
||||
// suggestion should be made.
|
||||
mTestThreadingDomain.executeNext();
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertUncertainSuggestionMadeAndCommit();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabled_briefUncertaintyTriggersNoSuggestion() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received by the provider. This should cause a suggestion
|
||||
// to be made, and the timeout to be cleared.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// Simulate a location event being received from the primary provider. This should cause a
|
||||
// suggestion to be made.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Uncertainty should cause a suggestion to (only) be queued.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
|
||||
// timeout should be started.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay());
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
|
||||
|
||||
// And a third event should cause yet another suggestion and for the queued item to be
|
||||
// removed.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// And a success event from the primary provider should cause the controller to make another
|
||||
// suggestion, the uncertainty timeout should be cancelled.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configChanges_enableAndDisable() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsDisabled();
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Now signal a config change so that geo detection is enabled.
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.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);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsDisabled();
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configChanges_disableWithPreviousSuggestion() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
public void configChanges_enableAndDisableWithPreviousSuggestion() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a location event being received by the provider. This should cause a suggestion
|
||||
// to be made, and the timeout to be cleared.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// Now signal a config change so that geo detection is enabled.
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate a success event being received from the primary provider.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate the user disabling the provider.
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
// Now signal a config change so that geo detection is disabled.
|
||||
// Because there had been a previous suggestion, the controller should withdraw it
|
||||
// immediately to let the downstream components know that the provider can no longer be sure
|
||||
// of the time zone.
|
||||
mTestLocationTimeZoneProvider.assertIsDisabled();
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestCallback.assertSuggestionMadeAndCommit(null);
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
|
||||
mTestCallback.assertUncertainSuggestionMadeAndCommit();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configChanges_userSwitch_enabledToEnabled() {
|
||||
ControllerImpl controllerImpl =
|
||||
new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
TestEnvironment testEnvironment = new TestEnvironment(
|
||||
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// Initialize and check initial state.
|
||||
controllerImpl.initialize(testEnvironment, mTestCallback);
|
||||
|
||||
// There should be a runnable scheduled to suggest uncertainty if no event is received.
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
Duration expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Have the provider suggest a time zone.
|
||||
mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
// Simulate the primary provider suggesting a time zone.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
|
||||
|
||||
// Receiving a "success" provider event should cause a suggestion to be made synchronously,
|
||||
// and also clear the scheduled uncertainty suggestion.
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
|
||||
PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestCallback.assertSuggestionMadeAndCommit(
|
||||
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate the user change (but geo detection still enabled).
|
||||
testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
// We expect the provider to end up in PROVIDER_STATE_ENABLED, but it should have been
|
||||
// disabled when the user changed.
|
||||
// The controller should schedule a runnable to make a suggestion if the provider doesn't
|
||||
// send a success event.
|
||||
int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
|
||||
mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
|
||||
mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
|
||||
expectedTimeout = expectedProviderInitializationTimeout();
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
|
||||
int[] expectedStateTransitions =
|
||||
{ PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED_INITIALIZING };
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
|
||||
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
|
||||
PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primaryPermFailure_disableAndEnable() {
|
||||
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
|
||||
mTestPrimaryLocationTimeZoneProvider);
|
||||
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);
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Simulate no event being received, and time passing.
|
||||
mTestThreadingDomain.executeNext();
|
||||
// Simulate a failure location event being received from the primary provider. This should
|
||||
// cause an uncertain suggestion to be made.
|
||||
mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
|
||||
USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
|
||||
|
||||
mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
|
||||
mTestCallback.assertUncertainSuggestionMadeAndCommit();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Now signal a config change so that geo detection is disabled.
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
|
||||
mTestCallback.assertNoSuggestionMade();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
|
||||
// Now signal a config change so that geo detection is enabled.
|
||||
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
|
||||
|
||||
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
|
||||
mTestCallback.assertUncertainSuggestionMadeAndCommit();
|
||||
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
|
||||
}
|
||||
|
||||
private static void assertUncertaintyTimeoutSet(
|
||||
LocationTimeZoneProviderController.Environment environment,
|
||||
LocationTimeZoneProviderController controller) {
|
||||
assertTrue(controller.isUncertaintyTimeoutSet());
|
||||
assertEquals(environment.getUncertaintyDelay().toMillis(),
|
||||
controller.getUncertaintyTimeoutDelayMillis());
|
||||
}
|
||||
|
||||
private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId,
|
||||
@@ -362,16 +540,15 @@ public class ControllerImplTest {
|
||||
}
|
||||
|
||||
|
||||
private Duration expectedProviderInitializationTimeout() {
|
||||
return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT
|
||||
.plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
|
||||
}
|
||||
|
||||
private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
|
||||
|
||||
// These timeouts are set deliberately so that:
|
||||
// (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 UNCERTAINTY_DELAY = Duration.ofMinutes(3);
|
||||
private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15);
|
||||
|
||||
private final LocationTimeZoneProviderController mController;
|
||||
private ConfigurationInternal mConfigurationInternal;
|
||||
@@ -491,38 +668,57 @@ public class ControllerImplTest {
|
||||
assertTrue(mInitialized);
|
||||
}
|
||||
|
||||
void assertIsDisabled() {
|
||||
// Disabled providers don't hold config.
|
||||
assertConfig(null);
|
||||
assertIsEnabledAndCommit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the provider's config matches the expected, and the current state is set
|
||||
* accordingly. Commits the latest changes to the state.
|
||||
*/
|
||||
void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
|
||||
assertConfig(expectedConfig);
|
||||
|
||||
boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior();
|
||||
assertIsEnabledAndCommit(expectIsEnabled);
|
||||
}
|
||||
|
||||
private void assertIsEnabledAndCommit(boolean enabled) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
if (enabled) {
|
||||
assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
|
||||
} else {
|
||||
assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
|
||||
}
|
||||
public void assertIsPermFailedAndCommit() {
|
||||
// A failed provider doesn't hold config.
|
||||
assertStateEnumAndConfig(PROVIDER_STATE_PERM_FAILED, null /* config */);
|
||||
mTestProviderState.commitLatest();
|
||||
}
|
||||
|
||||
void assertConfig(@NonNull ConfigurationInternal expectedConfig) {
|
||||
void assertIsDisabledAndCommit() {
|
||||
// A disabled provider doesn't hold config.
|
||||
assertStateEnumAndConfig(PROVIDER_STATE_DISABLED, null /* config */);
|
||||
mTestProviderState.commitLatest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the provider's state enum and config matches the expected.
|
||||
* Commits the latest changes to the state.
|
||||
*/
|
||||
void assertStateEnumAndConfigAndCommit(
|
||||
@ProviderStateEnum int expectedStateEnum,
|
||||
@Nullable ConfigurationInternal expectedConfig) {
|
||||
assertStateEnumAndConfig(expectedStateEnum, expectedConfig);
|
||||
mTestProviderState.commitLatest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the provider's state enum and config matches the expected.
|
||||
* Does not commit any state changes.
|
||||
*/
|
||||
void assertStateEnumAndConfig(
|
||||
@ProviderStateEnum int expectedStateEnum,
|
||||
@Nullable ConfigurationInternal expectedConfig) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
assertEquals(expectedStateEnum, currentState.stateEnum);
|
||||
|
||||
// If and only if the controller is initializing, the initialization timeout must be
|
||||
// set.
|
||||
assertEquals(expectedStateEnum == PROVIDER_STATE_ENABLED_INITIALIZING,
|
||||
isInitializationTimeoutSet());
|
||||
|
||||
assertConfig(expectedConfig);
|
||||
}
|
||||
|
||||
private void assertConfig(@Nullable ConfigurationInternal expectedConfig) {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
assertEquals(expectedConfig, currentState.currentUserConfiguration);
|
||||
}
|
||||
|
||||
void assertInitializationTimeoutSet(Duration expectedTimeout) {
|
||||
assertTrue(isInitializationTimeoutSet());
|
||||
assertEquals(expectedTimeout, getInitializationTimeoutDelay());
|
||||
}
|
||||
|
||||
void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) {
|
||||
handleLocationTimeZoneEvent(event);
|
||||
}
|
||||
|
||||
@@ -115,28 +115,7 @@ public class HandlerThreadingDomainTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleRunnableHandler_runSynchronously() throws Exception {
|
||||
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
|
||||
SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
|
||||
|
||||
AtomicBoolean testPassed = new AtomicBoolean(false);
|
||||
// Calls to SingleRunnableQueue must be made on the handler thread it is associated with,
|
||||
// so this uses runWithScissors() to block until the lambda has completed.
|
||||
mTestHandler.runWithScissors(() -> {
|
||||
Thread testThread = Thread.currentThread();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
singleRunnableQueue.runSynchronously(() -> {
|
||||
assertSame(Thread.currentThread(), testThread);
|
||||
latch.countDown();
|
||||
});
|
||||
assertTrue(awaitWithRuntimeException(latch, 60, TimeUnit.SECONDS));
|
||||
testPassed.set(true);
|
||||
}, TimeUnit.SECONDS.toMillis(60));
|
||||
assertTrue(testPassed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleRunnableHandler_runDelayed() throws Exception {
|
||||
public void singleRunnableQueue_runDelayed() throws Exception {
|
||||
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
|
||||
SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.android.server.location.timezone;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
|
||||
@@ -76,15 +76,16 @@ public class NullLocationTimeZoneProviderTest {
|
||||
|
||||
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
|
||||
provider.enable(config, arbitraryInitializationTimeout);
|
||||
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
|
||||
provider.enable(config, arbitraryInitializationTimeout, arbitraryInitializationTimeoutFuzz);
|
||||
|
||||
// The StubbedProvider should enters enabled state, but immediately schedule a runnable to
|
||||
// switch to perm failure.
|
||||
// The NullProvider should enter the enabled state, but have schedule an immediate runnable
|
||||
// to switch to perm failure.
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
|
||||
assertEquals(PROVIDER_STATE_ENABLED_INITIALIZING, currentState.stateEnum);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertSingleImmediateQueueItem();
|
||||
mTestThreadingDomain.assertNextQueueItemIsImmediate();
|
||||
// Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
|
||||
// controller.
|
||||
mTestController.assertProviderChangeNotTriggered();
|
||||
@@ -116,6 +117,18 @@ public class NullLocationTimeZoneProviderTest {
|
||||
// Not needed for provider testing.
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUncertaintyTimeoutSet() {
|
||||
// Not needed for provider testing.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
long getUncertaintyTimeoutDelayMillis() {
|
||||
// Not needed for provider testing.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void onProviderStateChange(ProviderState providerState) {
|
||||
this.mProviderState.set(providerState);
|
||||
}
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
package com.android.server.location.timezone;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,10 @@ import java.util.Objects;
|
||||
class TestThreadingDomain extends ThreadingDomain {
|
||||
|
||||
static class QueuedRunnable {
|
||||
|
||||
static final Comparator<? super QueuedRunnable> COMPARATOR =
|
||||
(o1, o2) -> (int) (o1.executionTimeMillis - o2.executionTimeMillis);
|
||||
|
||||
@NonNull public final Runnable runnable;
|
||||
@Nullable public final Object token;
|
||||
public final long executionTimeMillis;
|
||||
@@ -55,7 +61,7 @@ class TestThreadingDomain extends ThreadingDomain {
|
||||
}
|
||||
|
||||
private long mCurrentTimeMillis;
|
||||
private LinkedList<QueuedRunnable> mQueue = new LinkedList<>();
|
||||
private ArrayList<QueuedRunnable> mQueue = new ArrayList<>();
|
||||
|
||||
TestThreadingDomain() {
|
||||
// Pick an arbitrary time.
|
||||
@@ -69,22 +75,23 @@ class TestThreadingDomain extends ThreadingDomain {
|
||||
|
||||
@Override
|
||||
void post(Runnable r) {
|
||||
mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis));
|
||||
postDelayed(r, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
void postDelayed(Runnable r, long delayMillis) {
|
||||
mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis));
|
||||
postDelayed(r, null, delayMillis);
|
||||
}
|
||||
|
||||
@Override
|
||||
void postDelayed(Runnable r, Object token, long delayMillis) {
|
||||
mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis));
|
||||
mQueue.sort(QueuedRunnable.COMPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
void removeQueuedRunnables(Object token) {
|
||||
mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token));
|
||||
mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token);
|
||||
}
|
||||
|
||||
void assertSingleDelayedQueueItem(Duration expectedDelay) {
|
||||
@@ -114,14 +121,14 @@ class TestThreadingDomain extends ThreadingDomain {
|
||||
}
|
||||
|
||||
long getNextQueueItemDelayMillis() {
|
||||
assertQueueLength(1);
|
||||
return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis;
|
||||
assertFalse(mQueue.isEmpty());
|
||||
return mQueue.get(0).executionTimeMillis - mCurrentTimeMillis;
|
||||
}
|
||||
|
||||
void executeNext() {
|
||||
assertQueueLength(1);
|
||||
assertFalse(mQueue.isEmpty());
|
||||
QueuedRunnable queued = mQueue.remove(0);
|
||||
|
||||
QueuedRunnable queued = mQueue.removeFirst();
|
||||
mCurrentTimeMillis = queued.executionTimeMillis;
|
||||
queued.runnable.run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user