Improve server flags / add more server control

Add more server flags that can be pushed to devices to influence geo
detection service behavior.

This renames DeviceConfig as ServerFlags and introduces an intermediary,
the ServiceConfigAccessor, to hide details and handle changes to config
values that need the location_time_zone_manager to restart.

Simulating server flag changes example:
adb shell cmd device_config put \
    system_time primary_location_time_zone_provider_enabled_override false

Inspecting manager / provider states:
adb shell dump location_time_zone_manager

The following manual tests were conducted to check the plumbing:

Modifying settings behavior:
adb shell cmd device_config put \
    system_time location_time_zone_detection_setting_enabled_override true
<Confirm the settings value cannot be changed by user>

adb shell cmd device_config put \
    system_time location_time_zone_detection_setting_enabled_override false
<Confirm the settings value can be changed by user>

adb shell cmd device_config delete \
    system_time location_time_zone_detection_setting_enabled_override
<Confirm it returns to normal>

Disabling the feature / location_time_zone_manager:
adb shell cmd device_config put \
    system_time location_time_zone_detection_feature_supported false
<Confirm SettingsUI (after restart) doesn't show the geo detection option>
<Confirm manager is not started>

Enabling the feature / location_time_zone_manager:
adb shell cmd device_config put \
    system_time location_time_zone_detection_feature_supported true
<Confirm manager is restarted>

Note: "cmd device_config" doesn't allow multiple config keys to be
changed at once, and any change to service config keys cause the
location_time_zone_manager to restart. When changing multiple, set
"location_time_zone_detection_feature_supported false" to stop the
manager, and "location_time_zone_detection_feature_supported true" when
all changes are done.

Test: Manual testing (see above)
Test: treehugger
Bug: 178107773
Change-Id: I07b57add1ed47197587907b9f0a85f38acc0a76a
This commit is contained in:
Neil Fuller
2021-02-05 16:57:16 +00:00
parent 35c53e2a73
commit 2856d9e52f
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)