From a6d063e55ada60983d83640a3a86f7781b4de080 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Wed, 24 Nov 2021 10:22:39 +0000 Subject: [PATCH] Add a state to LocationTimeZoneProviderController Add an explicit state field to LocationTimeZoneProviderController to help with telemetry / metrics. Previously, this state information was implicit. This commit adds the code necessary to support metrics, but does not actually integrate with the metrics system. A TODO has been left in the code to complete the plumbing. The state information records the state as would be reflected by the controller's actions. For example, the controller may have received an "uncertain" suggestion from a provider, but still be in the uncertainty timeout period, and therefore the state could still be considered "INITIALIZING" or "CERTAIN" until the controller has actually communicated it is uncertain. The intent of adding this explicit state information is to help with metrics and telemetry. It also helps with testing and debugging to ensure the controller is transitioning through all the correct states on the way to a final correct state. The main indicator desired from this state is to enable metrics that record how much time the controller spends active / inactive, as well as certain and uncertain. Previous to this change, similar information was captured at the location time zone provider (LTZP) level, but LTZP-level information would be difficult to use when multiple LTZPs are running concurrently. For example, in order to tell what percentage of time the geolocation detection system as a whole is certain / uncertain, it is necessary to understand how much time the geolocation detection system as a whole was actually running. Bug: 200279201 Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/ Test: atest cts/hostsidetests/time/host/src/android/time/cts/host/ Change-Id: I8154caee41e96b03e6a43b51ee8d1556ba77bb4d --- .../app/location_time_zone_manager.proto | 14 + .../ServiceConfigAccessor.java | 7 +- .../ServiceConfigAccessorImpl.java | 12 +- .../LocationTimeZoneManagerService.java | 23 +- .../LocationTimeZoneManagerServiceState.java | 32 +- .../LocationTimeZoneManagerShellCommand.java | 41 +++ .../LocationTimeZoneProviderController.java | 142 ++++++-- .../FakeServiceConfigAccessor.java | 4 +- .../server/timezonedetector/TestState.java | 6 + ...ocationTimeZoneProviderControllerTest.java | 325 +++++++++++++++--- 10 files changed, 514 insertions(+), 92 deletions(-) diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto index 891e9fca36aaa..5fdcfdf35a37a 100644 --- a/core/proto/android/app/location_time_zone_manager.proto +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -23,6 +23,19 @@ import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "LocationTimeZoneManagerProto"; +// A state enum that matches states for LocationTimeZoneProviderController. See that class for +// details. +enum ControllerStateEnum { + CONTROLLER_STATE_UNKNOWN = 0; + CONTROLLER_STATE_PROVIDERS_INITIALIZING = 1; + CONTROLLER_STATE_STOPPED = 2; + CONTROLLER_STATE_INITIALIZING = 3; + CONTROLLER_STATE_UNCERTAIN = 4; + CONTROLLER_STATE_CERTAIN = 5; + CONTROLLER_STATE_FAILED = 6; + CONTROLLER_STATE_DESTROYED = 7; +} + // Represents the state of the LocationTimeZoneManagerService for use in tests. message LocationTimeZoneManagerServiceStateProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -30,6 +43,7 @@ message LocationTimeZoneManagerServiceStateProto { optional GeolocationTimeZoneSuggestionProto last_suggestion = 1; repeated TimeZoneProviderStateProto primary_provider_states = 2; repeated TimeZoneProviderStateProto secondary_provider_states = 3; + repeated ControllerStateEnum controller_states = 4; } // The state tracked for a LocationTimeZoneProvider. diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index 984b9baf0fc7a..692b0cc795f56 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -172,12 +172,13 @@ public interface ServiceConfigAccessor { * Enables/disables the state recording mode for tests. The value is reset with {@link * #resetVolatileTestConfig()}. */ - void setRecordProviderStateChanges(boolean enabled); + void setRecordStateChangesForTests(boolean enabled); /** - * Returns {@code true} if providers are expected to record their state changes for tests. + * Returns {@code true} if the controller / providers are expected to record their state changes + * for tests. */ - boolean getRecordProviderStateChanges(); + boolean getRecordStateChangesForTests(); /** * Returns the mode for the primary location time zone provider. diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java index b9885d237bbd7..02ea43341beff 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java @@ -150,7 +150,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { * See also {@link #resetVolatileTestConfig()}. */ @GuardedBy("this") - private boolean mRecordProviderStateChanges; + private boolean mRecordStateChangesForTests; private ServiceConfigAccessorImpl(@NonNull Context context) { mContext = Objects.requireNonNull(context); @@ -453,13 +453,13 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { } @Override - public synchronized void setRecordProviderStateChanges(boolean enabled) { - mRecordProviderStateChanges = enabled; + public synchronized void setRecordStateChangesForTests(boolean enabled) { + mRecordStateChangesForTests = enabled; } @Override - public synchronized boolean getRecordProviderStateChanges() { - return mRecordProviderStateChanges; + public synchronized boolean getRecordStateChangesForTests() { + return mRecordStateChangesForTests; } @Override @@ -548,7 +548,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { mTestPrimaryLocationTimeZoneProviderMode = null; mTestSecondaryLocationTimeZoneProviderPackageName = null; mTestSecondaryLocationTimeZoneProviderMode = null; - mRecordProviderStateChanges = false; + mRecordStateChangesForTests = false; } private boolean isTelephonyFallbackSupported() { diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index af8cf6e05a840..b23f11aa85976 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -247,8 +247,7 @@ public class LocationTimeZoneManagerService extends Binder { * completion, it cannot be called from the {@code mThreadingDomain} thread. */ void startWithTestProviders(@Nullable String testPrimaryProviderPackageName, - @Nullable String testSecondaryProviderPackageName, - boolean recordProviderStateChanges) { + @Nullable String testSecondaryProviderPackageName, boolean recordStateChanges) { enforceManageTimeZoneDetectorPermission(); if (testPrimaryProviderPackageName == null && testSecondaryProviderPackageName == null) { @@ -263,7 +262,7 @@ public class LocationTimeZoneManagerService extends Binder { testPrimaryProviderPackageName); mServiceConfigAccessor.setTestSecondaryLocationTimeZoneProviderPackageName( testSecondaryProviderPackageName); - mServiceConfigAccessor.setRecordProviderStateChanges(recordProviderStateChanges); + mServiceConfigAccessor.setRecordStateChangesForTests(recordStateChanges); startOnDomainThread(); } }, BLOCKING_OP_WAIT_DURATION_MILLIS); @@ -281,10 +280,20 @@ public class LocationTimeZoneManagerService extends Binder { if (mLocationTimeZoneProviderController == null) { LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider(); LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider(); + LocationTimeZoneProviderController.MetricsLogger metricsLogger = + new LocationTimeZoneProviderController.MetricsLogger() { + @Override + public void onStateChange( + @LocationTimeZoneProviderController.State String state) { + // TODO b/200279201 - wire this up to metrics code + // No-op. + } + }; + boolean recordStateChanges = mServiceConfigAccessor.getRecordStateChangesForTests(); LocationTimeZoneProviderController controller = - new LocationTimeZoneProviderController( - mThreadingDomain, primary, secondary); + new LocationTimeZoneProviderController(mThreadingDomain, metricsLogger, + primary, secondary, recordStateChanges); LocationTimeZoneProviderControllerEnvironmentImpl environment = new LocationTimeZoneProviderControllerEnvironmentImpl( mThreadingDomain, mServiceConfigAccessor, controller); @@ -342,7 +351,7 @@ public class LocationTimeZoneManagerService extends Binder { mThreadingDomain.postAndWait(() -> { synchronized (mSharedLock) { if (mLocationTimeZoneProviderController != null) { - mLocationTimeZoneProviderController.clearRecordedProviderStates(); + mLocationTimeZoneProviderController.clearRecordedStates(); } } }, BLOCKING_OP_WAIT_DURATION_MILLIS); @@ -450,7 +459,7 @@ public class LocationTimeZoneManagerService extends Binder { ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex); return new BinderLocationTimeZoneProvider( providerMetricsLogger, mThreadingDomain, mName, proxy, - mServiceConfigAccessor.getRecordProviderStateChanges()); + mServiceConfigAccessor.getRecordStateChangesForTests()); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java index 113926a265f5d..1f752f45fd51a 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import java.util.ArrayList; import java.util.Collections; @@ -30,21 +31,34 @@ import java.util.Objects; /** A snapshot of the location time zone manager service's state for tests. */ final class LocationTimeZoneManagerServiceState { + private final @State String mControllerState; @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion; + @NonNull private final List<@State String> mControllerStates; @NonNull private final List mPrimaryProviderStates; @NonNull private final List mSecondaryProviderStates; LocationTimeZoneManagerServiceState(@NonNull Builder builder) { + mControllerState = builder.mControllerState; mLastSuggestion = builder.mLastSuggestion; + mControllerStates = Objects.requireNonNull(builder.mControllerStates); mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates); mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates); } + public @State String getControllerState() { + return mControllerState; + } + @Nullable public GeolocationTimeZoneSuggestion getLastSuggestion() { return mLastSuggestion; } + @NonNull + public List<@State String> getControllerStates() { + return mControllerStates; + } + @NonNull public List getPrimaryProviderStates() { return Collections.unmodifiableList(mPrimaryProviderStates); @@ -58,7 +72,9 @@ final class LocationTimeZoneManagerServiceState { @Override public String toString() { return "LocationTimeZoneManagerServiceState{" - + "mLastSuggestion=" + mLastSuggestion + + "mControllerState=" + mControllerState + + ", mLastSuggestion=" + mLastSuggestion + + ", mControllerStates=" + mControllerStates + ", mPrimaryProviderStates=" + mPrimaryProviderStates + ", mSecondaryProviderStates=" + mSecondaryProviderStates + '}'; @@ -66,16 +82,30 @@ final class LocationTimeZoneManagerServiceState { static final class Builder { + private @State String mControllerState; private GeolocationTimeZoneSuggestion mLastSuggestion; + private List<@State String> mControllerStates; private List mPrimaryProviderStates; private List mSecondaryProviderStates; + @NonNull + public Builder setControllerState(@State String stateEnum) { + mControllerState = stateEnum; + return this; + } + @NonNull Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) { mLastSuggestion = Objects.requireNonNull(lastSuggestion); return this; } + @NonNull + public Builder setStateChanges(@NonNull List<@State String> states) { + mControllerStates = new ArrayList<>(states); + return this; + } + @NonNull Builder setPrimaryProviderStateChanges(@NonNull List primaryProviderStates) { mPrimaryProviderStates = new ArrayList<>(primaryProviderStates); diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 6c9e174b0fc59..60bbea77b6365 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -40,6 +40,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,6 +63,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -245,6 +254,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { outputStream.end(lastSuggestionToken); } + writeControllerStates(outputStream, state.getControllerStates()); writeProviderStates(outputStream, state.getPrimaryProviderStates(), "primary_provider_states", LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES); @@ -256,6 +266,37 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { return 0; } + private static void writeControllerStates(DualDumpOutputStream outputStream, + List<@State String> states) { + for (@State String state : states) { + outputStream.write("controller_states", + LocationTimeZoneManagerServiceStateProto.CONTROLLER_STATES, + convertControllerStateToProtoEnum(state)); + } + } + + private static int convertControllerStateToProtoEnum(@State String state) { + switch (state) { + case STATE_PROVIDERS_INITIALIZING: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_PROVIDERS_INITIALIZING; + case STATE_STOPPED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_STOPPED; + case STATE_INITIALIZING: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_INITIALIZING; + case STATE_UNCERTAIN: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_UNCERTAIN; + case STATE_CERTAIN: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_CERTAIN; + case STATE_FAILED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_FAILED; + case STATE_DESTROYED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_DESTROYED; + case STATE_UNKNOWN: + default: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_UNKNOWN; + } + } + private static void writeProviderStates(DualDumpOutputStream outputStream, List providerStates, String fieldName, long fieldId) { diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index dbd3877debfeb..5d7730ab9aac9 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -34,6 +34,7 @@ import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.service.timezone.TimeZoneProviderEvent; import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; @@ -43,9 +44,15 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.ReferenceWithHistory; import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.time.Duration; +import java.util.ArrayList; import java.util.Objects; /** @@ -93,6 +100,36 @@ import java.util.Objects; */ class LocationTimeZoneProviderController implements Dumpable { + // String is used for easier logging / interpretation in bug reports Vs int. + @StringDef(prefix = "STATE_", + value = { STATE_UNKNOWN, STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, + STATE_INITIALIZING, STATE_UNCERTAIN, STATE_CERTAIN, STATE_FAILED, + STATE_DESTROYED }) + @Retention(RetentionPolicy.SOURCE) + @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) + @interface State {} + + /** The state used for an uninitialized controller. */ + static final @State String STATE_UNKNOWN = "UNKNOWN"; + + /** + * A state used while the location time zone providers are initializing. Enables detection + * / avoidance of unwanted fail-over behavior before both providers are initialized. + */ + static final @State String STATE_PROVIDERS_INITIALIZING = "PROVIDERS_INITIALIZING"; + /** An inactive state: Detection is disabled. */ + static final @State String STATE_STOPPED = "STOPPED"; + /** An active state: No suggestion has yet been made. */ + static final @State String STATE_INITIALIZING = "INITIALIZING"; + /** An active state: The last suggestion was "uncertain". */ + static final @State String STATE_UNCERTAIN = "UNCERTAIN"; + /** An active state: The last suggestion was "certain". */ + static final @State String STATE_CERTAIN = "CERTAIN"; + /** An inactive state: The location time zone providers have failed. */ + static final @State String STATE_FAILED = "FAILED"; + /** An inactive state: The controller is destroyed. */ + static final @State String STATE_DESTROYED = "DESTROYED"; + @NonNull private final ThreadingDomain mThreadingDomain; @NonNull private final Object mSharedLock; /** @@ -102,6 +139,7 @@ class LocationTimeZoneProviderController implements Dumpable { */ @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue; + @NonNull private final MetricsLogger mMetricsLogger; @NonNull private final LocationTimeZoneProvider mPrimaryProvider; @NonNull private final LocationTimeZoneProvider mSecondaryProvider; @@ -117,10 +155,22 @@ class LocationTimeZoneProviderController implements Dumpable { // Non-null after initialize() private Callback mCallback; - /** Indicates both providers have completed initialization. */ - @GuardedBy("mSharedLock") - private boolean mProvidersInitialized; + /** Usually {@code false} but can be set to {@code true} to record state changes for testing. */ + private final boolean mRecordStateChanges; + @GuardedBy("mSharedLock") + @NonNull + private final ArrayList<@State String> mRecordedStates = new ArrayList<>(0); + + /** + * The current state. This is primarily for metrics / reporting of how long the controller + * spends active / inactive during a period. There is overlap with the provider states, but + * providers operate independently of each other, so this can help to understand how long the + * geo detection system overall was certain or uncertain when multiple providers might have been + * enabled concurrently. + */ + @GuardedBy("mSharedLock") + private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10); /** Contains the last suggestion actually made, if there is one. */ @GuardedBy("mSharedLock") @@ -128,13 +178,21 @@ class LocationTimeZoneProviderController implements Dumpable { private GeolocationTimeZoneSuggestion mLastSuggestion; LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain, + @NonNull MetricsLogger metricsLogger, @NonNull LocationTimeZoneProvider primaryProvider, - @NonNull LocationTimeZoneProvider secondaryProvider) { + @NonNull LocationTimeZoneProvider secondaryProvider, + boolean recordStateChanges) { mThreadingDomain = Objects.requireNonNull(threadingDomain); mSharedLock = threadingDomain.getLockObject(); mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue(); + mMetricsLogger = Objects.requireNonNull(metricsLogger); mPrimaryProvider = Objects.requireNonNull(primaryProvider); mSecondaryProvider = Objects.requireNonNull(secondaryProvider); + mRecordStateChanges = recordStateChanges; + + synchronized (mSharedLock) { + mState.set(STATE_UNKNOWN); + } } /** @@ -152,9 +210,10 @@ class LocationTimeZoneProviderController implements Dumpable { LocationTimeZoneProvider.ProviderListener providerListener = LocationTimeZoneProviderController.this::onProviderStateChange; + setState(STATE_PROVIDERS_INITIALIZING); mPrimaryProvider.initialize(providerListener); mSecondaryProvider.initialize(providerListener); - mProvidersInitialized = true; + setState(STATE_STOPPED); alterProvidersStartedStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); @@ -209,8 +268,26 @@ class LocationTimeZoneProviderController implements Dumpable { synchronized (mSharedLock) { stopProviders(); + + // Enter destroyed state. mPrimaryProvider.destroy(); mSecondaryProvider.destroy(); + setState(STATE_DESTROYED); + } + } + + /** + * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated + * with state changes. + */ + @GuardedBy("mSharedLock") + private void setState(@State String state) { + if (!Objects.equals(mState.get(), state)) { + mState.set(state); + if (mRecordStateChanges) { + mRecordedStates.add(state); + } + mMetricsLogger.onStateChange(state); } } @@ -226,11 +303,12 @@ class LocationTimeZoneProviderController implements Dumpable { // 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 providers are // re-started). - if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) { + if (Objects.equals(mState.get(), STATE_CERTAIN)) { GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( mEnvironment.elapsedRealtimeMillis(), "Providers are stopping"); - makeSuggestion(suggestion); + makeSuggestion(suggestion, STATE_UNCERTAIN); } + setState(STATE_STOPPED); } @GuardedBy("mSharedLock") @@ -300,6 +378,8 @@ class LocationTimeZoneProviderController implements Dumpable { // timeout started when the primary entered {started uncertain} should be cancelled. if (newGeoDetectionEnabled) { + setState(STATE_INITIALIZING); + // Try to start the primary provider. tryStartProvider(mPrimaryProvider, newConfiguration); @@ -314,13 +394,13 @@ class LocationTimeZoneProviderController implements Dumpable { ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); if (!newSecondaryState.isStarted()) { // If both providers are {perm failed} then the controller immediately - // becomes uncertain. + // reports uncertain. GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( mEnvironment.elapsedRealtimeMillis(), "Providers are failed:" + " primary=" + mPrimaryProvider.getCurrentState() + " secondary=" + mPrimaryProvider.getCurrentState()); - makeSuggestion(suggestion); + makeSuggestion(suggestion, STATE_FAILED); } } } else { @@ -368,7 +448,7 @@ class LocationTimeZoneProviderController implements Dumpable { // Ignore provider state changes during initialization. e.g. if the primary provider // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not // be ready to take over yet. - if (!mProvidersInitialized) { + if (Objects.equals(mState.get(), STATE_PROVIDERS_INITIALIZING)) { warnLog("onProviderStateChange: Ignoring provider state change because both" + " providers have not yet completed initialization." + " providerState=" + providerState); @@ -453,13 +533,13 @@ class LocationTimeZoneProviderController implements Dumpable { cancelUncertaintyTimeout(); // If both providers are now terminated, then a suggestion must be sent informing the - // time zone detector that there are no further updates coming in future. + // time zone detector that there are no further updates coming in the future. GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( mEnvironment.elapsedRealtimeMillis(), "Both providers are terminated:" + " primary=" + primaryCurrentState.provider + ", secondary=" + secondaryCurrentState.provider); - makeSuggestion(suggestion); + makeSuggestion(suggestion, STATE_FAILED); } } @@ -548,7 +628,7 @@ class LocationTimeZoneProviderController implements Dumpable { + ", providerEvent=" + providerEvent + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis(); geoSuggestion.addDebugInfo(debugInfo); - makeSuggestion(geoSuggestion); + makeSuggestion(geoSuggestion, STATE_CERTAIN); } @Override @@ -563,8 +643,14 @@ class LocationTimeZoneProviderController implements Dumpable { ipw.println("providerInitializationTimeoutFuzz=" + mEnvironment.getProviderInitializationTimeoutFuzz()); ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay()); + ipw.println("mState=" + mState.get()); ipw.println("mLastSuggestion=" + mLastSuggestion); + ipw.println("State history:"); + ipw.increaseIndent(); // level 2 + mState.dump(ipw); + ipw.decreaseIndent(); // level 2 + ipw.println("Primary Provider:"); ipw.increaseIndent(); // level 2 mPrimaryProvider.dump(ipw, args); @@ -579,12 +665,17 @@ class LocationTimeZoneProviderController implements Dumpable { } } - /** Sends an immediate suggestion, updating mLastSuggestion. */ + /** + * Sends an immediate suggestion and enters a new state if needed. This method updates + * mLastSuggestion and changes mStateEnum / reports the new state for metrics. + */ @GuardedBy("mSharedLock") - private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) { + private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion, + @State String newState) { debugLog("makeSuggestion: suggestion=" + suggestion); mCallback.suggest(suggestion); mLastSuggestion = suggestion; + setState(newState); } /** Clears the uncertainty timeout. */ @@ -604,7 +695,7 @@ class LocationTimeZoneProviderController implements Dumpable { *

This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be * triggered 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 + * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link * Environment#getUncertaintyDelay()}. Preemption causes the scheduled * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout @@ -666,7 +757,7 @@ class LocationTimeZoneProviderController implements Dumpable { + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis) + ", uncertaintyDelay=" + uncertaintyDelay ); - makeSuggestion(suggestion); + makeSuggestion(suggestion, STATE_UNCERTAIN); } } @@ -682,12 +773,13 @@ class LocationTimeZoneProviderController implements Dumpable { } /** - * Clears recorded provider state changes (for use during tests). + * Clears recorded controller and provider state changes (for use during tests). */ - void clearRecordedProviderStates() { + void clearRecordedStates() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { + mRecordedStates.clear(); mPrimaryProvider.clearRecordedStates(); mSecondaryProvider.clearRecordedStates(); } @@ -706,7 +798,9 @@ class LocationTimeZoneProviderController implements Dumpable { if (mLastSuggestion != null) { builder.setLastSuggestion(mLastSuggestion); } - builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates()) + builder.setControllerState(mState.get()) + .setStateChanges(mRecordedStates) + .setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates()) .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates()); return builder.build(); } @@ -782,4 +876,12 @@ class LocationTimeZoneProviderController implements Dumpable { */ abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion); } + + /** + * Used by {@link LocationTimeZoneProviderController} to record events for metrics / telemetry. + */ + interface MetricsLogger { + /** Called when the controller's state changes. */ + void onStateChange(@State String stateEnum); + } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java index 9d1c74b145257..a97ad8c57a0db 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java @@ -151,12 +151,12 @@ class FakeServiceConfigAccessor implements ServiceConfigAccessor { } @Override - public void setRecordProviderStateChanges(boolean enabled) { + public void setRecordStateChangesForTests(boolean enabled) { failUnimplemented(); } @Override - public boolean getRecordProviderStateChanges() { + public boolean getRecordStateChangesForTests() { return failUnimplemented(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java index 97b8360172f29..97095c4f675f8 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Arrays; /** * A test support class used for tracking a piece of state in test objects like fakes and mocks. @@ -79,6 +80,11 @@ public class TestState { assertEquals(expectedCount, getChangeCount()); } + /** Asserts the value has been {@link #set} to the expected values in the order given. */ + public void assertChanges(T... expected) { + assertEquals(Arrays.asList(expected), mValues); + } + /** * Returns the latest value passed to {@link #set}. If {@link #set} hasn't been called then the * initial value is returned. diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java index 20c25a04715bd..d54e1f1d8ec3d 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java @@ -21,6 +21,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN; import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED; import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; import static com.android.server.timezonedetector.location.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED; @@ -45,7 +53,9 @@ import android.util.IndentingPrintWriter; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.TestState; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import org.junit.Before; import org.junit.Test; @@ -73,6 +83,7 @@ public class LocationTimeZoneProviderControllerTest { TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test"); private TestThreadingDomain mTestThreadingDomain; + private TestMetricsLogger mTestMetricsLogger; private TestCallback mTestCallback; private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider; @@ -82,22 +93,31 @@ public class LocationTimeZoneProviderControllerTest { // For simplicity, the TestThreadingDomain uses the test's main thread. To execute posted // runnables, the test must call methods on mTestThreadingDomain otherwise those runnables // will never get a chance to execute. - LocationTimeZoneProvider.ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> { - // Stubbed. - }; mTestThreadingDomain = new TestThreadingDomain(); + mTestMetricsLogger = new TestMetricsLogger(); + mTestCallback = new TestCallback(mTestThreadingDomain); + + ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> {}; mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( stubbedProviderMetricsLogger, mTestThreadingDomain, "primary"); mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary"); } + @Test + public void controllerStartsInUnknownState() { + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); + assertControllerState(controller, STATE_UNKNOWN); + } + @Test public void initializationFailure_primary() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() @@ -112,10 +132,13 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -123,8 +146,8 @@ public class LocationTimeZoneProviderControllerTest { @Test public void initializationFailure_secondary() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() @@ -139,10 +162,13 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -150,8 +176,8 @@ public class LocationTimeZoneProviderControllerTest { @Test public void initializationFailure_both() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -165,8 +191,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_FAILED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -174,8 +203,8 @@ public class LocationTimeZoneProviderControllerTest { @Test public void initialState_started() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() @@ -188,10 +217,13 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -199,8 +231,8 @@ public class LocationTimeZoneProviderControllerTest { @Test public void initialState_disabled() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); @@ -211,8 +243,10 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -220,8 +254,8 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_uncertaintySuggestionSentIfNoEventReceived() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -231,18 +265,22 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); // The primary should have reported uncertainty, which should trigger the controller to // start the uncertainty timeout and start the secondary. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -250,12 +288,14 @@ public class LocationTimeZoneProviderControllerTest { // secondary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); // Now both initialization timeouts should have triggered. The uncertainty timeout should // still not be triggered. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -263,10 +303,12 @@ public class LocationTimeZoneProviderControllerTest { // suggestion. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_UNCERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -274,17 +316,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_eventReceivedBeforeInitializationTimeout() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -293,9 +338,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -304,27 +351,32 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -333,9 +385,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -344,27 +398,32 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_eventReceivedFromSecondaryAfterInitializationTimeout() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -373,10 +432,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -385,17 +446,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_repeatedPrimaryCertainty() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -404,9 +468,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -415,9 +481,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -425,9 +493,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -436,27 +506,32 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_repeatedSecondaryCertainty() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -465,10 +540,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -477,10 +554,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -488,10 +567,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -500,17 +581,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -519,9 +603,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -532,10 +618,12 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -545,10 +633,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -559,10 +649,12 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -570,10 +662,12 @@ public class LocationTimeZoneProviderControllerTest { // suggestion should be made. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_UNCERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -582,17 +676,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void enabled_briefUncertaintyTriggersNoSuggestion() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -601,9 +698,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -613,10 +712,12 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -626,9 +727,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -637,33 +740,39 @@ public class LocationTimeZoneProviderControllerTest { @Test public void configChanges_enableAndDisableWithNoPreviousSuggestion() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -671,25 +780,29 @@ public class LocationTimeZoneProviderControllerTest { @Test public void configChanges_enableAndDisableWithPreviousSuggestion() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -697,9 +810,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -710,8 +825,10 @@ public class LocationTimeZoneProviderControllerTest { // of the time zone. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -719,17 +836,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void configChanges_userSwitch_enabledToEnabled() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -739,9 +859,11 @@ public class LocationTimeZoneProviderControllerTest { // Receiving a "success" provider event should cause a suggestion to be made synchronously, // and also clear the scheduled uncertainty suggestion. + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -750,7 +872,7 @@ public class LocationTimeZoneProviderControllerTest { testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED); // Confirm that the previous suggestion was overridden. - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertControllerState(controller, STATE_INITIALIZING); // We expect the provider to end up in PROVIDER_STATE_STARTED_INITIALIZING, but it should // have been stopped when the user changed. @@ -760,23 +882,29 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig( PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void primaryPermFailure_secondaryEventsReceived() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -785,9 +913,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -795,9 +925,11 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -806,9 +938,11 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -817,9 +951,11 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); } @@ -827,17 +963,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void primaryPermFailure_disableAndEnable() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -846,26 +985,32 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -873,17 +1018,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void secondaryPermFailure_primaryEventsReceived() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -893,10 +1041,12 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -904,9 +1054,11 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -915,9 +1067,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -926,9 +1080,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); } @@ -936,17 +1092,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void secondaryPermFailure_disableAndEnable() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -956,10 +1115,12 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); @@ -967,17 +1128,21 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertUncertaintyTimeoutSet(testEnvironment, controller); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -985,9 +1150,11 @@ public class LocationTimeZoneProviderControllerTest { // started. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -995,17 +1162,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void bothPermFailure_disableAndEnable() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1013,9 +1183,11 @@ public class LocationTimeZoneProviderControllerTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1023,8 +1195,10 @@ public class LocationTimeZoneProviderControllerTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_FAILED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); assertFalse(controller.isUncertaintyTimeoutSet()); } @@ -1033,8 +1207,8 @@ public class LocationTimeZoneProviderControllerTest { public void stateRecording() { // The test provider enables state recording by default. LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, true /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); @@ -1043,12 +1217,15 @@ public class LocationTimeZoneProviderControllerTest { { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_INITIALIZING, state.getControllerState()); assertNull(state.getLastSuggestion()); + assertControllerRecordedStates(state, + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); assertProviderStates(state.getPrimaryProviderStates(), PROVIDER_STATE_STOPPED, PROVIDER_STATE_STARTED_INITIALIZING); assertProviderStates(state.getSecondaryProviderStates(), PROVIDER_STATE_STOPPED); } - controller.clearRecordedProviderStates(); + controller.clearRecordedStates(); // Simulate some provider behavior that will show up in the state recording. @@ -1058,13 +1235,15 @@ public class LocationTimeZoneProviderControllerTest { { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_INITIALIZING, state.getControllerState()); assertNull(state.getLastSuggestion()); + assertControllerRecordedStates(state); assertProviderStates( state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN); assertProviderStates( state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_INITIALIZING); } - controller.clearRecordedProviderStates(); + controller.clearRecordedStates(); // Simulate a certain event from the secondary. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( @@ -1072,18 +1251,22 @@ public class LocationTimeZoneProviderControllerTest { { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), state.getLastSuggestion().getZoneIds()); + assertControllerRecordedStates(state, STATE_CERTAIN); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates( state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_CERTAIN); } - controller.clearRecordedProviderStates(); + controller.clearRecordedStates(); { LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), state.getLastSuggestion().getZoneIds()); + assertControllerRecordedStates(state); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates(state.getSecondaryProviderStates()); } @@ -1101,17 +1284,20 @@ public class LocationTimeZoneProviderControllerTest { @Test public void destroy() { LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( - mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, - mTestSecondaryLocationTimeZoneProvider); + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1121,9 +1307,11 @@ public class LocationTimeZoneProviderControllerTest { // Receiving a "success" provider event should cause a suggestion to be made synchronously, // and also clear the scheduled uncertainty suggestion. + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); assertFalse(controller.isUncertaintyTimeoutSet()); @@ -1131,6 +1319,10 @@ public class LocationTimeZoneProviderControllerTest { // Trigger destroy(). controller.destroy(); + assertControllerState(controller, STATE_DESTROYED); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED); + // Confirm that the previous suggestion was overridden. mTestCallback.assertUncertainSuggestionMadeAndCommit(); @@ -1158,6 +1350,17 @@ public class LocationTimeZoneProviderControllerTest { .build()); } + private static void assertControllerState(LocationTimeZoneProviderController controller, + @State String expectedState) { + assertEquals(expectedState, controller.getStateForTests().getControllerState()); + } + + private static void assertControllerRecordedStates( + LocationTimeZoneManagerServiceState state, + @State String... expectedStates) { + assertEquals(Arrays.asList(expectedStates), state.getControllerStates()); + } + private static class TestEnvironment extends LocationTimeZoneProviderController.Environment { // These timeouts are set deliberately so that: @@ -1229,6 +1432,22 @@ public class LocationTimeZoneProviderControllerTest { } } + private static class TestMetricsLogger + implements LocationTimeZoneProviderController.MetricsLogger { + + private final TestState<@State String> mLatestStateEnum = new TestState<>(); + + @Override + public void onStateChange(@State String stateEnum) { + mLatestStateEnum.set(stateEnum); + } + + public void assertStateChangesAndCommit(@State String... expectedStateEnums) { + mLatestStateEnum.assertChanges(expectedStateEnums); + mLatestStateEnum.commitLatest(); + } + } + private static class TestCallback extends LocationTimeZoneProviderController.Callback { private TestState mLatestSuggestion = new TestState<>();