summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/time/LocationTimeZoneManager.java76
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java64
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerImpl.java32
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java109
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java97
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java188
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java44
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java14
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java50
11 files changed, 742 insertions, 82 deletions
diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java
index d909c0cb1eba..71a800f2085e 100644
--- a/core/java/android/app/time/LocationTimeZoneManager.java
+++ b/core/java/android/app/time/LocationTimeZoneManager.java
@@ -40,17 +40,58 @@ public final class LocationTimeZoneManager {
public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";
/**
- * Shell command that starts the service (after stop).
+ * A shell command that starts the service (after stop).
*/
public static final String SHELL_COMMAND_START = "start";
/**
- * Shell command that stops the service.
+ * A shell command that stops the service.
*/
public static final String SHELL_COMMAND_STOP = "stop";
/**
- * Shell command that sends test commands to a provider
+ * A shell command that can put providers into different modes. Takes effect next time the
+ * service is started.
+ */
+ public static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE =
+ "set_provider_mode_override";
+
+ /**
+ * The default provider mode.
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ public static final String PROVIDER_MODE_OVERRIDE_NONE = "none";
+
+ /**
+ * The "simulated" provider mode.
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ public static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";
+
+ /**
+ * The "disabled" provider mode (equivalent to there being no provider configured).
+ * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+ */
+ public static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";
+
+ /**
+ * A shell command that tells the service to record state information during tests. The next
+ * argument value is "true" or "false".
+ */
+ public static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+
+ /**
+ * A shell command that tells the service to dump its current state.
+ */
+ public static final String SHELL_COMMAND_DUMP_STATE = "dump_state";
+
+ /**
+ * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
+ */
+ public static final String DUMP_STATE_OPTION_PROTO = "--proto";
+
+ /**
+ * A shell command that sends test commands to a provider
*/
public static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
"send_provider_test_command";
@@ -88,35 +129,6 @@ public final class LocationTimeZoneManager {
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";
- private static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX =
- "persist.sys.geotz.";
-
- /**
- * The name of the system property that can be used to set the primary provider into test mode
- * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
- * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
- */
- public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY =
- SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + PRIMARY_PROVIDER_NAME;
-
- /**
- * The name of the system property that can be used to set the secondary provider into test mode
- * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
- * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
- */
- public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY =
- SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + SECONDARY_PROVIDER_NAME;
-
- /**
- * The value of the provider mode system property to put a provider into test mode.
- */
- public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED = "simulated";
-
- /**
- * The value of the provider mode system property to put a provider into disabled mode.
- */
- public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED = "disabled";
-
private LocationTimeZoneManager() {
// No need to instantiate.
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index e0b7ffed1b36..ee718b35d4c5 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -29,11 +29,69 @@ import android.content.Context;
@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
public interface TimeZoneDetector {
- /** @hide */
+ /**
+ * The name of the service for shell commands.
+ * @hide
+ */
+ String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";
+
+ /**
+ * A shell command that prints the current "auto time zone detection" global setting value.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
+
+ /**
+ * A shell command that sets the current "auto time zone detection" global setting value.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";
+
+ /**
+ * A shell command that prints whether the geolocation-based time zone detection feature is
+ * supported on the device.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";
+
+ /**
+ * A shell command that prints the current user's "location enabled" setting.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_LOCATION_ENABLED = "is_location_enabled";
+
+ /**
+ * A shell command that prints the current user's "location-based time zone detection enabled"
+ * setting.
+ * @hide
+ */
+ String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";
+
+ /**
+ * A shell command that sets the current user's "location-based time zone detection enabled"
+ * setting.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
+
+ /**
+ * A shell command that injects a geolocation time zone suggestion (as if from the
+ * location_time_zone_manager).
+ * @hide
+ */
String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
- /** @hide */
+
+ /**
+ * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
+ * similar).
+ * @hide
+ */
String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
- /** @hide */
+
+ /**
+ * A shell command that injects a telephony time zone suggestion (as if from the phone app).
+ * @hide
+ */
String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
/**
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index 03ce8ecd1cc0..2b3c0bff8548 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -610,6 +610,38 @@ class ControllerImpl extends LocationTimeZoneProviderController {
}
}
+ /**
+ * Sets whether the controller should record provider state changes for later dumping via
+ * {@link #getStateForTests()}.
+ */
+ void setProviderStateRecordingEnabled(boolean enabled) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ mPrimaryProvider.setStateChangeRecordingEnabled(enabled);
+ mSecondaryProvider.setStateChangeRecordingEnabled(enabled);
+ }
+ }
+
+ /**
+ * Returns a snapshot of the current controller state for tests.
+ */
+ @NonNull
+ LocationTimeZoneManagerServiceState getStateForTests() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ LocationTimeZoneManagerServiceState.Builder builder =
+ new LocationTimeZoneManagerServiceState.Builder();
+ if (mLastSuggestion != null) {
+ builder.setLastSuggestion(mLastSuggestion);
+ }
+ builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates())
+ .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates());
+ return builder.build();
+ }
+ }
+
@Nullable
private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) {
LocationTimeZoneProvider targetProvider;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 9f159fbe7b65..54535eb50130 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -17,11 +17,10 @@
package com.android.server.location.timezone;
import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,7 +32,6 @@ import android.os.Handler;
import android.os.RemoteCallback;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.SystemProperties;
import android.service.timezone.TimeZoneProviderService;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -42,6 +40,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
@@ -161,6 +160,14 @@ public class LocationTimeZoneManagerService extends Binder {
@GuardedBy("mSharedLock")
private ControllerEnvironmentImpl mEnvironment;
+ @GuardedBy("mSharedLock")
+ @NonNull
+ private String mPrimaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;
+
+ @GuardedBy("mSharedLock")
+ @NonNull
+ private String mSecondaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;
+
LocationTimeZoneManagerService(Context context) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mHandler = FgThread.getHandler();
@@ -231,9 +238,9 @@ public class LocationTimeZoneManagerService extends Binder {
private LocationTimeZoneProvider createPrimaryProvider() {
LocationTimeZoneProviderProxy proxy;
- if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) {
+ if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) {
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
- } else if (isDisabled(PRIMARY_PROVIDER_NAME)) {
+ } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) {
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
proxy = new RealLocationTimeZoneProviderProxy(
@@ -250,9 +257,9 @@ public class LocationTimeZoneManagerService extends Binder {
private LocationTimeZoneProvider createSecondaryProvider() {
LocationTimeZoneProviderProxy proxy;
- if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) {
+ if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) {
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
- } else if (isDisabled(SECONDARY_PROVIDER_NAME)) {
+ } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) {
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
proxy = new RealLocationTimeZoneProviderProxy(
@@ -268,16 +275,14 @@ public class LocationTimeZoneManagerService extends Binder {
}
/** Used for bug triage and in tests to simulate provider events. */
- private static boolean isInSimulationMode(String providerName) {
- return isProviderModeSetInSystemProperties(providerName,
- SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
+ private boolean isProviderInSimulationMode(String providerName) {
+ return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED);
}
/** Used for bug triage, tests and experiments to remove a provider. */
- private boolean isDisabled(String providerName) {
+ private boolean isProviderDisabled(String providerName) {
return !isProviderEnabledInConfig(providerName)
- || isProviderModeSetInSystemProperties(
- providerName, SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
+ || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED);
}
private boolean isProviderEnabledInConfig(String providerName) {
@@ -299,25 +304,18 @@ public class LocationTimeZoneManagerService extends Binder {
return resources.getBoolean(providerEnabledConfigId);
}
- private static boolean isProviderModeSetInSystemProperties(
- @NonNull String providerName, @NonNull String mode) {
- String systemPropertyKey;
+ private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) {
switch (providerName) {
case PRIMARY_PROVIDER_NAME: {
- systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
- break;
+ return Objects.equals(mPrimaryProviderModeOverride, mode);
}
case SECONDARY_PROVIDER_NAME: {
- systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
- break;
+ return Objects.equals(mSecondaryProviderModeOverride, mode);
}
default: {
throw new IllegalArgumentException(providerName);
}
}
-
- String systemPropertyProviderMode = SystemProperties.get(systemPropertyKey, null);
- return Objects.equals(systemPropertyProviderMode, mode);
}
/**
@@ -347,6 +345,67 @@ public class LocationTimeZoneManagerService extends Binder {
this, in, out, err, args, callback, resultReceiver);
}
+ /** Sets this service into provider state recording mode for tests. */
+ void setProviderModeOverride(@NonNull String providerName, @NonNull String mode) {
+ enforceManageTimeZoneDetectorPermission();
+
+ Preconditions.checkArgument(
+ PRIMARY_PROVIDER_NAME.equals(providerName)
+ || SECONDARY_PROVIDER_NAME.equals(providerName));
+ Preconditions.checkArgument(PROVIDER_MODE_OVERRIDE_DISABLED.equals(mode)
+ || PROVIDER_MODE_OVERRIDE_SIMULATED.equals(mode)
+ || PROVIDER_MODE_OVERRIDE_NONE.equals(mode));
+
+ mThreadingDomain.postAndWait(() -> {
+ synchronized (mSharedLock) {
+ switch (providerName) {
+ case PRIMARY_PROVIDER_NAME: {
+ mPrimaryProviderModeOverride = mode;
+ break;
+ }
+ case SECONDARY_PROVIDER_NAME: {
+ mSecondaryProviderModeOverride = mode;
+ break;
+ }
+ }
+ }
+ }, BLOCKING_OP_WAIT_DURATION_MILLIS);
+ }
+
+ /** Sets this service into provider state recording mode for tests. */
+ void setProviderStateRecordingEnabled(boolean enabled) {
+ enforceManageTimeZoneDetectorPermission();
+
+ mThreadingDomain.postAndWait(() -> {
+ synchronized (mSharedLock) {
+ if (mLocationTimeZoneDetectorController != null) {
+ mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled);
+ }
+ }
+ }, BLOCKING_OP_WAIT_DURATION_MILLIS);
+ }
+
+ /** Returns a snapshot of the current controller state for tests. */
+ @NonNull
+ LocationTimeZoneManagerServiceState getStateForTests() {
+ enforceManageTimeZoneDetectorPermission();
+
+ try {
+ return mThreadingDomain.postAndWait(
+ () -> {
+ synchronized (mSharedLock) {
+ if (mLocationTimeZoneDetectorController == null) {
+ return null;
+ }
+ return mLocationTimeZoneDetectorController.getStateForTests();
+ }
+ },
+ BLOCKING_OP_WAIT_DURATION_MILLIS);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Passes a {@link TestCommand} to the specified provider and waits for the response.
*/
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
new file mode 100644
index 000000000000..b1dd55f3d4fd
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/** A snapshot of the location time zone manager service's state for tests. */
+final class LocationTimeZoneManagerServiceState {
+
+ @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+ @NonNull private final List<ProviderState> mPrimaryProviderStates;
+ @NonNull private final List<ProviderState> mSecondaryProviderStates;
+
+ LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
+ mLastSuggestion = builder.mLastSuggestion;
+ mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
+ mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
+ }
+
+ @Nullable
+ public GeolocationTimeZoneSuggestion getLastSuggestion() {
+ return mLastSuggestion;
+ }
+
+ @NonNull
+ public List<ProviderState> getPrimaryProviderStates() {
+ return Collections.unmodifiableList(mPrimaryProviderStates);
+ }
+
+ @NonNull
+ public List<ProviderState> getSecondaryProviderStates() {
+ return Collections.unmodifiableList(mSecondaryProviderStates);
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneManagerServiceState{"
+ + "mLastSuggestion=" + mLastSuggestion
+ + ", mPrimaryProviderStates=" + mPrimaryProviderStates
+ + ", mSecondaryProviderStates=" + mSecondaryProviderStates
+ + '}';
+ }
+
+ static final class Builder {
+
+ private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private List<ProviderState> mPrimaryProviderStates;
+ private List<ProviderState> mSecondaryProviderStates;
+
+ @NonNull
+ Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
+ mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+ return this;
+ }
+
+ @NonNull
+ Builder setPrimaryProviderStateChanges(@NonNull List<ProviderState> primaryProviderStates) {
+ mPrimaryProviderStates = new ArrayList<>(primaryProviderStates);
+ return this;
+ }
+
+ @NonNull
+ Builder setSecondaryProviderStateChanges(
+ @NonNull List<ProviderState> secondaryProviderStates) {
+ mSecondaryProviderStates = new ArrayList<>(secondaryProviderStates);
+ return this;
+ }
+
+ @NonNull
+ LocationTimeZoneManagerServiceState build() {
+ return new LocationTimeZoneManagerServiceState(this);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
index 554df07aa485..6f9863c9bd09 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
@@ -15,23 +15,46 @@
*/
package com.android.server.location.timezone;
+import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
+import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED;
-import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED;
+
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
import android.annotation.NonNull;
+import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneManagerProto;
+import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.TimeZoneProviderStateProto;
import android.os.Bundle;
import android.os.ShellCommand;
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */
class LocationTimeZoneManagerShellCommand extends ShellCommand {
@@ -58,9 +81,18 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
case SHELL_COMMAND_STOP: {
return runStop();
}
+ case SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE: {
+ return runSetProviderModeOverride();
+ }
case SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND: {
return runSendProviderTestCommand();
}
+ case SHELL_COMMAND_RECORD_PROVIDER_STATES: {
+ return runRecordProviderStates();
+ }
+ case SHELL_COMMAND_DUMP_STATE: {
+ return runDumpControllerState();
+ }
default: {
return handleDefaultCommands(cmd);
}
@@ -70,35 +102,42 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
- pw.println("Location Time Zone Manager (location_time_zone_manager) commands:");
+ pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:");
pw.println(" help");
pw.println(" Print this help text.");
pw.printf(" %s\n", SHELL_COMMAND_START);
pw.println(" Starts the location_time_zone_manager, creating time zone providers.");
pw.printf(" %s\n", SHELL_COMMAND_STOP);
pw.println(" Stops the location_time_zone_manager, destroying time zone providers.");
+ pw.printf(" %s <provider name> <mode>\n", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE);
+ pw.println(" Sets a provider into a test mode next time the service started.");
+ pw.printf(" Values: %s|%s|%s\n", PROVIDER_MODE_OVERRIDE_NONE,
+ PROVIDER_MODE_OVERRIDE_DISABLED, PROVIDER_MODE_OVERRIDE_SIMULATED);
+ pw.printf(" %s (true|false)\n", SHELL_COMMAND_RECORD_PROVIDER_STATES);
+ pw.printf(" Enables / disables provider state recording mode. See also %s. The default"
+ + " state is always \"false\".\n", SHELL_COMMAND_DUMP_STATE);
+ pw.println(" Note: When enabled, this mode consumes memory and it is only intended for"
+ + " testing.");
+ pw.println(" It should be disabled after use, or the device can be rebooted to"
+ + " reset the mode to disabled.");
+ pw.println(" Disabling (or enabling repeatedly) clears any existing stored states.");
+ pw.printf(" %s [%s]\n", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
+ pw.println(" Dumps Location Time Zone Manager state for tests as text or binary proto"
+ + " form.");
+ pw.println(" See the LocationTimeZoneManagerServiceStateProto definition for details.");
pw.printf(" %s <provider name> <test command>\n",
SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println(" Passes a test command to the named provider.");
pw.println();
- pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
- pw.println();
pw.printf("<provider name> = One of %s\n", VALID_PROVIDER_NAMES);
pw.println();
- pw.println("<test command> encoding:");
+ pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println();
- TestCommand.printShellCommandEncodingHelp(pw);
+ pw.println("Provider <test command> encoding:");
pw.println();
- pw.printf("Provider modes can be modified by setting the \"%s\" or \"%s\"\n system"
- + " property and restarting the service or rebooting the device.\n",
- SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY,
- SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY);
- pw.println("Values are:");
- pw.printf(" %s - simulation mode (see below for commands)\n",
- SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
- pw.printf(" %s - disabled mode\n", SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
+ TestCommand.printShellCommandEncodingHelp(pw);
pw.println();
- pw.println("Simulated providers can be used to test the system server behavior or to"
+ pw.println("Simulated provider mode can be used to test the system server behavior or to"
+ " reproduce bugs without the complexity of using real providers.");
pw.println();
pw.println("The test commands for simulated providers are:");
@@ -132,6 +171,119 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetProviderModeOverride() {
+ PrintWriter outPrintWriter = getOutPrintWriter();
+ try {
+ String providerName = getNextArgRequired();
+ String modeOverride = getNextArgRequired();
+ outPrintWriter.println("Setting provider mode override for " + providerName
+ + " to " + modeOverride);
+ mService.setProviderModeOverride(providerName, modeOverride);
+ } catch (RuntimeException e) {
+ reportError(e);
+ return 1;
+ }
+ return 0;
+ }
+
+ private int runRecordProviderStates() {
+ PrintWriter outPrintWriter = getOutPrintWriter();
+ boolean enabled;
+ try {
+ String nextArg = getNextArgRequired();
+ enabled = Boolean.parseBoolean(nextArg);
+ } catch (RuntimeException e) {
+ reportError(e);
+ return 1;
+ }
+
+ outPrintWriter.println("Setting provider state recording to " + enabled);
+ try {
+ mService.setProviderStateRecordingEnabled(enabled);
+ } catch (IllegalStateException e) {
+ reportError(e);
+ return 2;
+ }
+ return 0;
+ }
+
+ private int runDumpControllerState() {
+ LocationTimeZoneManagerServiceState state;
+ try {
+ state = mService.getStateForTests();
+ } catch (RuntimeException e) {
+ reportError(e);
+ return 1;
+ }
+
+ DualDumpOutputStream outputStream;
+ boolean useProto = Objects.equals(DUMP_STATE_OPTION_PROTO, getNextOption());
+ if (useProto) {
+ FileDescriptor outFd = getOutFileDescriptor();
+ outputStream = new DualDumpOutputStream(new ProtoOutputStream(outFd));
+ } else {
+ outputStream = new DualDumpOutputStream(
+ new IndentingPrintWriter(getOutPrintWriter(), " "));
+ }
+ if (state.getLastSuggestion() != null) {
+ GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
+ long lastSuggestionToken = outputStream.start(
+ "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
+ for (String zoneId : lastSuggestion.getZoneIds()) {
+ outputStream.write(
+ "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+ }
+ for (String debugInfo : lastSuggestion.getDebugInfo()) {
+ outputStream.write(
+ "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+ }
+ outputStream.end(lastSuggestionToken);
+ }
+
+ writeProviderStates(outputStream, state.getPrimaryProviderStates(),
+ "primary_provider_states",
+ LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES);
+ writeProviderStates(outputStream, state.getSecondaryProviderStates(),
+ "secondary_provider_states",
+ LocationTimeZoneManagerServiceStateProto.SECONDARY_PROVIDER_STATES);
+ outputStream.flush();
+
+ return 0;
+ }
+
+ private static void writeProviderStates(DualDumpOutputStream outputStream,
+ List<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName,
+ long fieldId) {
+ for (LocationTimeZoneProvider.ProviderState providerState : providerStates) {
+ long providerStateToken = outputStream.start(fieldName, fieldId);
+ outputStream.write("state", TimeZoneProviderStateProto.STATE,
+ convertProviderStateEnumToProtoEnum(providerState.stateEnum));
+ outputStream.end(providerStateToken);
+ }
+ }
+
+ private static int convertProviderStateEnumToProtoEnum(@ProviderStateEnum int stateEnum) {
+ switch (stateEnum) {
+ case PROVIDER_STATE_UNKNOWN:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNKNOWN;
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_INITIALIZING;
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_CERTAIN;
+ case PROVIDER_STATE_STARTED_UNCERTAIN:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNCERTAIN;
+ case PROVIDER_STATE_STOPPED:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DISABLED;
+ case PROVIDER_STATE_PERM_FAILED:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_PERM_FAILED;
+ case PROVIDER_STATE_DESTROYED:
+ return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DESTROYED;
+ default: {
+ throw new IllegalArgumentException("Unknown stateEnum=" + stateEnum);
+ }
+ }
+ }
+
private int runSendProviderTestCommand() {
PrintWriter outPrintWriter = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 132c1671f725..9a7b7750659c 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -49,6 +49,8 @@ import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -339,11 +341,20 @@ abstract class LocationTimeZoneProvider implements Dumpable {
@NonNull final String mProviderName;
/**
+ * Usually {@code false} but can be set to {@code true} for testing.
+ */
+ @GuardedBy("mSharedLock")
+ private boolean mStateChangeRecording;
+
+ @GuardedBy("mSharedLock")
+ @NonNull
+ private final ArrayList<ProviderState> mRecordedStates = new ArrayList<>(0);
+
+ /**
* The current state (with history for debugging).
*/
@GuardedBy("mSharedLock")
- final ReferenceWithHistory<ProviderState> mCurrentState =
- new ReferenceWithHistory<>(10);
+ final ReferenceWithHistory<ProviderState> mCurrentState = new ReferenceWithHistory<>(10);
/**
* Used for scheduling initialization timeouts, i.e. for providers that have just been started.
@@ -423,6 +434,28 @@ abstract class LocationTimeZoneProvider implements Dumpable {
abstract void onDestroy();
/**
+ * Sets the provider into state recording mode for tests.
+ */
+ final void setStateChangeRecordingEnabled(boolean enabled) {
+ mThreadingDomain.assertCurrentThread();
+ synchronized (mSharedLock) {
+ mStateChangeRecording = enabled;
+ mRecordedStates.clear();
+ mRecordedStates.trimToSize();
+ }
+ }
+
+ /**
+ * Returns recorded states.
+ */
+ final List<ProviderState> getRecordedStates() {
+ mThreadingDomain.assertCurrentThread();
+ synchronized (mSharedLock) {
+ return new ArrayList<>(mRecordedStates);
+ }
+ }
+
+ /**
* Set the current state, for use by this class and subclasses only. If {@code #notifyChanges}
* is {@code true} and {@code newState} is not equal to the old state, then {@link
* ProviderListener#onProviderStateChange(ProviderState)} must be called on
@@ -434,8 +467,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
ProviderState oldState = mCurrentState.get();
mCurrentState.set(newState);
onSetCurrentState(newState);
- if (notifyChanges) {
- if (!Objects.equals(newState, oldState)) {
+ if (!Objects.equals(newState, oldState)) {
+ if (mStateChangeRecording) {
+ mRecordedStates.add(newState);
+ }
+ if (notifyChanges) {
mProviderListener.onProviderStateChange(newState);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 033bfa648f2f..865571e90338 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -25,6 +25,7 @@ import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
+import android.location.LocationManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -311,6 +312,19 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion));
}
+ boolean isGeoTimeZoneDetectionSupported() {
+ enforceManageTimeZoneDetectorPermission();
+
+ return isGeoLocationTimeZoneDetectionEnabled(mContext);
+ }
+
+ boolean isLocationEnabled() {
+ enforceManageTimeZoneDetectorPermission();
+
+ return mContext.getSystemService(LocationManager.class)
+ .isLocationEnabledForUser(mContext.getUser());
+ }
+
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 13b44566c66d..b2630300a6aa 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,10 +15,17 @@
*/
package com.android.server.timezonedetector;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_LOCATION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
@@ -43,6 +50,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
}
switch (cmd) {
+ case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED:
+ return runIsAutoDetectionEnabled();
+ case SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED:
+ return runSetAutoDetectionEnabled();
+ case SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED:
+ return runIsGeoDetectionSupported();
+ case SHELL_COMMAND_IS_LOCATION_ENABLED:
+ return runIsLocationEnabled();
+ case SHELL_COMMAND_IS_GEO_DETECTION_ENABLED:
+ return runIsGeoDetectionEnabled();
+ case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
+ return runSetGeoDetectionEnabled();
case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
return runSuggestGeolocationTimeZone();
case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
@@ -55,6 +74,54 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
}
}
+ private int runIsAutoDetectionEnabled() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean enabled = mInterface.getCapabilitiesAndConfig()
+ .getConfiguration()
+ .isAutoDetectionEnabled();
+ pw.println(enabled);
+ return 0;
+ }
+
+ private int runIsGeoDetectionSupported() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean enabled = mInterface.isGeoTimeZoneDetectionSupported();
+ pw.println(enabled);
+ return 0;
+ }
+
+ private int runIsLocationEnabled() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean enabled = mInterface.isLocationEnabled();
+ pw.println(enabled);
+ return 0;
+ }
+
+ private int runIsGeoDetectionEnabled() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean enabled = mInterface.getCapabilitiesAndConfig()
+ .getConfiguration()
+ .isGeoDetectionEnabled();
+ pw.println(enabled);
+ return 0;
+ }
+
+ private int runSetAutoDetectionEnabled() {
+ boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+ TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(enabled)
+ .build();
+ return mInterface.updateConfiguration(configuration) ? 0 : 1;
+ }
+
+ private int runSetGeoDetectionEnabled() {
+ boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+ TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(enabled)
+ .build();
+ return mInterface.updateConfiguration(configuration) ? 0 : 1;
+ }
+
private int runSuggestGeolocationTimeZone() {
return runSuggestTimeZone(
() -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
@@ -96,6 +163,20 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.println("Time Zone Detector (time_zone_detector) commands:");
pw.println(" help");
pw.println(" Print this help text.");
+ pw.printf(" %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
+ pw.println(" Prints true/false according to the automatic tz detection setting");
+ pw.printf(" %s true|false\n", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED);
+ pw.println(" Sets the automatic tz detection setting.");
+ pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
+ pw.println(" Prints true/false according to whether geolocation time zone detection is"
+ + " supported on this device");
+ pw.printf(" %s\n", SHELL_COMMAND_IS_LOCATION_ENABLED);
+ pw.println(" Prints true/false according to whether the master location toggle is"
+ + " enabled for the current user");
+ pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
+ pw.println(" Prints true/false according to the geolocation tz detection setting");
+ pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED);
+ pw.println(" Sets the geolocation tz detection setting.");
pw.printf(" %s <geolocation suggestion opts>\n",
SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
pw.printf(" %s <manual suggestion opts>\n",
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index 23365f70b2f4..5cff2081e59e 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -28,6 +28,7 @@ import static com.android.server.location.timezone.TimeZoneProviderEvent.createU
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -923,6 +924,74 @@ public class ControllerImplTest {
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
+ @Test
+ public void stateRecording() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ {
+ LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ assertNull(state.getLastSuggestion());
+ assertTrue(state.getPrimaryProviderStates().isEmpty());
+ assertTrue(state.getSecondaryProviderStates().isEmpty());
+ }
+
+ // State recording and simulate some provider behavior that will show up in the state
+ // recording.
+ controllerImpl.setProviderStateRecordingEnabled(true);
+
+ // Simulate an uncertain event from the primary. This will start the secondary.
+ mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ {
+ LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ assertNull(state.getLastSuggestion());
+ List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
+ state.getPrimaryProviderStates();
+ assertEquals(1, primaryProviderStates.size());
+ assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN,
+ primaryProviderStates.get(0).stateEnum);
+ List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
+ state.getSecondaryProviderStates();
+ assertEquals(1, secondaryProviderStates.size());
+ assertEquals(PROVIDER_STATE_STARTED_INITIALIZING,
+ secondaryProviderStates.get(0).stateEnum);
+ }
+
+ // Simulate an uncertain event from the primary. This will start the secondary.
+ mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ {
+ LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
+ state.getLastSuggestion().getZoneIds());
+ List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
+ state.getPrimaryProviderStates();
+ assertEquals(1, primaryProviderStates.size());
+ assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum);
+ List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
+ state.getSecondaryProviderStates();
+ assertEquals(2, secondaryProviderStates.size());
+ assertEquals(PROVIDER_STATE_STARTED_CERTAIN, secondaryProviderStates.get(1).stateEnum);
+ }
+
+ controllerImpl.setProviderStateRecordingEnabled(false);
+ {
+ LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
+ state.getLastSuggestion().getZoneIds());
+ assertTrue(state.getPrimaryProviderStates().isEmpty());
+ assertTrue(state.getSecondaryProviderStates().isEmpty());
+ }
+ }
+
private static void assertUncertaintyTimeoutSet(
LocationTimeZoneProviderController.Environment environment,
LocationTimeZoneProviderController controller) {
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
index 49c67ea1b8f1..cb292db50115 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
@@ -18,6 +18,7 @@ package com.android.server.location.timezone;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
@@ -49,6 +50,7 @@ import org.junit.Test;
import java.time.Duration;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -174,6 +176,48 @@ public class LocationTimeZoneProviderTest {
assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY));
}
+ @Test
+ public void stateRecording() {
+ String providerName = "primary";
+ TestLocationTimeZoneProvider provider =
+ new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ provider.setStateChangeRecordingEnabled(true);
+
+ // initialize()
+ provider.initialize(mProviderListener);
+ provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
+
+ // startUpdates()
+ ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
+ Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+ Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ provider.startUpdates(config, arbitraryInitializationTimeout,
+ arbitraryInitializationTimeoutFuzz);
+ provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING);
+
+ // Simulate a suggestion event being received.
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .setTimeZoneIds(Arrays.asList("Europe/London"))
+ .build();
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
+ provider.simulateProviderEventReceived(event);
+ provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
+
+ // Simulate an uncertain event being received.
+ event = TimeZoneProviderEvent.createUncertainEvent();
+ provider.simulateProviderEventReceived(event);
+ provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
+
+ // stopUpdates()
+ provider.stopUpdates();
+ provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
+
+ // destroy()
+ provider.destroy();
+ provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED);
+ }
+
/** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */
private static class TestProviderListener implements ProviderListener {
@@ -257,5 +301,11 @@ public class LocationTimeZoneProviderTest {
void assertOnDestroyCalled() {
assertTrue(mOnDestroyCalled);
}
+
+ void assertLatestRecordedState(@ProviderState.ProviderStateEnum int expectedStateEnum) {
+ List<ProviderState> recordedStates = getRecordedStates();
+ assertEquals(expectedStateEnum,
+ recordedStates.get(recordedStates.size() - 1).stateEnum);
+ }
}
}