Merge "Add a state to LocationTimeZoneProviderController"

This commit is contained in:
Neil Fuller
2021-11-30 12:08:51 +00:00
committed by Android (Google) Code Review
10 changed files with 514 additions and 92 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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() {

View File

@@ -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

View File

@@ -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<ProviderState> mPrimaryProviderStates;
@NonNull private final List<ProviderState> 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<ProviderState> 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<ProviderState> mPrimaryProviderStates;
private List<ProviderState> 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<ProviderState> primaryProviderStates) {
mPrimaryProviderStates = new ArrayList<>(primaryProviderStates);

View File

@@ -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<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName,
long fieldId) {

View File

@@ -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 {
* <p>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);
}
}

View File

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

View File

@@ -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<T> {
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.