diff options
| author | 2020-12-23 20:13:24 +0000 | |
|---|---|---|
| committer | 2021-01-08 16:19:39 +0000 | |
| commit | 9e34ba7c6870af619c5b87c22f16d24b81cdf94d (patch) | |
| tree | 43b6183831b074b0e8c851b218db84b0ef59ad87 | |
| parent | 1d30d73d0eef90522dcc46803042155405c6435d (diff) | |
Add shell commands and other support for host CTS
Host CTS tests are needed for metrics approval. The added shell commands
enable tests to assert LocationTimeZoneManager behavior / internal
states.
location_time_zone_manager:
The location time zone manager can be set into a mode where it records
provider state changes. It can also dump its state on request as a
proto. This will enable the impact of commands made during tests to be
asserted.
Example usages:
A command to start recording state changes:
$ adb shell cmd location_time_zone_manager record_provider_states true
A command to dump the manager's state:
$ adb shell cmd location_time_zone_manager dump_state
(--proto can be used to obtain a binary proto representation of the
state, easier for parsing / manipulating during tests than text output).
This commit also switches to using shell commands to set providers into
"simulated" and "disabled" mode. Previously this was handled with system
properties (requiring an -eng build) and a reboot to take effect. Now
it is achieved via the set_provider_mode_override shell command, is
non-persistent, and it leverages the new shell commands that can be used
to stop / start the location_time_zone_manager service.
time_zone_detector:
New commands have been added to query and modify higher-level time
zone-related settings for host tests:
+ is_auto_detection_enabled
+ set_auto_detection_enabled
+ is_geo_detection_supported
+ is_location_enabled
+ is_geo_detection_enabled
+ set_geo_detection_enabled
See also for help:
$ adb shell cmd location_time_zone_manager
$ adb shell cmd time_zone_detector
Bug: 172934905
Test: atest services/tests/servicestests/src/com/android/server/location/timezone/
Test: atest CtsLocationTimeZoneManagerHostTest
Change-Id: I318d82f292f48fa5bb106f84104c4856f7ef6e1c
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); + } } } |