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<>();