diff options
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); + } } } |