Merge "Improve server flags / add more server control" into sc-dev

This commit is contained in:
Neil Fuller
2021-02-25 18:24:54 +00:00
committed by Android (Google) Code Review
19 changed files with 707 additions and 363 deletions

View File

@@ -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).

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2021 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.timedetector;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import java.time.Duration;
import java.util.concurrent.Executor;
/**
* A helper class for reading / monitoring the {@link
* android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags.
*/
public final class DeviceConfig {
/**
* An annotation used to indicate when a {@link
* android.provider.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
* 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_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
})
@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.
*/
@DeviceConfigKey
public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED =
"force_location_time_zone_detection_enabled";
/**
* 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_ENABLED_DEFAULT =
"location_time_zone_detection_enabled_default";
/**
* The key for the minimum delay after location time zone detection has been enabled before the
* location time zone manager can report it is uncertain about the time zone.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS =
"location_time_zone_detection_uncertainty_delay_millis";
/**
* The key for the timeout passed to a location time zone provider that tells it how long it has
* to provide an explicit first suggestion without being declared uncertain.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS =
"ltpz_init_timeout_millis";
/**
* The key for the extra time added to {@link
* #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
* manager before the location time zone provider will actually be declared uncertain.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
"ltpz_init_timeout_fuzz_millis";
/** Creates an instance. */
public DeviceConfig() {}
/** Adds a listener for the system_time namespace. */
public void addListener(
@NonNull Executor handlerExecutor, @NonNull Runnable listener) {
android.provider.DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_SYSTEM_TIME,
handlerExecutor,
properties -> listener.run());
}
/**
* Returns a boolean value from {@link android.provider.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);
}
/**
* Returns a positive duration from {@link android.provider.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);
if (deviceConfigValue < 0) {
return defaultValue;
}
return Duration.ofMillis(deviceConfigValue);
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2021 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.timedetector;
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.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace
* for server-configured flags.
*/
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 DeviceConfig#NAMESPACE_SYSTEM_TIME} key is
* required.
*
* <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider
* also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the
* prefix "geotz_" on all of its key strings.
*/
@StringDef(prefix = "KEY_", value = {
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 {}
/**
* 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_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED =
"location_time_zone_detection_feature_supported";
/**
* 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_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
* location time zone manager can report it is uncertain about the time zone.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS =
"location_time_zone_detection_uncertainty_delay_millis";
/**
* The key for the timeout passed to a location time zone provider that tells it how long it has
* to provide an explicit first suggestion without being declared uncertain.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS =
"ltpz_init_timeout_millis";
/**
* The key for the extra time added to {@link
* #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
* manager before the location time zone provider will actually be declared uncertain.
*/
@DeviceConfigKey
public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
"ltpz_init_timeout_fuzz_millis";
/**
* 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";
/**
* 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,
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 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 DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue);
}
/**
* 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 = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1);
if (deviceConfigValue < 0) {
return defaultValue;
}
return Duration.ofMillis(deviceConfigValue);
}
}

View File

@@ -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();
}

View File

@@ -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
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
}

View File

@@ -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;
@@ -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}.
@@ -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.
@@ -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();
@@ -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() {
@@ -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();
@@ -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);
}
@@ -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;
}
@@ -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) {
@@ -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);
}
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2021 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.timezonedetector;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.os.SystemProperties;
import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.timedetector.ServerFlags;
import java.time.Duration;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* A singleton that provides access to service configuration for time zone detection. This hides how
* configuration is split between static, compile-time config and dynamic, server-pushed flags. It
* provides a rudimentary mechanism to signal when values have changed.
*/
public final class ServiceConfigAccessor {
private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
new ArraySet<>(new String[] {
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE,
ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE,
ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
}));
// TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1);
// TODO(b/179488561): Put this back to 1 minute when primary provider is fully implemented
private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
Duration.ofSeconds(20);
private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
private static final Object SLOCK = new Object();
/** The singleton instance. Initialized once in {@link #getInstance(Context)}. */
@GuardedBy("SLOCK")
@Nullable
private static ServiceConfigAccessor sInstance;
@NonNull private final Context mContext;
/**
* An ultimate "feature switch" for location-based time zone detection. If this is
* {@code false}, the device cannot support the feature without a config change or a reboot:
* This affects what services are started on boot to minimize expense when the feature is not
* wanted.
*/
private final boolean mGeoDetectionFeatureSupportedInConfig;
@NonNull private final ServerFlags mServerFlags;
private ServiceConfigAccessor(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
// The config value is expected to be the main feature flag. Platform developers can also
// force enable the feature using a persistent system property. Because system properties
// can change, this value is cached and only changes on reboot.
mGeoDetectionFeatureSupportedInConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection)
|| SystemProperties.getBoolean(
"persist.sys.location_time_zone_detection_feature_supported", false);
mServerFlags = ServerFlags.getInstance(mContext);
}
/** Returns the singleton instance. */
public static ServiceConfigAccessor getInstance(Context context) {
synchronized (SLOCK) {
if (sInstance == null) {
sInstance = new ServiceConfigAccessor(context);
}
return sInstance;
}
}
/**
* Adds a listener that will be called server flags related to this class change. The callbacks
* are delivered on the main looper thread.
*
* <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
* method.
*/
public void addListener(@NonNull ConfigurationChangeListener listener) {
mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH);
}
/** Returns {@code true} if any form of automatic time zone detection is supported. */
public boolean isAutoDetectionFeatureSupported() {
return deviceHasTelephonyNetwork() || isGeoTimeZoneDetectionFeatureSupported();
}
private boolean deviceHasTelephonyNetwork() {
// TODO b/150583524 Avoid the use of a deprecated API.
return mContext.getSystemService(ConnectivityManager.class)
.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
/**
* Returns {@code true} if the location-based time zone detection feature can be supported on
* this device at all according to config. When {@code false}, implies that various other
* location-based settings will be turned off or rendered meaningless. Typically {@link
* #isGeoTimeZoneDetectionFeatureSupported()} should be used instead.
*/
public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() {
return mGeoDetectionFeatureSupportedInConfig;
}
/**
* Returns {@code true} if the location-based time zone detection feature is supported on the
* device. This can be used during feature testing on builds that are capable of location time
* zone detection to enable / disable the feature for some users.
*/
public boolean isGeoTimeZoneDetectionFeatureSupported() {
return mGeoDetectionFeatureSupportedInConfig
&& isGeoTimeZoneDetectionFeatureSupportedInternal();
}
private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() {
final boolean defaultEnabled = true;
return mServerFlags.getBoolean(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
defaultEnabled);
}
/**
* Returns {@code true} if the primary location time zone provider can be used.
*/
public boolean isPrimaryLocationTimeZoneProviderEnabled() {
return getPrimaryLocationTimeZoneProviderEnabledOverride()
.orElse(isPrimaryLocationTimeZoneProviderEnabledInConfig());
}
private boolean isPrimaryLocationTimeZoneProviderEnabledInConfig() {
int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider;
return getConfigBoolean(providerEnabledConfigId);
}
@NonNull
private Optional<Boolean> getPrimaryLocationTimeZoneProviderEnabledOverride() {
return mServerFlags.getOptionalBoolean(
ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE);
}
/**
* Returns {@code true} if the secondary location time zone provider can be used.
*/
public boolean isSecondaryLocationTimeZoneProviderEnabled() {
return getSecondaryLocationTimeZoneProviderEnabledOverride()
.orElse(isSecondaryLocationTimeZoneProviderEnabledInConfig());
}
private boolean isSecondaryLocationTimeZoneProviderEnabledInConfig() {
int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider;
return getConfigBoolean(providerEnabledConfigId);
}
@NonNull
private Optional<Boolean> getSecondaryLocationTimeZoneProviderEnabledOverride() {
return mServerFlags.getOptionalBoolean(
ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE);
}
/**
* Returns whether location time zone detection is enabled for users when there's no setting
* value. Intended for use during feature release testing to "opt-in" users that haven't shown
* an explicit preference.
*/
public boolean isGeoDetectionEnabledForUsersByDefault() {
return mServerFlags.getBoolean(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, false);
}
/**
* Returns whether location time zone detection is force enabled/disabled for users. Intended
* for use during feature release testing to force a given state.
*/
@NonNull
public Optional<Boolean> getGeoDetectionSettingEnabledOverride() {
return mServerFlags.getOptionalBoolean(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE);
}
/**
* Returns the time to send to a location time zone provider that informs it how long it has
* to return its first time zone suggestion.
*/
@NonNull
public Duration getLocationTimeZoneProviderInitializationTimeout() {
return mServerFlags.getDurationFromMillis(
ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT);
}
/**
* Returns the time added to {@link #getLocationTimeZoneProviderInitializationTimeout()} by the
* server before unilaterally declaring the provider is uncertain.
*/
@NonNull
public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() {
return mServerFlags.getDurationFromMillis(
ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
}
/**
* Returns the time after uncertainty is detected by providers before the location time zone
* manager makes a suggestion to the time zone detector.
*/
@NonNull
public Duration getLocationTimeZoneUncertaintyDelay() {
return mServerFlags.getDurationFromMillis(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
}
private boolean getConfigBoolean(int providerEnabledConfigId) {
Resources resources = mContext.getResources();
return resources.getBoolean(providerEnabledConfigId);
}
}

View File

@@ -27,16 +27,21 @@ import android.annotation.NonNull;
*/
public interface TimeZoneDetectorInternal extends Dumpable.Container {
/** Adds a listener that will be invoked when time zone detection configuration is changed. */
void addConfigurationListener(ConfigurationChangeListener listener);
/** Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. */
void addConfigurationListener(@NonNull ConfigurationChangeListener listener);
/**
* Removes a listener previously added via {@link
* #addConfigurationListener(ConfigurationChangeListener)}.
*/
void removeConfigurationListener(ConfigurationChangeListener listener);
void removeConfigurationListener(@NonNull ConfigurationChangeListener listener);
/** Returns the {@link ConfigurationInternal} for the current user. */
/**
* Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
* snapshot so callers must use {@link #addConfigurationListener(ConfigurationChangeListener)}
* to be notified when it changes.
*/
@NonNull
ConfigurationInternal getCurrentUserConfigurationInternal();
/**

View File

@@ -33,7 +33,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -61,27 +60,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
static final String TAG = "time_zone_detector";
/**
* A "feature switch" for location-based time zone detection. If this is {@code false}. It is
* initialized and never refreshed; it affects what services are started on boot so consistency
* is important.
*/
@Nullable
private static Boolean sGeoLocationTimeZoneDetectionSupported;
/** Returns {@code true} if the location-based time zone detection feature is enabled. */
public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) {
if (sGeoLocationTimeZoneDetectionSupported == null) {
// The config value is expected to be the main switch. Platform developers can also
// enable the feature using a persistent system property.
sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection)
|| SystemProperties.getBoolean(
"persist.sys.location_time_zone_detection_feature_enabled", false);
}
return sGeoLocationTimeZoneDetectionSupported;
}
/**
* Handles the service lifecycle for {@link TimeZoneDetectorService} and
* {@link TimeZoneDetectorInternalImpl}.
@@ -98,11 +76,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
Context context = getContext();
Handler handler = FgThread.getHandler();
boolean geolocationTimeZoneDetectionSupported =
isGeoLocationTimeZoneDetectionSupported(context);
ServiceConfigAccessor serviceConfigAccessor =
ServiceConfigAccessor.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
TimeZoneDetectorStrategyImpl.create(
context, handler, geolocationTimeZoneDetectionSupported);
TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
// Create and publish the local service for use by internal callers.
TimeZoneDetectorInternal internal =
@@ -330,7 +307,8 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
boolean isGeoTimeZoneDetectionSupported() {
enforceManageTimeZoneDetectorPermission();
return isGeoLocationTimeZoneDetectionSupported(mContext);
return ServiceConfigAccessor.getInstance(mContext)
.isGeoTimeZoneDetectionFeatureSupported();
}
@Override

View File

@@ -75,17 +75,21 @@ import android.util.IndentingPrintWriter;
public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
/**
* Sets a listener that will be triggered whenever time zone detection configuration is
* Adds a listener that will be triggered whenever {@link ConfigurationInternal} may have
* changed.
*/
void addConfigChangeListener(@NonNull ConfigurationChangeListener listener);
/** Returns the user's time zone configuration. */
/**
* Returns a snapshot of the configuration that controls time zone detector behavior for the
* specified user.
*/
@NonNull
ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
/**
* Returns the configuration that controls time zone detector behavior for the current user.
* Returns a snapshot of the configuration that controls time zone detector behavior for the
* current user.
*/
@NonNull
ConfigurationInternal getCurrentUserConfigurationInternal();

View File

@@ -38,7 +38,6 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timedetector.DeviceConfig;
import java.util.ArrayList;
import java.util.List;
@@ -204,11 +203,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
*/
public static TimeZoneDetectorStrategyImpl create(
@NonNull Context context, @NonNull Handler handler,
boolean geoDetectionSupported) {
@NonNull ServiceConfigAccessor serviceConfigAccessor) {
DeviceConfig deviceConfig = new DeviceConfig();
EnvironmentImpl environment = new EnvironmentImpl(
context, handler, deviceConfig, geoDetectionSupported);
Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
return new TimeZoneDetectorStrategyImpl(environment);
}

View File

@@ -19,9 +19,9 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import com.android.server.LocalServices;
import com.android.server.timedetector.DeviceConfig;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.ServiceConfigAccessor;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
import java.time.Duration;
@@ -33,28 +33,19 @@ import java.util.Objects;
*/
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
// TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1);
// TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
Duration.ofSeconds(20);
private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
@NonNull private final LocationTimeZoneProviderController mController;
@NonNull private final DeviceConfig mDeviceConfig;
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final ConfigurationChangeListener mConfigurationChangeListener;
ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
@NonNull DeviceConfig deviceConfig,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull LocationTimeZoneProviderController controller) {
super(threadingDomain);
mController = Objects.requireNonNull(controller);
mDeviceConfig = Objects.requireNonNull(deviceConfig);
mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);
// Listen for configuration changes.
mConfigurationChangeListener = () -> mThreadingDomain.post(mController::onConfigChanged);
mConfigurationChangeListener = () -> mThreadingDomain.post(controller::onConfigChanged);
mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener);
}
@@ -73,24 +64,18 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
@Override
@NonNull
Duration getProviderInitializationTimeout() {
return mDeviceConfig.getDurationFromMillis(
DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT);
return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeout();
}
@Override
@NonNull
Duration getProviderInitializationTimeoutFuzz() {
return mDeviceConfig.getDurationFromMillis(
DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeoutFuzz();
}
@Override
@NonNull
Duration getUncertaintyDelay() {
return mDeviceConfig.getDurationFromMillis(
DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay();
}
}

View File

@@ -21,11 +21,11 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI
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.SERVICE_NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -43,9 +43,8 @@ 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.timedetector.DeviceConfig;
import com.android.server.timezonedetector.ServiceConfigAccessor;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
import com.android.server.timezonedetector.TimeZoneDetectorService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -96,28 +95,31 @@ public class LocationTimeZoneManagerService extends Binder {
private LocationTimeZoneManagerService mService;
@NonNull
private final ServiceConfigAccessor mServerConfigAccessor;
public Lifecycle(@NonNull Context context) {
super(Objects.requireNonNull(context));
mServerConfigAccessor = ServiceConfigAccessor.getInstance(context);
}
@Override
public void onStart() {
Context context = getContext();
if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) {
mService = new LocationTimeZoneManagerService(context);
// The service currently exposes no LocalService or Binder API, but it extends
// Binder and is registered as a binder service so it can receive shell commands.
publishBinderService("location_time_zone_manager", mService);
publishBinderService(SERVICE_NAME, mService);
} else {
Slog.i(TAG, getClass() + " is disabled");
Slog.d(TAG, "Geo time zone detection feature is disabled in config");
}
}
@Override
public void onBootPhase(int phase) {
Context context = getContext();
if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
public void onBootPhase(@BootPhase int phase) {
if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// The location service must be functioning after this boot phase.
mService.onSystemReady();
@@ -159,6 +161,9 @@ public class LocationTimeZoneManagerService extends Binder {
/** The shared lock from {@link #mThreadingDomain}. */
@NonNull private final Object mSharedLock;
@NonNull
private final ServiceConfigAccessor mServiceConfigAccessor;
// Lazily initialized. Can be null if the service has been stopped.
@GuardedBy("mSharedLock")
private ControllerImpl mLocationTimeZoneDetectorController;
@@ -180,24 +185,38 @@ public class LocationTimeZoneManagerService extends Binder {
mHandler = FgThread.getHandler();
mThreadingDomain = new HandlerThreadingDomain(mHandler);
mSharedLock = mThreadingDomain.getLockObject();
mServiceConfigAccessor = ServiceConfigAccessor.getInstance(mContext);
}
// According to the SystemService docs: All lifecycle methods are called from the system
// server's main looper thread.
void onSystemReady() {
// Called on an arbitrary thread during initialization.
synchronized (mSharedLock) {
// TODO(b/152744911): LocationManagerService watches for packages disappearing. Need to
// do anything here?
mServiceConfigAccessor.addListener(this::handleServiceConfigurationChangedOnMainThread);
}
// TODO(b/152744911): LocationManagerService watches for foreground app changes. Need to
// do anything here?
// TODO(b/152744911): LocationManagerService watches screen state. Need to do anything
// here?
private void handleServiceConfigurationChangedOnMainThread() {
// This method is called on the main thread, but service logic takes place on the threading
// domain thread, so we post the work there.
// The way all service-level configuration changes are handled is to just restart this
// service - this is simple and effective, and service configuration changes should be rare.
mThreadingDomain.post(this::restartIfRequiredOnDomainThread);
}
private void restartIfRequiredOnDomainThread() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
// Stop and start the service, waiting until completion.
stopOnDomainThread();
startOnDomainThread();
}
}
// According to the SystemService docs: All lifecycle methods are called from the system
// server's main looper thread.
void onSystemThirdPartyAppsCanStart() {
// Called on an arbitrary thread during initialization. We do not want to wait for
// completion as it would delay boot.
// Do not wait for completion as it would delay boot.
final boolean waitForCompletion = false;
startInternal(waitForCompletion);
}
@@ -205,6 +224,9 @@ public class LocationTimeZoneManagerService extends Binder {
/**
* Starts the service during server initialization or during tests after a call to
* {@link #stop()}.
*
* <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for
* completion, it cannot be called from the {@code mThreadingDomain} thread.
*/
void start() {
enforceManageTimeZoneDetectorPermission();
@@ -214,28 +236,17 @@ public class LocationTimeZoneManagerService extends Binder {
}
/**
* Starts the service during server initialization or during tests after a call to
* {@link #stop()}.
* Starts the service during server initialization, if the configuration changes or during tests
* after a call to {@link #stop()}.
*
* <p>To avoid tests needing to sleep, when {@code waitForCompletion} is {@code true}, this
* method will not return until all the system server components have started.
*
* <p>Because this method posts work to the {@code mThreadingDomain} thread, it cannot be
* called from the {@code mThreadingDomain} thread when {@code waitForCompletion} is true.
*/
private void startInternal(boolean waitForCompletion) {
Runnable runnable = () -> {
synchronized (mSharedLock) {
if (mLocationTimeZoneDetectorController == null) {
LocationTimeZoneProvider primary = createPrimaryProvider();
LocationTimeZoneProvider secondary = createSecondaryProvider();
mLocationTimeZoneDetectorController =
new ControllerImpl(mThreadingDomain, primary, secondary);
DeviceConfig deviceConfig = new DeviceConfig();
mEnvironment = new ControllerEnvironmentImpl(
mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController);
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
mLocationTimeZoneDetectorController.initialize(mEnvironment, callback);
}
}
};
Runnable runnable = this::startOnDomainThread;
if (waitForCompletion) {
mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS);
} else {
@@ -243,11 +254,38 @@ public class LocationTimeZoneManagerService extends Binder {
}
}
private void startOnDomainThread() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
if (!mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) {
debugLog("Not starting " + SERVICE_NAME + ": it is disabled in service config");
return;
}
if (mLocationTimeZoneDetectorController == null) {
LocationTimeZoneProvider primary = createPrimaryProvider();
LocationTimeZoneProvider secondary = createSecondaryProvider();
ControllerImpl controller =
new ControllerImpl(mThreadingDomain, primary, secondary);
ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
mThreadingDomain, mServiceConfigAccessor, controller);
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
controller.initialize(environment, callback);
mEnvironment = environment;
mLocationTimeZoneDetectorController = controller;
}
}
}
@NonNull
private LocationTimeZoneProvider createPrimaryProvider() {
LocationTimeZoneProviderProxy proxy;
if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) {
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) {
} else if (!isProviderEnabled(PRIMARY_PROVIDER_NAME)) {
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
proxy = new RealLocationTimeZoneProviderProxy(
@@ -262,11 +300,12 @@ public class LocationTimeZoneManagerService extends Binder {
return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy);
}
@NonNull
private LocationTimeZoneProvider createSecondaryProvider() {
LocationTimeZoneProviderProxy proxy;
if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) {
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) {
} else if (!isProviderEnabled(SECONDARY_PROVIDER_NAME)) {
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
proxy = new RealLocationTimeZoneProviderProxy(
@@ -282,33 +321,27 @@ public class LocationTimeZoneManagerService extends Binder {
}
/** Used for bug triage and in tests to simulate provider events. */
private boolean isProviderInSimulationMode(String providerName) {
private boolean isProviderInSimulationMode(@NonNull String providerName) {
return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED);
}
/** Used for bug triage, tests and experiments to remove a provider. */
private boolean isProviderDisabled(String providerName) {
return !isProviderEnabledInConfig(providerName)
|| isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED);
}
/** Used for bug triage, and by tests and experiments to remove a provider. */
private boolean isProviderEnabled(@NonNull String providerName) {
if (isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED)) {
return false;
}
private boolean isProviderEnabledInConfig(String providerName) {
int providerEnabledConfigId;
switch (providerName) {
case PRIMARY_PROVIDER_NAME: {
providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider;
break;
return mServiceConfigAccessor.isPrimaryLocationTimeZoneProviderEnabled();
}
case SECONDARY_PROVIDER_NAME: {
providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider;
break;
return mServiceConfigAccessor.isSecondaryLocationTimeZoneProviderEnabled();
}
default: {
throw new IllegalArgumentException(providerName);
}
}
Resources resources = mContext.getResources();
return resources.getBoolean(providerEnabledConfigId);
}
private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) {
@@ -326,22 +359,29 @@ public class LocationTimeZoneManagerService extends Binder {
}
/**
* Stops the service for tests. To avoid tests needing to sleep, this method will not return
* until all the system server components have stopped.
* Stops the service for tests and other rare cases. To avoid tests needing to sleep, this
* method will not return until all the system server components have stopped.
*
* <p>Because this method posts work to the {@code mThreadingDomain} thread and waits it cannot
* be called from the {@code mThreadingDomain} thread.
*/
void stop() {
enforceManageTimeZoneDetectorPermission();
mThreadingDomain.postAndWait(() -> {
synchronized (mSharedLock) {
if (mLocationTimeZoneDetectorController != null) {
mLocationTimeZoneDetectorController.destroy();
mLocationTimeZoneDetectorController = null;
mEnvironment.destroy();
mEnvironment = null;
}
mThreadingDomain.postAndWait(this::stopOnDomainThread, BLOCKING_OP_WAIT_DURATION_MILLIS);
}
private void stopOnDomainThread() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
if (mLocationTimeZoneDetectorController != null) {
mLocationTimeZoneDetectorController.destroy();
mLocationTimeZoneDetectorController = null;
mEnvironment.destroy();
mEnvironment = null;
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
}
}
@Override

View File

@@ -21,6 +21,7 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI
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.SERVICE_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;
@@ -102,7 +103,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:");
pw.printf("Location Time Zone Manager (%s) commands for tests:\n", SERVICE_NAME);
pw.println(" help");
pw.println(" Print this help text.");
pw.printf(" %s\n", SHELL_COMMAND_START);

View File

@@ -63,8 +63,6 @@ final class TimeZoneProviderRequest {
return mSendUpdates;
}
// TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
// be passed to the TimeZoneProviderService and remove if it is not useful.
/**
* Returns the maximum time that the provider is allowed to initialize before it is expected to
* send an event of any sort. Only valid when {@link #sendUpdates()} is {@code true}. Failure to

View File

@@ -46,8 +46,8 @@ public class ConfigurationInternalTest {
public void test_unrestricted() {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -108,8 +108,8 @@ public class ConfigurationInternalTest {
public void test_restricted() {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -170,8 +170,8 @@ public class ConfigurationInternalTest {
public void test_autoDetectNotSupported() {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(false)
.setGeoDetectionSupported(false)
.setAutoDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -232,8 +232,8 @@ public class ConfigurationInternalTest {
public void test_geoDetectNotSupported() {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(false)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)

View File

@@ -364,8 +364,8 @@ public class TimeZoneDetectorServiceTest {
// the tests.
final boolean geoDetectionEnabled = autoDetectionEnabled;
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(autoDetectionEnabled)
.setLocationEnabled(geoDetectionEnabled)

View File

@@ -90,8 +90,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
.setGeoDetectionEnabled(false)
@@ -100,8 +100,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -110,8 +110,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(false)
.setGeoDetectionSupported(false)
.setAutoDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
.setGeoDetectionEnabled(false)
@@ -120,8 +120,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(false)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -130,8 +130,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
.setGeoDetectionEnabled(false)
@@ -139,8 +139,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
@@ -149,8 +149,8 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)

View File

@@ -44,8 +44,8 @@ final class TestSupport {
@UserIdInt int userId, boolean geoDetectionEnabled) {
return new ConfigurationInternal.Builder(userId)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setGeoDetectionSupported(true)
.setAutoDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(geoDetectionEnabled)