Add a state to LocationTimeZoneProviderController

Add an explicit state field to LocationTimeZoneProviderController to
help with telemetry / metrics. Previously, this state information was
implicit.

This commit adds the code necessary to support metrics, but does not
actually integrate with the metrics system. A TODO has been left in the
code to complete the plumbing.

The state information records the state as would be reflected by the
controller's actions. For example, the controller may have received an
"uncertain" suggestion from a provider, but still be in the uncertainty
timeout period, and therefore the state could still be considered
"INITIALIZING" or "CERTAIN" until the controller has actually
communicated it is uncertain.

The intent of adding this explicit state information is to help with
metrics and telemetry. It also helps with testing and debugging to
ensure the controller is transitioning through all the correct states on
the way to a final correct state.

The main indicator desired from this state is to enable metrics that
record how much time the controller spends active / inactive, as well as
certain and uncertain. Previous to this change, similar information was
captured at the location time zone provider (LTZP) level, but
LTZP-level information would be difficult to use when multiple LTZPs are
running concurrently. For example, in order to tell what percentage of
time the geolocation detection system as a whole is certain / uncertain,
it is necessary to understand how much time the geolocation detection
system as a whole was actually running.

Bug: 200279201
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/
Test: atest cts/hostsidetests/time/host/src/android/time/cts/host/
Change-Id: I8154caee41e96b03e6a43b51ee8d1556ba77bb4d
This commit is contained in:
Neil Fuller
2021-11-24 10:22:39 +00:00
parent d3dadfbd01
commit a6d063e55a
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.