diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index fc6e372b80414..ec620b5e2a426 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -16,126 +16,44 @@ package com.android.server.timezonedetector; -import static android.content.Intent.ACTION_USER_SWITCHED; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.ActivityManagerInternal; import android.app.AlarmManager; -import android.app.time.TimeZoneConfiguration; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.ContentObserver; -import android.location.LocationManager; import android.os.Handler; import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.LocalServices; import java.util.Objects; -import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. */ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment { - private static final String LOG_TAG = TimeZoneDetectorService.TAG; private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @NonNull private final Context mContext; @NonNull private final Handler mHandler; - @NonNull private final ContentResolver mCr; - @NonNull private final UserManager mUserManager; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; - @NonNull private final LocationManager mLocationManager; - - // @NonNull after setConfigChangeListener() is called. - @GuardedBy("this") - private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); - mCr = context.getContentResolver(); - mUserManager = context.getSystemService(UserManager.class); - mLocationManager = context.getSystemService(LocationManager.class); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); - - // Wire up the config change listeners for anything that could affect the return values from - // this object. All listener invocations are performed on the mHandler thread. - - // Listen for the user changing / the user's location mode changing. - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_USER_SWITCHED); - filter.addAction(LocationManager.MODE_CHANGED_ACTION); - mContext.registerReceiverForAllUsers(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - handleConfigChangeOnHandlerThread(); - } - }, filter, null, mHandler); - - // Add async callbacks for global settings being changed. - ContentResolver contentResolver = mContext.getContentResolver(); - ContentObserver contentObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - handleConfigChangeOnHandlerThread(); - } - }; - contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, contentObserver); - - // Add async callbacks for user scoped location settings being changed. - contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED), - true, contentObserver, UserHandle.USER_ALL); - } - - private void handleConfigChangeOnHandlerThread() { - synchronized (this) { - if (mConfigChangeListener == null) { - Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null"); - } - mConfigChangeListener.onChange(); - } } @Override - public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) { - synchronized (this) { - mConfigChangeListener = Objects.requireNonNull(listener); - } + public void setConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + ConfigurationChangeListener configurationChangeListener = + () -> mHandler.post(listener::onChange); + mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener); } @Override - public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { - return new ConfigurationInternal.Builder(userId) - .setTelephonyDetectionFeatureSupported( - mServiceConfigAccessor.isTelephonyTimeZoneDetectionFeatureSupported()) - .setGeoDetectionFeatureSupported( - mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) - .setAutoDetectionEnabled(isAutoDetectionEnabled()) - .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setLocationEnabled(isLocationEnabled(userId)) - .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) - .build(); - } - - @Override - public @UserIdInt int getCurrentUserId() { - return LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId(); + public ConfigurationInternal getCurrentUserConfigurationInternal() { + return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @Override @@ -161,78 +79,4 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); alarmManager.setTimeZone(zoneId); } - - @Override - public void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration) { - Objects.requireNonNull(configuration); - - // Avoid writing the auto detection enabled setting for devices that do not support auto - // 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 (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { - final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); - setAutoDetectionEnabledIfRequired(autoDetectionEnabled); - - // 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); - } - } - } - - private boolean isUserConfigAllowed(@UserIdInt int userId) { - UserHandle userHandle = UserHandle.of(userId); - return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); - } - - private boolean isAutoDetectionEnabled() { - return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; - } - - private void setAutoDetectionEnabledIfRequired(boolean enabled) { - // This check is racey, but the whole settings update process is racey. This check prevents - // a ConfigurationChangeListener callback triggering due to ContentObserver's still - // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary - // for stable behavior during tests. - if (isAutoDetectionEnabled() != enabled) { - Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0); - } - } - - private boolean isLocationEnabled(@UserIdInt int userId) { - return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId)); - } - - 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/off for testers (but only where their other settings would allow them to turn it on - // for themselves). - Optional override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); - if (override.isPresent()) { - return override.get(); - } - - final boolean geoDetectionEnabledByDefault = - mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); - return Settings.Secure.getIntForUser(mCr, - Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, - (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; - } - - private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { - // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 - if (isGeoDetectionEnabled(userId) != enabled) { - Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, - enabled ? 1 : 0, userId); - } - } } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index f7ac9b66bb02c..984b9baf0fc7a 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -18,165 +18,96 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.util.ArraySet; - -import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; -import com.android.server.timedetector.ServerFlags; +import android.annotation.UserIdInt; +import android.app.time.TimeZoneConfiguration; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; 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. + * An interface that provides access to service configuration for time zone detection. This hides + * how configuration is split between static, compile-time config, dynamic server-pushed flags and + * user settings. It provides listeners to signal when values that affect different components have + * changed. */ -public final class ServiceConfigAccessor { +public interface ServiceConfigAccessor { @StringDef(prefix = "PROVIDER_MODE_", - value = { PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED}) + value = { PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED }) @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) - @interface ProviderMode {} + @interface ProviderMode { + } /** * The "disabled" provider mode. For use with {@link #getPrimaryLocationTimeZoneProviderMode()} * and {@link #getSecondaryLocationTimeZoneProviderMode()}. */ - public static final @ProviderMode String PROVIDER_MODE_DISABLED = "disabled"; + @ProviderMode String PROVIDER_MODE_DISABLED = "disabled"; /** * The "enabled" provider mode. For use with {@link #getPrimaryLocationTimeZoneProviderMode()} * and {@link #getSecondaryLocationTimeZoneProviderMode()}. */ - public static final @ProviderMode String PROVIDER_MODE_ENABLED = "enabled"; + @ProviderMode String PROVIDER_MODE_ENABLED = "enabled"; /** - * Device config keys that can affect {@link - * com.android.server.timezonedetector.location.LocationTimeZoneManagerService} behavior. + * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. + * The listener is invoked on the main thread. */ - private static final Set LOCATION_TIME_ZONE_MANAGER_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_LTZP_MODE_OVERRIDE, - ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE, - ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, - ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, - ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS - })); - - private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); - private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); - private static final Duration DEFAULT_LTZP_UNCERTAINTY_DELAY = Duration.ofMinutes(5); - private static final Duration DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD = Duration.ofMinutes(1); - - 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; - - @NonNull private final ServerFlags mServerFlags; + void addConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener); /** - * The mode to use for the primary location time zone provider in a test. Setting this - * disables some permission checks. - * This state is volatile: it is never written to storage / never survives a reboot. This is to - * avoid a test provider accidentally being left configured on a device. - * See also {@link #resetVolatileTestConfig()}. + * Removes a listener previously added via {@link + * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}. */ - @Nullable - private String mTestPrimaryLocationTimeZoneProviderMode; + void removeConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener); /** - * The package name to use for the primary location time zone provider in a test. - * This state is volatile: it is never written to storage / never survives a reboot. This is to - * avoid a test provider accidentally being left configured on a device. - * See also {@link #resetVolatileTestConfig()}. + * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a + * snapshot so callers must use {@link + * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it + * changes. */ - @Nullable - private String mTestPrimaryLocationTimeZoneProviderPackageName; + @NonNull + ConfigurationInternal getCurrentUserConfigurationInternal(); /** - * See {@link #mTestPrimaryLocationTimeZoneProviderMode}; this is the equivalent for the - * secondary provider. + * Updates the configuration properties that control a device's time zone behavior. + * + *

This method returns {@code true} if the configuration was changed, + * {@code false} otherwise. */ - @Nullable - private String mTestSecondaryLocationTimeZoneProviderMode; + boolean updateConfiguration(@UserIdInt int userId, + @NonNull TimeZoneConfiguration requestedConfiguration); /** - * See {@link #mTestPrimaryLocationTimeZoneProviderPackageName}; this is the equivalent for the - * secondary provider. + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * specified user. */ - @Nullable - private String mTestSecondaryLocationTimeZoneProviderPackageName; - - /** - * Whether to record state changes for tests. - * This state is volatile: it is never written to storage / never survives a reboot. This is to - * avoid a test state accidentally being left configured on a device. - * See also {@link #resetVolatileTestConfig()}. - */ - private boolean mRecordProviderStateChanges; - - private ServiceConfigAccessor(@NonNull Context context) { - mContext = Objects.requireNonNull(context); - - 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; - } - } + @NonNull + ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** * Adds a listener that will be called when server flags related to location_time_zone_manager * change. The callbacks are delivered on the main looper thread. * - *

Note: Only for use by long-lived objects. There is deliberately no associated remove - * method. + *

Note: Currently only for use by long-lived objects; there is no associated remove method. */ - public void addLocationTimeZoneManagerConfigListener( - @NonNull ConfigurationChangeListener listener) { - mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH); - } - - /** Returns {@code true} if any form of automatic time zone detection is supported. */ - public boolean isAutoDetectionFeatureSupported() { - return isTelephonyTimeZoneDetectionFeatureSupported() - || isGeoTimeZoneDetectionFeatureSupported(); - } + void addLocationTimeZoneManagerConfigListener( + @NonNull ConfigurationChangeListener listener); /** * Returns {@code true} if the telephony-based time zone detection feature is supported on the * device. */ - public boolean isTelephonyTimeZoneDetectionFeatureSupported() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); - } + boolean isTelephonyTimeZoneDetectionFeatureSupported(); /** * Returns {@code true} if the location-based time zone detection feature can be supported on @@ -191,239 +122,116 @@ public final class ServiceConfigAccessor { * Typically {@link #isGeoTimeZoneDetectionFeatureSupported()} should be used except during * boot. */ - public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() { - return mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection); - } + boolean isGeoTimeZoneDetectionFeatureSupportedInConfig(); /** * Returns {@code true} if the location-based time zone detection feature is supported on the * device. */ - public boolean isGeoTimeZoneDetectionFeatureSupported() { - // For the feature to be enabled it must: - // 1) Be turned on in config. - // 2) Not be turned off via a server flag. - // 3) There must be at least one location time zone provider enabled / configured. - return isGeoTimeZoneDetectionFeatureSupportedInConfig() - && isGeoTimeZoneDetectionFeatureSupportedInternal() - && atLeastOneProviderIsEnabled(); - } - - private boolean atLeastOneProviderIsEnabled() { - return !(Objects.equals(getPrimaryLocationTimeZoneProviderMode(), PROVIDER_MODE_DISABLED) - && Objects.equals(getSecondaryLocationTimeZoneProviderMode(), - PROVIDER_MODE_DISABLED)); - } - - /** - * Returns {@code true} if the location-based time zone detection feature is not explicitly - * disabled by a server flag. - */ - private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() { - final boolean defaultEnabled = true; - return mServerFlags.getBoolean( - ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, - defaultEnabled); - } + boolean isGeoTimeZoneDetectionFeatureSupported(); /** Returns the package name of the app hosting the primary location time zone provider. */ @NonNull - public String getPrimaryLocationTimeZoneProviderPackageName() { - if (mTestPrimaryLocationTimeZoneProviderMode != null) { - // In test mode: use the test setting value. - return mTestPrimaryLocationTimeZoneProviderPackageName; - } - return mContext.getResources().getString( - R.string.config_primaryLocationTimeZoneProviderPackageName); - } + String getPrimaryLocationTimeZoneProviderPackageName(); /** * Sets the package name of the app hosting the primary location time zone provider for tests. * Setting a {@code null} value means the provider is to be disabled. * The values are reset with {@link #resetVolatileTestConfig()}. */ - public void setTestPrimaryLocationTimeZoneProviderPackageName( - @Nullable String testPrimaryLocationTimeZoneProviderPackageName) { - mTestPrimaryLocationTimeZoneProviderPackageName = - testPrimaryLocationTimeZoneProviderPackageName; - mTestPrimaryLocationTimeZoneProviderMode = - mTestPrimaryLocationTimeZoneProviderPackageName == null - ? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED; - } + void setTestPrimaryLocationTimeZoneProviderPackageName( + @Nullable String testPrimaryLocationTimeZoneProviderPackageName); /** * Returns {@code true} if the usual permission checks are to be bypassed for the primary * provider. Returns {@code true} only if {@link * #setTestPrimaryLocationTimeZoneProviderPackageName} has been called. */ - public boolean isTestPrimaryLocationTimeZoneProvider() { - return mTestPrimaryLocationTimeZoneProviderMode != null; - } + boolean isTestPrimaryLocationTimeZoneProvider(); /** Returns the package name of the app hosting the secondary location time zone provider. */ @NonNull - public String getSecondaryLocationTimeZoneProviderPackageName() { - if (mTestSecondaryLocationTimeZoneProviderMode != null) { - // In test mode: use the test setting value. - return mTestSecondaryLocationTimeZoneProviderPackageName; - } - return mContext.getResources().getString( - R.string.config_secondaryLocationTimeZoneProviderPackageName); - } + String getSecondaryLocationTimeZoneProviderPackageName(); /** * Sets the package name of the app hosting the secondary location time zone provider for tests. * Setting a {@code null} value means the provider is to be disabled. * The values are reset with {@link #resetVolatileTestConfig()}. */ - public void setTestSecondaryLocationTimeZoneProviderPackageName( - @Nullable String testSecondaryLocationTimeZoneProviderPackageName) { - mTestSecondaryLocationTimeZoneProviderPackageName = - testSecondaryLocationTimeZoneProviderPackageName; - mTestSecondaryLocationTimeZoneProviderMode = - mTestSecondaryLocationTimeZoneProviderPackageName == null - ? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED; - } + void setTestSecondaryLocationTimeZoneProviderPackageName( + @Nullable String testSecondaryLocationTimeZoneProviderPackageName); /** * Returns {@code true} if the usual permission checks are to be bypassed for the secondary * provider. Returns {@code true} only if {@link * #setTestSecondaryLocationTimeZoneProviderPackageName} has been called. */ - public boolean isTestSecondaryLocationTimeZoneProvider() { - return mTestSecondaryLocationTimeZoneProviderMode != null; - } + boolean isTestSecondaryLocationTimeZoneProvider(); /** * Enables/disables the state recording mode for tests. The value is reset with {@link * #resetVolatileTestConfig()}. */ - public void setRecordProviderStateChanges(boolean enabled) { - mRecordProviderStateChanges = enabled; - } + void setRecordProviderStateChanges(boolean enabled); /** * Returns {@code true} if providers are expected to record their state changes for tests. */ - public boolean getRecordProviderStateChanges() { - return mRecordProviderStateChanges; - } + boolean getRecordProviderStateChanges(); /** * Returns the mode for the primary location time zone provider. */ @NonNull - public @ProviderMode String getPrimaryLocationTimeZoneProviderMode() { - if (mTestPrimaryLocationTimeZoneProviderMode != null) { - // In test mode: use the test setting value. - return mTestPrimaryLocationTimeZoneProviderMode; - } - return mServerFlags.getOptionalString(ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE) - .orElse(getPrimaryLocationTimeZoneProviderModeFromConfig()); - } - - @NonNull - private @ProviderMode String getPrimaryLocationTimeZoneProviderModeFromConfig() { - int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; - return getConfigBoolean(providerEnabledConfigId) - ? PROVIDER_MODE_ENABLED : PROVIDER_MODE_DISABLED; - } + @ProviderMode String getPrimaryLocationTimeZoneProviderMode(); /** * Returns the mode for the secondary location time zone provider. */ - public @ProviderMode String getSecondaryLocationTimeZoneProviderMode() { - if (mTestSecondaryLocationTimeZoneProviderMode != null) { - // In test mode: use the test setting value. - return mTestSecondaryLocationTimeZoneProviderMode; - } - return mServerFlags.getOptionalString(ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE) - .orElse(getSecondaryLocationTimeZoneProviderModeFromConfig()); - } - - @NonNull - private @ProviderMode String getSecondaryLocationTimeZoneProviderModeFromConfig() { - int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; - return getConfigBoolean(providerEnabledConfigId) - ? PROVIDER_MODE_ENABLED : PROVIDER_MODE_DISABLED; - } + @ProviderMode String getSecondaryLocationTimeZoneProviderMode(); /** * 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); - } + boolean isGeoDetectionEnabledForUsersByDefault(); /** * 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 getGeoDetectionSettingEnabledOverride() { - return mServerFlags.getOptionalBoolean( - ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); - } + Optional getGeoDetectionSettingEnabledOverride(); /** * 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_LTZP_INITIALIZATION_TIMEOUT_MILLIS, - DEFAULT_LTZP_INITIALIZATION_TIMEOUT); - } + Duration getLocationTimeZoneProviderInitializationTimeout(); /** * 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_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ); - } + Duration getLocationTimeZoneProviderInitializationTimeoutFuzz(); /** * 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_LTZP_UNCERTAINTY_DELAY); - } + Duration getLocationTimeZoneUncertaintyDelay(); /** * Returns the time between equivalent events before the provider process will send the event * to the system server. */ @NonNull - public Duration getLocationTimeZoneProviderEventFilteringAgeThreshold() { - return mServerFlags.getDurationFromMillis( - ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, - DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD); - } + Duration getLocationTimeZoneProviderEventFilteringAgeThreshold(); /** Clears all in-memory test config. */ - public void resetVolatileTestConfig() { - mTestPrimaryLocationTimeZoneProviderPackageName = null; - mTestPrimaryLocationTimeZoneProviderMode = null; - mTestSecondaryLocationTimeZoneProviderPackageName = null; - mTestSecondaryLocationTimeZoneProviderMode = null; - mRecordProviderStateChanges = false; - } - - private boolean getConfigBoolean(int providerEnabledConfigId) { - Resources resources = mContext.getResources(); - return resources.getBoolean(providerEnabledConfigId); - } + void resetVolatileTestConfig(); } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java new file mode 100644 index 0000000000000..6e63f59cecb43 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java @@ -0,0 +1,556 @@ +/* + * 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 static android.content.Intent.ACTION_USER_SWITCHED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.location.LocationManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import com.android.server.timedetector.ServerFlags; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A singleton implementation of {@link ServiceConfigAccessor}. + */ +public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { + + /** + * Device config keys that can affect the content of {@link ConfigurationInternal}. + */ + private static final Set CONFIGURATION_INTERNAL_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, + })); + + /** + * Device config keys that can affect {@link + * com.android.server.timezonedetector.location.LocationTimeZoneManagerService} behavior. + */ + private static final Set LOCATION_TIME_ZONE_MANAGER_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_LTZP_MODE_OVERRIDE, + ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE, + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS + })); + + private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); + private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); + private static final Duration DEFAULT_LTZP_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + private static final Duration DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD = Duration.ofMinutes(1); + + 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; + @NonNull private final ServerFlags mServerFlags; + @NonNull private final ContentResolver mCr; + @NonNull private final UserManager mUserManager; + @NonNull private final LocationManager mLocationManager; + + @GuardedBy("this") + @NonNull private final List mConfigurationInternalListeners = + new ArrayList<>(); + + /** + * The mode to use for the primary location time zone provider in a test. Setting this + * disables some permission checks. + * This state is volatile: it is never written to storage / never survives a reboot. This is to + * avoid a test provider accidentally being left configured on a device. + * See also {@link #resetVolatileTestConfig()}. + */ + @GuardedBy("this") + @Nullable + private String mTestPrimaryLocationTimeZoneProviderMode; + + /** + * The package name to use for the primary location time zone provider in a test. + * This state is volatile: it is never written to storage / never survives a reboot. This is to + * avoid a test provider accidentally being left configured on a device. + * See also {@link #resetVolatileTestConfig()}. + */ + @GuardedBy("this") + @Nullable + private String mTestPrimaryLocationTimeZoneProviderPackageName; + + /** + * See {@link #mTestPrimaryLocationTimeZoneProviderMode}; this is the equivalent for the + * secondary provider. + */ + @GuardedBy("this") + @Nullable + private String mTestSecondaryLocationTimeZoneProviderMode; + + /** + * See {@link #mTestPrimaryLocationTimeZoneProviderPackageName}; this is the equivalent for the + * secondary provider. + */ + @GuardedBy("this") + @Nullable + private String mTestSecondaryLocationTimeZoneProviderPackageName; + + /** + * Whether to record state changes for tests. + * This state is volatile: it is never written to storage / never survives a reboot. This is to + * avoid a test state accidentally being left configured on a device. + * See also {@link #resetVolatileTestConfig()}. + */ + @GuardedBy("this") + private boolean mRecordProviderStateChanges; + + private ServiceConfigAccessorImpl(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + mCr = context.getContentResolver(); + mUserManager = context.getSystemService(UserManager.class); + mLocationManager = context.getSystemService(LocationManager.class); + mServerFlags = ServerFlags.getInstance(mContext); + + // Wire up the config change listeners for anything that could affect ConfigurationInternal. + // Use the main thread for event delivery, listeners can post to their chosen thread. + + // Listen for the user changing / the user's location mode changing. Report on the main + // thread. + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USER_SWITCHED); + filter.addAction(LocationManager.MODE_CHANGED_ACTION); + mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleConfigurationInternalChangeOnMainThread(); + } + }, filter, null, null /* main thread */); + + // Add async callbacks for global settings being changed. + ContentResolver contentResolver = mContext.getContentResolver(); + ContentObserver contentObserver = new ContentObserver(mContext.getMainThreadHandler()) { + @Override + public void onChange(boolean selfChange) { + handleConfigurationInternalChangeOnMainThread(); + } + }; + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, contentObserver); + + // Add async callbacks for user scoped location settings being changed. + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED), + true, contentObserver, UserHandle.USER_ALL); + + // Watch server flags. + mServerFlags.addListener(this::handleConfigurationInternalChangeOnMainThread, + CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH); + } + + /** Returns the singleton instance. */ + public static ServiceConfigAccessor getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServiceConfigAccessorImpl(context); + } + return sInstance; + } + } + + private synchronized void handleConfigurationInternalChangeOnMainThread() { + for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) { + changeListener.onChange(); + } + } + + @Override + public synchronized void addConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + mConfigurationInternalListeners.add(Objects.requireNonNull(listener)); + } + + @Override + public synchronized void removeConfigurationInternalChangeListener( + @NonNull ConfigurationChangeListener listener) { + mConfigurationInternalListeners.remove(Objects.requireNonNull(listener)); + } + + @Override + @NonNull + public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() { + int currentUserId = + LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId(); + return getConfigurationInternal(currentUserId); + } + + @Override + public synchronized boolean updateConfiguration(@UserIdInt int userId, + @NonNull TimeZoneConfiguration requestedConfiguration) { + Objects.requireNonNull(requestedConfiguration); + + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + getConfigurationInternal(userId).createCapabilitiesAndConfig(); + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration(); + + final TimeZoneConfiguration newConfiguration = + capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration); + if (newConfiguration == null) { + // The changes could not be made because the user's capabilities do not allow it. + return false; + } + + // Store the configuration / notify as needed. This will cause the mEnvironment to invoke + // handleConfigChanged() asynchronously. + storeConfiguration(userId, newConfiguration); + + return true; + } + + /** + * Stores the configuration properties contained in {@code newConfiguration}. + * All checks about user capabilities must be done by the caller and + * {@link TimeZoneConfiguration#isComplete()} must be {@code true}. + */ + @GuardedBy("this") + private void storeConfiguration(@UserIdInt int userId, + @NonNull TimeZoneConfiguration configuration) { + Objects.requireNonNull(configuration); + + // Avoid writing the auto detection enabled setting for devices that do not support auto + // 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 (isAutoDetectionFeatureSupported()) { + final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); + setAutoDetectionEnabledIfRequired(autoDetectionEnabled); + + // 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 (!getGeoDetectionSettingEnabledOverride().isPresent() + && isGeoTimeZoneDetectionFeatureSupported()) { + final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); + setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); + } + } + } + + @Override + @NonNull + public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { + return new ConfigurationInternal.Builder(userId) + .setTelephonyDetectionFeatureSupported( + isTelephonyTimeZoneDetectionFeatureSupported()) + .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported()) + .setAutoDetectionEnabled(isAutoDetectionEnabled()) + .setUserConfigAllowed(isUserConfigAllowed(userId)) + .setLocationEnabled(isLocationEnabled(userId)) + .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) + .build(); + } + + private void setAutoDetectionEnabledIfRequired(boolean enabled) { + // This check is racey, but the whole settings update process is racey. This check prevents + // a ConfigurationChangeListener callback triggering due to ContentObserver's still + // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary + // for stable behavior during tests. + if (isAutoDetectionEnabled() != enabled) { + Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0); + } + } + + private boolean isLocationEnabled(@UserIdInt int userId) { + return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId)); + } + + private boolean isUserConfigAllowed(@UserIdInt int userId) { + UserHandle userHandle = UserHandle.of(userId); + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); + } + + private boolean isAutoDetectionEnabled() { + return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; + } + + 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/off for testers (but only where their other settings would allow them to turn it on + // for themselves). + Optional override = getGeoDetectionSettingEnabledOverride(); + if (override.isPresent()) { + return override.get(); + } + + final boolean geoDetectionEnabledByDefault = isGeoDetectionEnabledForUsersByDefault(); + return Settings.Secure.getIntForUser(mCr, + Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, + (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; + } + + private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { + // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 + if (isGeoDetectionEnabled(userId) != enabled) { + Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, + enabled ? 1 : 0, userId); + } + } + + @Override + public void addLocationTimeZoneManagerConfigListener( + @NonNull ConfigurationChangeListener listener) { + mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH); + } + + /** Returns {@code true} if any form of automatic time zone detection is supported. */ + private boolean isAutoDetectionFeatureSupported() { + return isTelephonyTimeZoneDetectionFeatureSupported() + || isGeoTimeZoneDetectionFeatureSupported(); + } + + @Override + public boolean isTelephonyTimeZoneDetectionFeatureSupported() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + } + + @Override + public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection); + } + + @Override + public boolean isGeoTimeZoneDetectionFeatureSupported() { + // For the feature to be enabled it must: + // 1) Be turned on in config. + // 2) Not be turned off via a server flag. + // 3) There must be at least one location time zone provider enabled / configured. + return isGeoTimeZoneDetectionFeatureSupportedInConfig() + && isGeoTimeZoneDetectionFeatureSupportedInternal() + && atLeastOneProviderIsEnabled(); + } + + private boolean atLeastOneProviderIsEnabled() { + return !(Objects.equals(getPrimaryLocationTimeZoneProviderMode(), PROVIDER_MODE_DISABLED) + && Objects.equals(getSecondaryLocationTimeZoneProviderMode(), + PROVIDER_MODE_DISABLED)); + } + + /** + * Returns {@code true} if the location-based time zone detection feature is not explicitly + * disabled by a server flag. + */ + private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() { + final boolean defaultEnabled = true; + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + defaultEnabled); + } + + @Override + @NonNull + public synchronized String getPrimaryLocationTimeZoneProviderPackageName() { + if (mTestPrimaryLocationTimeZoneProviderMode != null) { + // In test mode: use the test setting value. + return mTestPrimaryLocationTimeZoneProviderPackageName; + } + return mContext.getResources().getString( + R.string.config_primaryLocationTimeZoneProviderPackageName); + } + + @Override + public synchronized void setTestPrimaryLocationTimeZoneProviderPackageName( + @Nullable String testPrimaryLocationTimeZoneProviderPackageName) { + mTestPrimaryLocationTimeZoneProviderPackageName = + testPrimaryLocationTimeZoneProviderPackageName; + mTestPrimaryLocationTimeZoneProviderMode = + mTestPrimaryLocationTimeZoneProviderPackageName == null + ? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED; + } + + @Override + public synchronized boolean isTestPrimaryLocationTimeZoneProvider() { + return mTestPrimaryLocationTimeZoneProviderMode != null; + } + + @Override + @NonNull + public synchronized String getSecondaryLocationTimeZoneProviderPackageName() { + if (mTestSecondaryLocationTimeZoneProviderMode != null) { + // In test mode: use the test setting value. + return mTestSecondaryLocationTimeZoneProviderPackageName; + } + return mContext.getResources().getString( + R.string.config_secondaryLocationTimeZoneProviderPackageName); + } + + @Override + public synchronized void setTestSecondaryLocationTimeZoneProviderPackageName( + @Nullable String testSecondaryLocationTimeZoneProviderPackageName) { + mTestSecondaryLocationTimeZoneProviderPackageName = + testSecondaryLocationTimeZoneProviderPackageName; + mTestSecondaryLocationTimeZoneProviderMode = + mTestSecondaryLocationTimeZoneProviderPackageName == null + ? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED; + } + + @Override + public synchronized boolean isTestSecondaryLocationTimeZoneProvider() { + return mTestSecondaryLocationTimeZoneProviderMode != null; + } + + @Override + public synchronized void setRecordProviderStateChanges(boolean enabled) { + mRecordProviderStateChanges = enabled; + } + + @Override + public synchronized boolean getRecordProviderStateChanges() { + return mRecordProviderStateChanges; + } + + @Override + @NonNull + public synchronized @ProviderMode String getPrimaryLocationTimeZoneProviderMode() { + if (mTestPrimaryLocationTimeZoneProviderMode != null) { + // In test mode: use the test setting value. + return mTestPrimaryLocationTimeZoneProviderMode; + } + return mServerFlags.getOptionalString(ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE) + .orElse(getPrimaryLocationTimeZoneProviderModeFromConfig()); + } + + @NonNull + private synchronized @ProviderMode String getPrimaryLocationTimeZoneProviderModeFromConfig() { + int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId) + ? PROVIDER_MODE_ENABLED : PROVIDER_MODE_DISABLED; + } + + @Override + public synchronized @ProviderMode String getSecondaryLocationTimeZoneProviderMode() { + if (mTestSecondaryLocationTimeZoneProviderMode != null) { + // In test mode: use the test setting value. + return mTestSecondaryLocationTimeZoneProviderMode; + } + return mServerFlags.getOptionalString(ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE) + .orElse(getSecondaryLocationTimeZoneProviderModeFromConfig()); + } + + @NonNull + private synchronized @ProviderMode String getSecondaryLocationTimeZoneProviderModeFromConfig() { + int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId) + ? PROVIDER_MODE_ENABLED : PROVIDER_MODE_DISABLED; + } + + @Override + public boolean isGeoDetectionEnabledForUsersByDefault() { + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, false); + } + + @Override + @NonNull + public Optional getGeoDetectionSettingEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); + } + + @Override + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeout() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, + DEFAULT_LTZP_INITIALIZATION_TIMEOUT); + } + + @Override + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ); + } + + @Override + @NonNull + public Duration getLocationTimeZoneUncertaintyDelay() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + DEFAULT_LTZP_UNCERTAINTY_DELAY); + } + + @Override + @NonNull + public Duration getLocationTimeZoneProviderEventFilteringAgeThreshold() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, + DEFAULT_LTZP_EVENT_FILTER_AGE_THRESHOLD); + } + + @Override + public synchronized void resetVolatileTestConfig() { + mTestPrimaryLocationTimeZoneProviderPackageName = null; + mTestPrimaryLocationTimeZoneProviderMode = null; + mTestSecondaryLocationTimeZoneProviderPackageName = null; + mTestSecondaryLocationTimeZoneProviderMode = null; + mRecordProviderStateChanges = false; + } + + private boolean getConfigBoolean(int providerEnabledConfigId) { + Resources resources = mContext.getResources(); + return resources.getBoolean(providerEnabledConfigId); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index 41611772bc9ff..b6ce8026a2dc6 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -27,23 +27,6 @@ import android.annotation.NonNull; */ public interface TimeZoneDetectorInternal { - /** 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(@NonNull ConfigurationChangeListener listener); - - /** - * 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(); - /** * Suggests the current time zone, determined using geolocation, to the detector. The * detector may ignore the signal based on system settings, whether better information is diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java index ca87811ce34d9..f61df820c3e06 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.content.Context; import android.os.Handler; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; /** @@ -34,46 +32,12 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter @NonNull private final Context mContext; @NonNull private final Handler mHandler; @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; - @NonNull private final List mConfigurationListeners = - new ArrayList<>(); public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler, @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy); - - // Wire up a change listener so that any downstream listeners can be notified when - // the configuration changes for any reason. - mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged); - } - - private void handleConfigurationChanged() { - synchronized (mConfigurationListeners) { - for (ConfigurationChangeListener listener : mConfigurationListeners) { - listener.onChange(); - } - } - } - - @Override - public void addConfigurationListener(ConfigurationChangeListener listener) { - synchronized (mConfigurationListeners) { - mConfigurationListeners.add(Objects.requireNonNull(listener)); - } - } - - @Override - public void removeConfigurationListener(ConfigurationChangeListener listener) { - synchronized (mConfigurationListeners) { - mConfigurationListeners.remove(Objects.requireNonNull(listener)); - } - } - - @Override - @NonNull - public ConfigurationInternal getCurrentUserConfigurationInternal() { - return mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal(); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index e6a58a1d2c429..e0c39ad55f470 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -80,7 +80,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Handler handler = FgThread.getHandler(); ServiceConfigAccessor serviceConfigAccessor = - ServiceConfigAccessor.getInstance(context); + ServiceConfigAccessorImpl.getInstance(context); TimeZoneDetectorStrategy timeZoneDetectorStrategy = TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); @@ -92,7 +92,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub // Publish the binder service so it can be accessed from other (appropriately // permissioned) processes. TimeZoneDetectorService service = TimeZoneDetectorService.create( - context, handler, timeZoneDetectorStrategy); + context, handler, serviceConfigAccessor, timeZoneDetectorStrategy); publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service); } } @@ -106,6 +106,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @NonNull private final CallerIdentityInjector mCallerIdentityInjector; + @NonNull + private final ServiceConfigAccessor mServiceConfigAccessor; + @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; @@ -126,25 +129,29 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub private static TimeZoneDetectorService create( @NonNull Context context, @NonNull Handler handler, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) { CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL; - return new TimeZoneDetectorService( - context, handler, callerIdentityInjector, timeZoneDetectorStrategy); + return new TimeZoneDetectorService(context, handler, callerIdentityInjector, + serviceConfigAccessor, timeZoneDetectorStrategy); } @VisibleForTesting public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull CallerIdentityInjector callerIdentityInjector, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy); // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. - mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged); + mServiceConfigAccessor.addConfigurationInternalChangeListener( + () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); } @Override @@ -160,7 +167,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { ConfigurationInternal configurationInternal = - mTimeZoneDetectorStrategy.getConfigurationInternal(userId); + mServiceConfigAccessor.getConfigurationInternal(userId); return configurationInternal.createCapabilitiesAndConfig(); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); @@ -184,7 +191,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { - return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration); + return mServiceConfigAccessor.updateConfiguration(userId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } @@ -264,7 +271,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } } - void handleConfigurationChanged() { + void handleConfigurationInternalChangedOnHandlerThread() { // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. @@ -315,14 +322,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean isTelephonyTimeZoneDetectionSupported() { enforceManageTimeZoneDetectorPermission(); - return ServiceConfigAccessor.getInstance(mContext) - .isTelephonyTimeZoneDetectionFeatureSupported(); + return mServiceConfigAccessor.isTelephonyTimeZoneDetectionFeatureSupported(); } boolean isGeoTimeZoneDetectionSupported() { enforceManageTimeZoneDetectorPermission(); - return ServiceConfigAccessor.getInstance(mContext).isGeoTimeZoneDetectionFeatureSupported(); + return mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported(); } /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index e2cc679197008..ede52ba84d73b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -17,7 +17,6 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.util.IndentingPrintWriter; @@ -72,45 +71,14 @@ import android.util.IndentingPrintWriter; * *

Threading: * - *

Suggestion calls with a void return type may be handed off to a separate thread and handled - * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, - * {@link #generateMetricsState()} and debug calls like {@link - * #dump(IndentingPrintWriter, String[])}, may be called on a different thread concurrently with - * other operations. + *

Implementations of this class must be thread-safe as calls calls like {@link + * #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on + * differents thread concurrently with other operations. * * @hide */ public interface TimeZoneDetectorStrategy extends Dumpable { - /** - * Adds a listener that will be triggered whenever {@link ConfigurationInternal} may have - * changed. - */ - void addConfigChangeListener(@NonNull ConfigurationChangeListener listener); - - /** - * Returns a snapshot of the configuration that controls time zone detector behavior for the - * specified user. - */ - @NonNull - ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); - - /** - * Returns a snapshot of the configuration that controls time zone detector behavior for the - * current user. - */ - @NonNull - ConfigurationInternal getCurrentUserConfigurationInternal(); - - /** - * Updates the configuration properties that control a device's time zone behavior. - * - *

This method returns {@code true} if the configuration was changed, - * {@code false} otherwise. - */ - boolean updateConfiguration( - @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration); - /** * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}. diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 1b09f441a87eb..3b6c1ead3388f 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -27,7 +27,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.TimeZoneCapabilities; import android.app.time.TimeZoneCapabilitiesAndConfig; -import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; @@ -39,7 +38,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -63,16 +61,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat /** * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any - * changes that could affect time zone detection. This is invoked during system server - * setup. + * changes that could affect the content of {@link ConfigurationInternal}. + * This is invoked during system server setup. */ - void setConfigChangeListener(@NonNull ConfigurationChangeListener listener); + void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener); - /** Returns the current user at the instant it is called. */ - @UserIdInt int getCurrentUserId(); - - /** Returns the {@link ConfigurationInternal} for the specified user. */ - ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); + /** Returns the {@link ConfigurationInternal} for the current user. */ + @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Returns true if the device has had an explicit time zone set. @@ -88,13 +83,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * Sets the device's time zone. */ void setDeviceTimeZone(@NonNull String zoneId); - - /** - * Stores the configuration properties contained in {@code newConfiguration}. - * All checks about user capabilities must be done by the caller and - * {@link TimeZoneConfiguration#isComplete()} must be {@code true}. - */ - void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration); } private static final String LOG_TAG = TimeZoneDetectorService.TAG; @@ -166,10 +154,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @NonNull private final Environment mEnvironment; - @GuardedBy("this") - @NonNull - private final List mConfigChangeListeners = new ArrayList<>(); - /** * A log that records the decisions / decision metadata that affected the device's time zone. * This is logged in bug reports to assist with debugging issues with detection. @@ -202,6 +186,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private final ReferenceWithHistory mLatestManualSuggestion = new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + @GuardedBy("this") + @NonNull + private ConfigurationInternal mCurrentConfigurationInternal; /** * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. @@ -217,71 +204,19 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @VisibleForTesting public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) { mEnvironment = Objects.requireNonNull(environment); - mEnvironment.setConfigChangeListener(this::handleConfigChanged); - } - /** - * Adds a listener that allows the strategy to communicate with the surrounding service / - * internal. This must be called before the instance is used. - */ - @Override - public synchronized void addConfigChangeListener( - @NonNull ConfigurationChangeListener listener) { - Objects.requireNonNull(listener); - mConfigChangeListeners.add(listener); - } - - @Override - @NonNull - public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { - return mEnvironment.getConfigurationInternal(userId); - } - - @Override - @NonNull - public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() { - int currentUserId = mEnvironment.getCurrentUserId(); - return getConfigurationInternal(currentUserId); - } - - @Override - public synchronized boolean updateConfiguration(@UserIdInt int userId, - @NonNull TimeZoneConfiguration requestedConfiguration) { - Objects.requireNonNull(requestedConfiguration); - - TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = - getConfigurationInternal(userId).createCapabilitiesAndConfig(); - TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); - TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration(); - - final TimeZoneConfiguration newConfiguration = - capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration); - if (newConfiguration == null) { - // The changes could not be made because the user's capabilities do not allow it. - return false; + synchronized (this) { + mEnvironment.setConfigurationInternalChangeListener( + this::handleConfigurationInternalChanged); + mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal(); } - - // Store the configuration / notify as needed. This will cause the mEnvironment to invoke - // handleConfigChanged() asynchronously. - mEnvironment.storeConfiguration(userId, newConfiguration); - - String logMsg = "Configuration changed:" - + " oldConfiguration=" + oldConfiguration - + ", newConfiguration=" + newConfiguration; - mTimeZoneChangesLog.log(logMsg); - if (DBG) { - Slog.d(LOG_TAG, logMsg); - } - return true; } @Override public synchronized void suggestGeolocationTimeZone( @NonNull GeolocationTimeZoneSuggestion suggestion) { - int currentUserId = mEnvironment.getCurrentUserId(); - ConfigurationInternal currentUserConfig = - mEnvironment.getConfigurationInternal(currentUserId); + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; if (DBG) { Slog.d(LOG_TAG, "Geolocation suggestion received." + " currentUserConfig=" + currentUserConfig @@ -308,8 +243,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public synchronized boolean suggestManualTimeZone( @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) { - int currentUserId = mEnvironment.getCurrentUserId(); - if (userId != currentUserId) { + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (currentUserConfig.getUserId() != userId) { Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId + " suggestion=" + suggestion); @@ -323,7 +258,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat String cause = "Manual time suggestion received: suggestion=" + suggestion; TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = - getConfigurationInternal(userId).createCapabilitiesAndConfig(); + currentUserConfig.createCapabilitiesAndConfig(); TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) { Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually" @@ -347,9 +282,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public synchronized void suggestTelephonyTimeZone( @NonNull TelephonyTimeZoneSuggestion suggestion) { - int currentUserId = mEnvironment.getCurrentUserId(); - ConfigurationInternal currentUserConfig = - mEnvironment.getConfigurationInternal(currentUserId); + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; if (DBG) { Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig + " newSuggestion=" + suggestion); @@ -364,8 +297,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // Store the suggestion against the correct slotIndex. mTelephonySuggestionsBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); - // Now perform auto time zone detection. The new suggestion may be used to modify the time - // zone setting. + // Now perform auto time zone detection: the new suggestion might be used to modify the + // time zone setting. String reason = "New telephony time zone suggested. suggestion=" + suggestion; doAutoTimeZoneDetection(currentUserConfig, reason); } @@ -373,7 +306,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @Override @NonNull public synchronized MetricsTimeZoneDetectorState generateMetricsState() { - int currentUserId = mEnvironment.getCurrentUserId(); // Just capture one telephony suggestion: the one that would be used right now if telephony // detection is in use. QualifiedTelephonyTimeZoneSuggestion bestQualifiedTelephonySuggestion = @@ -386,7 +318,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat new OrdinalGenerator<>(new TimeZoneCanonicalizer()); return MetricsTimeZoneDetectorState.create( tzIdOrdinalGenerator, - getConfigurationInternal(currentUserId), + mCurrentConfigurationInternal, mEnvironment.getDeviceTimeZone(), getLatestManualSuggestion(), telephonySuggestion, @@ -593,26 +525,20 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return findBestTelephonySuggestion(); } - private synchronized void handleConfigChanged() { - if (DBG) { - Slog.d(LOG_TAG, "handleConfigChanged()"); - } - - // This method is called whenever the user changes or the config for any user changes. We - // don't know what happened, so we capture the current user's config, check to see if we - // need to clear state associated with a previous user, and rerun detection. - int currentUserId = mEnvironment.getCurrentUserId(); + private synchronized void handleConfigurationInternalChanged() { ConfigurationInternal currentUserConfig = - mEnvironment.getConfigurationInternal(currentUserId); + mEnvironment.getCurrentUserConfigurationInternal(); + String logMsg = "handleConfigurationInternalChanged:" + + " oldConfiguration=" + mCurrentConfigurationInternal + + ", newConfiguration=" + currentUserConfig; + if (DBG) { + Slog.d(LOG_TAG, logMsg); + } + mCurrentConfigurationInternal = currentUserConfig; // The configuration change may have changed available suggestions or the way suggestions // are used, so re-run detection. - doAutoTimeZoneDetection(currentUserConfig, "handleConfigChanged()"); - - // Pass on the signal to sub-components. - for (ConfigurationChangeListener listener : mConfigChangeListeners) { - listener.onChange(); - } + doAutoTimeZoneDetection(currentUserConfig, logMsg); } /** @@ -623,11 +549,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat ipw.println("TimeZoneDetectorStrategy:"); ipw.increaseIndent(); // level 1 - int currentUserId = mEnvironment.getCurrentUserId(); - ipw.println("mEnvironment.getCurrentUserId()=" + currentUserId); - ConfigurationInternal configuration = mEnvironment.getConfigurationInternal(currentUserId); - ipw.println("mEnvironment.getConfiguration(currentUserId)=" + configuration); - ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]"); + ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal); + ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig() + + "]"); ipw.println("mEnvironment.isDeviceTimeZoneInitialized()=" + mEnvironment.isDeviceTimeZoneInitialized()); ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone()); diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index 20fb61ddf83d1..33cdc5f66def8 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -20,11 +20,9 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.os.SystemClock; -import com.android.server.LocalServices; 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; import java.util.Objects; @@ -35,32 +33,32 @@ import java.util.Objects; */ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment { - @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; - @NonNull private final ConfigurationChangeListener mConfigurationChangeListener; + @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener; ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull LocationTimeZoneProviderController controller) { super(threadingDomain); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); - mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class); - // Listen for configuration changes. - mConfigurationChangeListener = () -> mThreadingDomain.post(controller::onConfigChanged); - mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener); + // Listen for configuration internal changes. + mConfigurationInternalChangeListener = + () -> mThreadingDomain.post(controller::onConfigurationInternalChanged); + mServiceConfigAccessor.addConfigurationInternalChangeListener( + mConfigurationInternalChangeListener); } - @Override void destroy() { - mTimeZoneDetectorInternal.removeConfigurationListener(mConfigurationChangeListener); + mServiceConfigAccessor.removeConfigurationInternalChangeListener( + mConfigurationInternalChangeListener); } @Override @NonNull ConfigurationInternal getCurrentUserConfigurationInternal() { - return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal(); + return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java index 466a0391746f7..b9da2ebd36638 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java @@ -122,7 +122,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { } @Override - void onConfigChanged() { + void onConfigurationInternalChanged() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index 942df532d612a..ddbeac4e458aa 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -41,6 +41,7 @@ import com.android.server.FgThread; import com.android.server.SystemService; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.ServiceConfigAccessor; +import com.android.server.timezonedetector.ServiceConfigAccessorImpl; import com.android.server.timezonedetector.TimeZoneDetectorInternal; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; @@ -77,18 +78,18 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneManagerService mService; @NonNull - private final ServiceConfigAccessor mServerConfigAccessor; + private final ServiceConfigAccessor mServiceConfigAccessor; public Lifecycle(@NonNull Context context) { super(Objects.requireNonNull(context)); - mServerConfigAccessor = ServiceConfigAccessor.getInstance(context); + mServiceConfigAccessor = ServiceConfigAccessorImpl.getInstance(context); } @Override public void onStart() { Context context = getContext(); - if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { - mService = new LocationTimeZoneManagerService(context); + if (mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { + mService = new LocationTimeZoneManagerService(context, mServiceConfigAccessor); // 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. @@ -100,7 +101,7 @@ public class LocationTimeZoneManagerService extends Binder { @Override public void onBootPhase(@BootPhase int phase) { - if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { + if (mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { if (phase == PHASE_SYSTEM_SERVICES_READY) { // The location service must be functioning after this boot phase. mService.onSystemReady(); @@ -157,12 +158,13 @@ public class LocationTimeZoneManagerService extends Binder { @GuardedBy("mSharedLock") private ControllerEnvironmentImpl mEnvironment; - LocationTimeZoneManagerService(Context context) { + LocationTimeZoneManagerService(@NonNull Context context, + @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mHandler = FgThread.getHandler(); mThreadingDomain = new HandlerThreadingDomain(mHandler); mSharedLock = mThreadingDomain.getLockObject(); - mServiceConfigAccessor = ServiceConfigAccessor.getInstance(mContext); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); } // According to the SystemService docs: All lifecycle methods are called from the system diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index fdb9c14e7c597..4dff02e8ab6ff 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -39,7 +39,7 @@ import java.util.Objects; *

The controller interacts with the following components: *