diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index e28d73e8e0b98..5dc4318e58036 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -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;
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index bedaedacf0e3d..179ce750d1068 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -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.
+ *
+ *
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.
- *
- *
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.
- *
- *
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 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.
+ *
+ * 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.
+ *
+ *
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);
}
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 238f999ff8a67..c9a211d96fc05 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -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);
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index abfa580b5c980..4b0b5a2cbbe43 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -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 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());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
index 88f0f00015c6f..ace066e12d3f3 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
@@ -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.
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
index 9b9c823589749..d55d3edf5e03a 100644
--- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
+++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
@@ -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;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index 631b8d3cb1ea0..9ee9259a23c24 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -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);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
index cbaf0f391375a..4d6775dcaecf5 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
@@ -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();
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
index 5542db0b5ae3d..5403a654f7e43 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -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);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
index def919e79731a..7359abd11c3a3 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -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 mQueue = new LinkedList<>();
+ private ArrayList 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();
}