Loading core/java/android/app/time/LocationTimeZoneManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ public final class LocationTimeZoneManager { /** * The name of the service for shell commands */ public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; public static final String SERVICE_NAME = "location_time_zone_manager"; /** * A shell command that starts the service (after stop). Loading services/core/java/com/android/server/timedetector/DeviceConfig.java→services/core/java/com/android/server/timedetector/ServerFlags.java +238 −0 Original line number Diff line number Diff line Loading @@ -20,49 +20,74 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.content.Context; import android.provider.DeviceConfig; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.server.timezonedetector.ConfigurationChangeListener; import com.android.server.timezonedetector.ServiceConfigAccessor; import java.time.Duration; import java.util.concurrent.Executor; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; /** * A helper class for reading / monitoring the {@link * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags. * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace * for server-configured flags. */ public final class DeviceConfig { public final class ServerFlags { private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); /** * An annotation used to indicate when a {@link * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required. * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is * required. * * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the * prefix "geotz_" on all of its key strings. */ @StringDef(prefix = "KEY_", value = { KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, }) @interface DeviceConfigKey {} /** * The key to force location time zone detection on for a device. Only intended for use during * release testing with droidfooders. The user can still disable the feature by turning off the * master location switch, or disabling automatic time zone detection. * Controls whether the location time zone manager service will started. Only observed if * the device build is configured to support location-based time zone detection. See * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. */ @DeviceConfigKey public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED = "force_location_time_zone_detection_enabled"; public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = "location_time_zone_detection_feature_supported"; /** * The key for the default value used to determine whether location time zone detection is * enabled when the user hasn't explicitly set it yet. * The key for the server flag that can override the device config for whether the primary * location time zone provider is enabled or disabled. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT = "location_time_zone_detection_enabled_default"; public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = "primary_location_time_zone_provider_enabled_override"; /** * The key for the server flag that can override the device config for whether the secondary * location time zone provider is enabled or disabled. */ @DeviceConfigKey public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = "secondary_location_time_zone_provider_enabled_override"; /** * The key for the minimum delay after location time zone detection has been enabled before the Loading @@ -89,35 +114,122 @@ public final class DeviceConfig { public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = "ltpz_init_timeout_fuzz_millis"; /** Creates an instance. */ public DeviceConfig() {} /** * The key for the server flag that can override location time zone detection being enabled for * a user. Only intended for use during release testing with droidfooders. The user can still * disable the feature by turning off the master location switch, or by disabling automatic time * zone detection. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = "location_time_zone_detection_setting_enabled_override"; /** Adds a listener for the system_time namespace. */ public void addListener( @NonNull Executor handlerExecutor, @NonNull Runnable listener) { android.provider.DeviceConfig.addOnPropertiesChangedListener( /** * The key for the default value used to determine whether location time zone detection is * enabled when the user hasn't explicitly set it yet. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = "location_time_zone_detection_setting_enabled_default"; @GuardedBy("mListeners") private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); private static final Object SLOCK = new Object(); @GuardedBy("SLOCK") @Nullable private static ServerFlags sInstance; private ServerFlags(Context context) { DeviceConfig.addOnPropertiesChangedListener( NAMESPACE_SYSTEM_TIME, handlerExecutor, properties -> listener.run()); context.getMainExecutor(), this::handlePropertiesChanged); } /** Returns the singleton instance. */ public static ServerFlags getInstance(Context context) { synchronized (SLOCK) { if (sInstance == null) { sInstance = new ServerFlags(context); } return sInstance; } } private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mListeners) { for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry : mListeners.entrySet()) { if (intersects(listenerEntry.getValue(), properties.getKeyset())) { listenerEntry.getKey().onChange(); } } } } private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) { for (String toFind : one) { if (two.contains(toFind)) { return true; } } return false; } /** * Adds a listener for the system_time namespace that will trigger if any of the specified keys * change. Listener callbacks are delivered on the main looper thread. * * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no * associated remove method. */ public void addListener(@NonNull ConfigurationChangeListener listener, @NonNull Set<String> keys) { Objects.requireNonNull(listener); Objects.requireNonNull(keys); synchronized (mListeners) { mListeners.put(listener, keys); } } /** * Returns an optional boolean value from {@link DeviceConfig} from the system_time * namespace, returns {@link Optional#empty()} if there is no explicit value set. */ @NonNull public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); return parseOptionalBoolean(value); } @NonNull private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { if (value == null) { return Optional.empty(); } else { return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; } } /** * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time * Returns a boolean value from {@link DeviceConfig} from the system_time * namespace, or {@code defaultValue} if there is no explicit value set. */ public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); } /** * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time * Returns a positive duration from {@link DeviceConfig} from the system_time * namespace, or {@code defaultValue} if there is no explicit value set. */ @Nullable public Duration getDurationFromMillis( @DeviceConfigKey String key, @Nullable Duration defaultValue) { long deviceConfigValue = android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); if (deviceConfigValue < 0) { return defaultValue; } Loading services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java +3 −2 Original line number Diff line number Diff line Loading @@ -17,10 +17,11 @@ package com.android.server.timezonedetector; /** * A listener used to receive notification that time zone configuration has changed. * A listener used to receive notification that configuration has / may have changed (depending on * the usecase). */ @FunctionalInterface public interface ConfigurationChangeListener { /** Called when the current user or a configuration value has changed. */ /** Called when the configuration may have changed. */ void onChange(); } services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +26 −25 Original line number Diff line number Diff line Loading @@ -33,26 +33,27 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** * Holds all configuration values that affect time zone behavior and some associated logic, e.g. * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link * #createCapabilitiesAndConfig()}. * Holds configuration values that affect user-facing time zone behavior and some associated logic. * Some configuration is global, some is user scoped, but this class deliberately doesn't make a * distinction for simplicity. */ public final class ConfigurationInternal { private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; private final boolean mAutoDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; private final boolean mLocationEnabled; private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; mAutoDetectionSupported = builder.mAutoDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported Loading @@ -60,22 +61,6 @@ public final class ConfigurationInternal { Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } /** Returns the ID of the user this configuration is associated with. */ public @UserIdInt int getUserId() { return mUserId; } /** Returns the handle of the user this configuration is associated with. */ @NonNull public UserHandle getUserHandle() { return UserHandle.of(mUserId); } /** Returns true if the user allowed to modify time zone configuration. */ public boolean isUserConfigAllowed() { return mUserConfigAllowed; } /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { return mAutoDetectionSupported; Loading @@ -98,6 +83,22 @@ public final class ConfigurationInternal { return mAutoDetectionSupported && mAutoDetectionEnabled; } /** Returns the ID of the user this configuration is associated with. */ public @UserIdInt int getUserId() { return mUserId; } /** Returns the handle of the user this configuration is associated with. */ @NonNull public UserHandle getUserHandle() { return UserHandle.of(mUserId); } /** Returns true if the user allowed to modify time zone configuration. */ public boolean isUserConfigAllowed() { return mUserConfigAllowed; } /** Returns true if user's location can be used generally. */ public boolean isLocationEnabled() { return mLocationEnabled; Loading Loading @@ -283,7 +284,7 @@ public final class ConfigurationInternal { /** * Sets whether any form of automatic time zone detection is supported on this device. */ public Builder setAutoDetectionSupported(boolean supported) { public Builder setAutoDetectionFeatureSupported(boolean supported) { mAutoDetectionSupported = supported; return this; } Loading @@ -291,7 +292,7 @@ public final class ConfigurationInternal { /** * Sets whether geolocation time zone detection is supported on this device. */ public Builder setGeoDetectionSupported(boolean supported) { public Builder setGeoDetectionFeatureSupported(boolean supported) { mGeoDetectionSupported = supported; return this; } Loading services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +28 −55 Original line number Diff line number Diff line Loading @@ -31,9 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.location.LocationManager; import android.net.ConnectivityManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; Loading @@ -42,10 +40,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.timedetector.DeviceConfig; import java.util.Objects; import java.util.concurrent.Executor; import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. Loading @@ -59,8 +56,7 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @NonNull private final Handler mHandler; @NonNull private final ContentResolver mCr; @NonNull private final UserManager mUserManager; @NonNull private final DeviceConfig mDeviceConfig; @NonNull private final boolean mGeoDetectionSupported; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final LocationManager mLocationManager; // @NonNull after setConfigChangeListener() is called. Loading @@ -68,17 +64,16 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) { @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); Executor handlerExecutor = new HandlerExecutor(mHandler); mCr = context.getContentResolver(); mUserManager = context.getSystemService(UserManager.class); mLocationManager = context.getSystemService(LocationManager.class); mDeviceConfig = deviceConfig; mGeoDetectionSupported = geoDetectionSupported; mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); // Wire up the change listeners. All invocations are performed on the mHandler thread. // Wire up the config change listeners. All invocations are performed on the mHandler // thread. // Listen for the user changing / the user's location mode changing. IntentFilter filter = new IntentFilter(); Loading Loading @@ -112,13 +107,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir handleConfigChangeOnHandlerThread(); } }, UserHandle.USER_ALL); // Add async callbacks for changes to server-side flags: some of the flags affect device / // user config. All changes can be treated like a config change. If flags that affect config // haven't changed then call will be a no-op. mDeviceConfig.addListener( handlerExecutor, this::handleConfigChangeOnHandlerThread); } private void handleConfigChangeOnHandlerThread() { Loading @@ -140,10 +128,12 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(isUserConfigAllowed(userId)) .setAutoDetectionSupported(isAutoDetectionSupported()) .setGeoDetectionSupported(isGeoDetectionSupported()) .setAutoDetectionFeatureSupported( mServiceConfigAccessor.isAutoDetectionFeatureSupported()) .setGeoDetectionFeatureSupported( mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabled(isLocationEnabled(userId)) .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) .build(); Loading Loading @@ -186,18 +176,19 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir // time zone detection: if we wrote it down then we'd set the value explicitly, which would // prevent detecting "default" later. That might influence what happens on later releases // that support new types of auto detection on the same hardware. if (isAutoDetectionSupported()) { if (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); setAutoDetectionEnabledIfRequired(autoDetectionEnabled); // Avoid writing the geo detection enabled setting for devices that do not support geo // time zone detection: if we wrote it down then we'd set the value explicitly, which // would prevent detecting "default" later. That might influence what happens on later // releases that support geo detection on the same hardware. // Also avoid writing the geo detection enabled setting for devices that are currently // force-enabled: otherwise we might overwrite a droidfood user's real setting // permanently. if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) { // Avoid writing the geo detection enabled setting for devices with settings that // are currently overridden by server flags: otherwise we might overwrite a droidfood // user's real setting permanently. // Also avoid writing the geo detection enabled setting for devices that do not support // geo time zone detection: if we wrote it down then we'd set the value explicitly, // which would prevent detecting "default" later. That might influence what happens on // later releases that start to support geo detection on the same hardware. if (!mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride().isPresent() && mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); } Loading @@ -209,14 +200,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } private boolean isAutoDetectionSupported() { return deviceHasTelephonyNetwork() || isGeoDetectionSupported(); } private boolean isGeoDetectionSupported() { return mGeoDetectionSupported; } private boolean isAutoDetectionEnabled() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } Loading @@ -237,24 +220,20 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private boolean isGeoDetectionEnabled(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection // on for testers (where their other settings allow). boolean forceEnabled = isGeoDetectionForceEnabled(); if (forceEnabled) { return true; // on/off for testers (but only where their other settings would allow them to turn it on // for themselves). Optional<Boolean> override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); if (override.isPresent()) { return override.get(); } final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean( DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false); final boolean geoDetectionEnabledByDefault = mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); return Settings.Secure.getIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } private boolean isGeoDetectionForceEnabled() { return mDeviceConfig.getBoolean( DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false); } private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 if (isGeoDetectionEnabled(userId) != enabled) { Loading @@ -262,10 +241,4 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir enabled ? 1 : 0, userId); } } private boolean deviceHasTelephonyNetwork() { // TODO b/150583524 Avoid the use of a deprecated API. return mContext.getSystemService(ConnectivityManager.class) .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } } Loading
core/java/android/app/time/LocationTimeZoneManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ public final class LocationTimeZoneManager { /** * The name of the service for shell commands */ public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; public static final String SERVICE_NAME = "location_time_zone_manager"; /** * A shell command that starts the service (after stop). Loading
services/core/java/com/android/server/timedetector/DeviceConfig.java→services/core/java/com/android/server/timedetector/ServerFlags.java +238 −0 Original line number Diff line number Diff line Loading @@ -20,49 +20,74 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.content.Context; import android.provider.DeviceConfig; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.server.timezonedetector.ConfigurationChangeListener; import com.android.server.timezonedetector.ServiceConfigAccessor; import java.time.Duration; import java.util.concurrent.Executor; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; /** * A helper class for reading / monitoring the {@link * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags. * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace * for server-configured flags. */ public final class DeviceConfig { public final class ServerFlags { private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); /** * An annotation used to indicate when a {@link * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required. * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is * required. * * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the * prefix "geotz_" on all of its key strings. */ @StringDef(prefix = "KEY_", value = { KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, }) @interface DeviceConfigKey {} /** * The key to force location time zone detection on for a device. Only intended for use during * release testing with droidfooders. The user can still disable the feature by turning off the * master location switch, or disabling automatic time zone detection. * Controls whether the location time zone manager service will started. Only observed if * the device build is configured to support location-based time zone detection. See * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. */ @DeviceConfigKey public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED = "force_location_time_zone_detection_enabled"; public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = "location_time_zone_detection_feature_supported"; /** * The key for the default value used to determine whether location time zone detection is * enabled when the user hasn't explicitly set it yet. * The key for the server flag that can override the device config for whether the primary * location time zone provider is enabled or disabled. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT = "location_time_zone_detection_enabled_default"; public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = "primary_location_time_zone_provider_enabled_override"; /** * The key for the server flag that can override the device config for whether the secondary * location time zone provider is enabled or disabled. */ @DeviceConfigKey public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = "secondary_location_time_zone_provider_enabled_override"; /** * The key for the minimum delay after location time zone detection has been enabled before the Loading @@ -89,35 +114,122 @@ public final class DeviceConfig { public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = "ltpz_init_timeout_fuzz_millis"; /** Creates an instance. */ public DeviceConfig() {} /** * The key for the server flag that can override location time zone detection being enabled for * a user. Only intended for use during release testing with droidfooders. The user can still * disable the feature by turning off the master location switch, or by disabling automatic time * zone detection. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = "location_time_zone_detection_setting_enabled_override"; /** Adds a listener for the system_time namespace. */ public void addListener( @NonNull Executor handlerExecutor, @NonNull Runnable listener) { android.provider.DeviceConfig.addOnPropertiesChangedListener( /** * The key for the default value used to determine whether location time zone detection is * enabled when the user hasn't explicitly set it yet. */ @DeviceConfigKey public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = "location_time_zone_detection_setting_enabled_default"; @GuardedBy("mListeners") private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); private static final Object SLOCK = new Object(); @GuardedBy("SLOCK") @Nullable private static ServerFlags sInstance; private ServerFlags(Context context) { DeviceConfig.addOnPropertiesChangedListener( NAMESPACE_SYSTEM_TIME, handlerExecutor, properties -> listener.run()); context.getMainExecutor(), this::handlePropertiesChanged); } /** Returns the singleton instance. */ public static ServerFlags getInstance(Context context) { synchronized (SLOCK) { if (sInstance == null) { sInstance = new ServerFlags(context); } return sInstance; } } private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mListeners) { for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry : mListeners.entrySet()) { if (intersects(listenerEntry.getValue(), properties.getKeyset())) { listenerEntry.getKey().onChange(); } } } } private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) { for (String toFind : one) { if (two.contains(toFind)) { return true; } } return false; } /** * Adds a listener for the system_time namespace that will trigger if any of the specified keys * change. Listener callbacks are delivered on the main looper thread. * * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no * associated remove method. */ public void addListener(@NonNull ConfigurationChangeListener listener, @NonNull Set<String> keys) { Objects.requireNonNull(listener); Objects.requireNonNull(keys); synchronized (mListeners) { mListeners.put(listener, keys); } } /** * Returns an optional boolean value from {@link DeviceConfig} from the system_time * namespace, returns {@link Optional#empty()} if there is no explicit value set. */ @NonNull public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); return parseOptionalBoolean(value); } @NonNull private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { if (value == null) { return Optional.empty(); } else { return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; } } /** * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time * Returns a boolean value from {@link DeviceConfig} from the system_time * namespace, or {@code defaultValue} if there is no explicit value set. */ public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); } /** * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time * Returns a positive duration from {@link DeviceConfig} from the system_time * namespace, or {@code defaultValue} if there is no explicit value set. */ @Nullable public Duration getDurationFromMillis( @DeviceConfigKey String key, @Nullable Duration defaultValue) { long deviceConfigValue = android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); if (deviceConfigValue < 0) { return defaultValue; } Loading
services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java +3 −2 Original line number Diff line number Diff line Loading @@ -17,10 +17,11 @@ package com.android.server.timezonedetector; /** * A listener used to receive notification that time zone configuration has changed. * A listener used to receive notification that configuration has / may have changed (depending on * the usecase). */ @FunctionalInterface public interface ConfigurationChangeListener { /** Called when the current user or a configuration value has changed. */ /** Called when the configuration may have changed. */ void onChange(); }
services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +26 −25 Original line number Diff line number Diff line Loading @@ -33,26 +33,27 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** * Holds all configuration values that affect time zone behavior and some associated logic, e.g. * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link * #createCapabilitiesAndConfig()}. * Holds configuration values that affect user-facing time zone behavior and some associated logic. * Some configuration is global, some is user scoped, but this class deliberately doesn't make a * distinction for simplicity. */ public final class ConfigurationInternal { private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; private final boolean mAutoDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; private final boolean mLocationEnabled; private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; mAutoDetectionSupported = builder.mAutoDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported Loading @@ -60,22 +61,6 @@ public final class ConfigurationInternal { Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } /** Returns the ID of the user this configuration is associated with. */ public @UserIdInt int getUserId() { return mUserId; } /** Returns the handle of the user this configuration is associated with. */ @NonNull public UserHandle getUserHandle() { return UserHandle.of(mUserId); } /** Returns true if the user allowed to modify time zone configuration. */ public boolean isUserConfigAllowed() { return mUserConfigAllowed; } /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { return mAutoDetectionSupported; Loading @@ -98,6 +83,22 @@ public final class ConfigurationInternal { return mAutoDetectionSupported && mAutoDetectionEnabled; } /** Returns the ID of the user this configuration is associated with. */ public @UserIdInt int getUserId() { return mUserId; } /** Returns the handle of the user this configuration is associated with. */ @NonNull public UserHandle getUserHandle() { return UserHandle.of(mUserId); } /** Returns true if the user allowed to modify time zone configuration. */ public boolean isUserConfigAllowed() { return mUserConfigAllowed; } /** Returns true if user's location can be used generally. */ public boolean isLocationEnabled() { return mLocationEnabled; Loading Loading @@ -283,7 +284,7 @@ public final class ConfigurationInternal { /** * Sets whether any form of automatic time zone detection is supported on this device. */ public Builder setAutoDetectionSupported(boolean supported) { public Builder setAutoDetectionFeatureSupported(boolean supported) { mAutoDetectionSupported = supported; return this; } Loading @@ -291,7 +292,7 @@ public final class ConfigurationInternal { /** * Sets whether geolocation time zone detection is supported on this device. */ public Builder setGeoDetectionSupported(boolean supported) { public Builder setGeoDetectionFeatureSupported(boolean supported) { mGeoDetectionSupported = supported; return this; } Loading
services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +28 −55 Original line number Diff line number Diff line Loading @@ -31,9 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.location.LocationManager; import android.net.ConnectivityManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; Loading @@ -42,10 +40,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.timedetector.DeviceConfig; import java.util.Objects; import java.util.concurrent.Executor; import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. Loading @@ -59,8 +56,7 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @NonNull private final Handler mHandler; @NonNull private final ContentResolver mCr; @NonNull private final UserManager mUserManager; @NonNull private final DeviceConfig mDeviceConfig; @NonNull private final boolean mGeoDetectionSupported; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final LocationManager mLocationManager; // @NonNull after setConfigChangeListener() is called. Loading @@ -68,17 +64,16 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) { @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); Executor handlerExecutor = new HandlerExecutor(mHandler); mCr = context.getContentResolver(); mUserManager = context.getSystemService(UserManager.class); mLocationManager = context.getSystemService(LocationManager.class); mDeviceConfig = deviceConfig; mGeoDetectionSupported = geoDetectionSupported; mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); // Wire up the change listeners. All invocations are performed on the mHandler thread. // Wire up the config change listeners. All invocations are performed on the mHandler // thread. // Listen for the user changing / the user's location mode changing. IntentFilter filter = new IntentFilter(); Loading Loading @@ -112,13 +107,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir handleConfigChangeOnHandlerThread(); } }, UserHandle.USER_ALL); // Add async callbacks for changes to server-side flags: some of the flags affect device / // user config. All changes can be treated like a config change. If flags that affect config // haven't changed then call will be a no-op. mDeviceConfig.addListener( handlerExecutor, this::handleConfigChangeOnHandlerThread); } private void handleConfigChangeOnHandlerThread() { Loading @@ -140,10 +128,12 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(isUserConfigAllowed(userId)) .setAutoDetectionSupported(isAutoDetectionSupported()) .setGeoDetectionSupported(isGeoDetectionSupported()) .setAutoDetectionFeatureSupported( mServiceConfigAccessor.isAutoDetectionFeatureSupported()) .setGeoDetectionFeatureSupported( mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabled(isLocationEnabled(userId)) .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) .build(); Loading Loading @@ -186,18 +176,19 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir // time zone detection: if we wrote it down then we'd set the value explicitly, which would // prevent detecting "default" later. That might influence what happens on later releases // that support new types of auto detection on the same hardware. if (isAutoDetectionSupported()) { if (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); setAutoDetectionEnabledIfRequired(autoDetectionEnabled); // Avoid writing the geo detection enabled setting for devices that do not support geo // time zone detection: if we wrote it down then we'd set the value explicitly, which // would prevent detecting "default" later. That might influence what happens on later // releases that support geo detection on the same hardware. // Also avoid writing the geo detection enabled setting for devices that are currently // force-enabled: otherwise we might overwrite a droidfood user's real setting // permanently. if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) { // Avoid writing the geo detection enabled setting for devices with settings that // are currently overridden by server flags: otherwise we might overwrite a droidfood // user's real setting permanently. // Also avoid writing the geo detection enabled setting for devices that do not support // geo time zone detection: if we wrote it down then we'd set the value explicitly, // which would prevent detecting "default" later. That might influence what happens on // later releases that start to support geo detection on the same hardware. if (!mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride().isPresent() && mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); } Loading @@ -209,14 +200,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } private boolean isAutoDetectionSupported() { return deviceHasTelephonyNetwork() || isGeoDetectionSupported(); } private boolean isGeoDetectionSupported() { return mGeoDetectionSupported; } private boolean isAutoDetectionEnabled() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } Loading @@ -237,24 +220,20 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private boolean isGeoDetectionEnabled(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection // on for testers (where their other settings allow). boolean forceEnabled = isGeoDetectionForceEnabled(); if (forceEnabled) { return true; // on/off for testers (but only where their other settings would allow them to turn it on // for themselves). Optional<Boolean> override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); if (override.isPresent()) { return override.get(); } final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean( DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false); final boolean geoDetectionEnabledByDefault = mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); return Settings.Secure.getIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } private boolean isGeoDetectionForceEnabled() { return mDeviceConfig.getBoolean( DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false); } private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 if (isGeoDetectionEnabled(userId) != enabled) { Loading @@ -262,10 +241,4 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir enabled ? 1 : 0, userId); } } private boolean deviceHasTelephonyNetwork() { // TODO b/150583524 Avoid the use of a deprecated API. return mContext.getSystemService(ConnectivityManager.class) .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } }