Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9250a5d0 authored by Neil Fuller's avatar Neil Fuller Committed by Automerger Merge Worker
Browse files

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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13690805

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ic66d5864573fa4f3cde8e8747eeedd31d1e2e7a6
parents 11d20fff fcfe1f1c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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).
+238 −0
Original line number Diff line number Diff line
@@ -20,49 +20,74 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.content.Context;
import android.provider.DeviceConfig;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ServiceConfigAccessor;

import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

/**
 * A helper class for reading / monitoring the {@link
 * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags.
 * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace
 * for server-configured flags.
 */
public final class DeviceConfig {
public final class ServerFlags {

    private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true);
    private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false);

    /**
     * An annotation used to indicate when a {@link
     * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required.
     * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is
     * required.
     *
     * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider
     * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the
     * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the
     * prefix "geotz_" on all of its key strings.
     */
    @StringDef(prefix = "KEY_", value = {
            KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED,
            KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT,
            KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
            KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
            KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE,
            KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE,
            KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
            KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
            KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
            KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
            KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
    })
    @interface DeviceConfigKey {}

    /**
     * The key to force location time zone detection on for a device. Only intended for use during
     * release testing with droidfooders. The user can still disable the feature by turning off the
     * master location switch, or disabling automatic time zone detection.
     * Controls whether the location time zone manager service will started. Only observed if
     * the device build is configured to support location-based time zone detection. See
     * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link
     * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}.
     */
    @DeviceConfigKey
    public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED =
            "force_location_time_zone_detection_enabled";
    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED =
            "location_time_zone_detection_feature_supported";

    /**
     * The key for the default value used to determine whether location time zone detection is
     * enabled when the user hasn't explicitly set it yet.
     * The key for the server flag that can override the device config for whether the primary
     * location time zone provider is enabled or disabled.
     */
    @DeviceConfigKey
    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT =
            "location_time_zone_detection_enabled_default";
    public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE =
            "primary_location_time_zone_provider_enabled_override";

    /**
     * The key for the server flag that can override the device config for whether the secondary
     * location time zone provider is enabled or disabled.
     */
    @DeviceConfigKey
    public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE =
            "secondary_location_time_zone_provider_enabled_override";

    /**
     * The key for the minimum delay after location time zone detection has been enabled before the
@@ -89,35 +114,122 @@ public final class DeviceConfig {
    public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
            "ltpz_init_timeout_fuzz_millis";

    /** Creates an instance. */
    public DeviceConfig() {}
    /**
     * The key for the server flag that can override location time zone detection being enabled for
     * a user. Only intended for use during release testing with droidfooders. The user can still
     * disable the feature by turning off the master location switch, or by disabling automatic time
     * zone detection.
     */
    @DeviceConfigKey
    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE =
            "location_time_zone_detection_setting_enabled_override";

    /** Adds a listener for the system_time namespace. */
    public void addListener(
            @NonNull Executor handlerExecutor, @NonNull Runnable listener) {
        android.provider.DeviceConfig.addOnPropertiesChangedListener(
    /**
     * The key for the default value used to determine whether location time zone detection is
     * enabled when the user hasn't explicitly set it yet.
     */
    @DeviceConfigKey
    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT =
            "location_time_zone_detection_setting_enabled_default";

    @GuardedBy("mListeners")
    private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();

    private static final Object SLOCK = new Object();

    @GuardedBy("SLOCK")
    @Nullable
    private static ServerFlags sInstance;

    private ServerFlags(Context context) {
        DeviceConfig.addOnPropertiesChangedListener(
                NAMESPACE_SYSTEM_TIME,
                handlerExecutor,
                properties -> listener.run());
                context.getMainExecutor(),
                this::handlePropertiesChanged);
    }

    /** Returns the singleton instance. */
    public static ServerFlags getInstance(Context context) {
        synchronized (SLOCK) {
            if (sInstance == null) {
                sInstance = new ServerFlags(context);
            }
            return sInstance;
        }
    }

    private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
        synchronized (mListeners) {
            for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry
                    : mListeners.entrySet()) {
                if (intersects(listenerEntry.getValue(), properties.getKeyset())) {
                    listenerEntry.getKey().onChange();
                }
            }
        }
    }

    private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) {
        for (String toFind : one) {
            if (two.contains(toFind)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adds a listener for the system_time namespace that will trigger if any of the specified keys
     * change. Listener callbacks are delivered on the main looper thread.
     *
     * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
     * associated remove method.
     */
    public void addListener(@NonNull ConfigurationChangeListener listener,
            @NonNull Set<String> keys) {
        Objects.requireNonNull(listener);
        Objects.requireNonNull(keys);

        synchronized (mListeners) {
            mListeners.put(listener, keys);
        }
    }

    /**
     * Returns an optional boolean value from {@link DeviceConfig} from the system_time
     * namespace, returns {@link Optional#empty()} if there is no explicit value set.
     */
    @NonNull
    public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) {
        String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key);
        return parseOptionalBoolean(value);
    }

    @NonNull
    private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) {
        if (value == null) {
            return Optional.empty();
        } else {
            return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE;
        }
    }

    /**
     * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time
     * Returns a boolean value from {@link DeviceConfig} from the system_time
     * namespace, or {@code defaultValue} if there is no explicit value set.
     */
    public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) {
        return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue);
        return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue);
    }

    /**
     * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time
     * Returns a positive duration from {@link DeviceConfig} from the system_time
     * namespace, or {@code defaultValue} if there is no explicit value set.
     */
    @Nullable
    public Duration getDurationFromMillis(
            @DeviceConfigKey String key, @Nullable Duration defaultValue) {
        long deviceConfigValue =
                android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1);
        long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1);
        if (deviceConfigValue < 0) {
            return defaultValue;
        }
+3 −2
Original line number Diff line number Diff line
@@ -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();
}
+26 −25
Original line number Diff line number Diff line
@@ -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;
        }
+28 −55
Original line number Diff line number Diff line
@@ -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);
    }
}
Loading