Merge "Add a state to LocationTimeZoneProviderController"
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user