Merge "Rewire config for time zone detection services"

This commit is contained in:
Neil Fuller
2021-11-18 14:32:21 +00:00
committed by Android (Google) Code Review
18 changed files with 1029 additions and 1050 deletions

View File

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

View File

@@ -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<String> 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.
*
* <p>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.
*
* <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
* method.
* <p>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<Boolean> getGeoDetectionSettingEnabledOverride() {
return mServerFlags.getOptionalBoolean(
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE);
}
Optional<Boolean> 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();
}

View File

@@ -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<String> 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<String> 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<ConfigurationChangeListener> 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<Boolean> 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<Boolean> 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);
}
}

View File

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

View File

@@ -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<ConfigurationChangeListener> 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

View File

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

View File

@@ -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;
*
* <p>Threading:
*
* <p>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.
* <p>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.
*
* <p>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}.

View File

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

View File

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

View File

@@ -122,7 +122,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
}
@Override
void onConfigChanged() {
void onConfigurationInternalChanged() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {

View File

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

View File

@@ -39,7 +39,7 @@ import java.util.Objects;
* <p>The controller interacts with the following components:
* <ul>
* <li>The surrounding service, which calls {@link #initialize(Environment, Callback)} and
* {@link #onConfigChanged()}.</li>
* {@link #onConfigurationInternalChanged()}.</li>
* <li>The {@link Environment} through which obtains information it needs.</li>
* <li>The {@link Callback} through which it makes time zone suggestions.</li>
* <li>Any {@link LocationTimeZoneProvider} instances it owns, which communicate via the
@@ -81,12 +81,11 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
abstract void initialize(@NonNull Environment environment, @NonNull Callback callback);
/**
* Called when any settings or other device state that affect location-based time zone detection
* have changed. The receiver should call {@link
* Environment#getCurrentUserConfigurationInternal()} to get the current user's config. This
* call must be made on the {@link ThreadingDomain} handler thread.
* Called when the content of the {@link ConfigurationInternal} may have changed. The receiver
* should call {@link Environment#getCurrentUserConfigurationInternal()} to get the current
* user's config. This call must be made on the {@link ThreadingDomain} handler thread.
*/
abstract void onConfigChanged();
abstract void onConfigurationInternalChanged();
@VisibleForTesting
abstract boolean isUncertaintyTimeoutSet();

View File

@@ -0,0 +1,213 @@
/*
* Copyright (C) 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
class FakeServiceConfigAccessor implements ServiceConfigAccessor {
private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
private ConfigurationInternal mConfigurationInternal;
@Override
public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mConfigurationInternal;
}
@Override
public boolean updateConfiguration(
@UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) {
assertNotNull(mConfigurationInternal);
assertNotNull(requestedChanges);
// Simulate the real strategy's behavior: the new configuration will be updated to be the
// old configuration merged with the new if the user has the capability to up the settings.
// Then, if the configuration changed, the change listener is invoked.
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
mConfigurationInternal.createCapabilitiesAndConfig();
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
// Note: Unlike the real strategy, the listeners are invoked synchronously.
simulateConfigurationChangeForTests();
}
return true;
}
void initializeConfiguration(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
void simulateConfigurationChangeForTests() {
for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
assertEquals("Multi-user testing not supported currently",
userId, mConfigurationInternal.getUserId());
return mConfigurationInternal;
}
@Override
public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
failUnimplemented();
}
@Override
public boolean isTelephonyTimeZoneDetectionFeatureSupported() {
return failUnimplemented();
}
@Override
public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() {
return failUnimplemented();
}
@Override
public boolean isGeoTimeZoneDetectionFeatureSupported() {
return failUnimplemented();
}
@Override
public String getPrimaryLocationTimeZoneProviderPackageName() {
return failUnimplemented();
}
@Override
public void setTestPrimaryLocationTimeZoneProviderPackageName(
String testPrimaryLocationTimeZoneProviderPackageName) {
failUnimplemented();
}
@Override
public boolean isTestPrimaryLocationTimeZoneProvider() {
return failUnimplemented();
}
@Override
public String getSecondaryLocationTimeZoneProviderPackageName() {
return failUnimplemented();
}
@Override
public void setTestSecondaryLocationTimeZoneProviderPackageName(
String testSecondaryLocationTimeZoneProviderPackageName) {
failUnimplemented();
}
@Override
public boolean isTestSecondaryLocationTimeZoneProvider() {
return failUnimplemented();
}
@Override
public void setRecordProviderStateChanges(boolean enabled) {
failUnimplemented();
}
@Override
public boolean getRecordProviderStateChanges() {
return failUnimplemented();
}
@Override
public @ProviderMode String getPrimaryLocationTimeZoneProviderMode() {
return failUnimplemented();
}
@Override
public @ProviderMode String getSecondaryLocationTimeZoneProviderMode() {
return failUnimplemented();
}
@Override
public boolean isGeoDetectionEnabledForUsersByDefault() {
return failUnimplemented();
}
@Override
public Optional<Boolean> getGeoDetectionSettingEnabledOverride() {
return failUnimplemented();
}
@Override
public Duration getLocationTimeZoneProviderInitializationTimeout() {
return failUnimplemented();
}
@Override
public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() {
return failUnimplemented();
}
@Override
public Duration getLocationTimeZoneUncertaintyDelay() {
return failUnimplemented();
}
@Override
public Duration getLocationTimeZoneProviderEventFilteringAgeThreshold() {
return failUnimplemented();
}
@Override
public void resetVolatileTestConfig() {
failUnimplemented();
}
@SuppressWarnings("UnusedReturnValue")
private static <T> T failUnimplemented() {
fail("Unimplemented");
return null;
}
}

View File

@@ -16,85 +16,22 @@
package com.android.server.timezonedetector;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.annotation.NonNull;
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.util.IndentingPrintWriter;
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
private ConfigurationChangeListener mConfigurationChangeListener;
// Fake state
private ConfigurationInternal mConfigurationInternal;
// Call tracking.
private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
private ManualTimeZoneSuggestion mLastManualSuggestion;
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
private boolean mDumpCalled;
@Override
public void addConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
if (mConfigurationChangeListener != null) {
fail("Fake only supports one listener");
}
mConfigurationChangeListener = listener;
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
if (mConfigurationInternal.getUserId() != userId) {
fail("Fake only supports one user");
}
return mConfigurationInternal;
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mConfigurationInternal;
}
@Override
public boolean updateConfiguration(
@UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) {
assertNotNull(mConfigurationInternal);
assertNotNull(requestedChanges);
// Simulate the real strategy's behavior: the new configuration will be updated to be the
// old configuration merged with the new if the user has the capability to up the settings.
// Then, if the configuration changed, the change listener is invoked.
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
mConfigurationInternal.createCapabilitiesAndConfig();
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
// Note: Unlike the real strategy, the listeners is invoked synchronously.
mConfigurationChangeListener.onChange();
}
return true;
}
public void simulateConfigurationChangeForTests() {
mConfigurationChangeListener.onChange();
}
@Override
public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
mLastGeolocationSuggestion = timeZoneSuggestion;
@@ -123,10 +60,6 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
mDumpCalled = true;
}
void initializeConfiguration(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
void resetCallTracking() {
mLastGeolocationSuggestion = null;
mLastManualSuggestion = null;

View File

@@ -16,7 +16,6 @@
package com.android.server.timezonedetector;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
@@ -77,18 +76,6 @@ public class TimeZoneDetectorInternalImplTest {
mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
}
@Test
public void testAddConfigurationListener() throws Exception {
boolean[] changeCalled = new boolean[2];
mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[0] = true);
mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[1] = true);
mFakeTimeZoneDetectorStrategy.simulateConfigurationChangeForTests();
assertTrue(changeCalled[0]);
assertTrue(changeCalled[1]);
}
private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_ZONE_IDS);

View File

@@ -65,6 +65,7 @@ public class TimeZoneDetectorServiceTest {
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
private FakeServiceConfigAccessor mFakeServiceConfigAccessor;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategy;
@@ -81,10 +82,11 @@ public class TimeZoneDetectorServiceTest {
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
mFakeTimeZoneDetectorStrategy = new FakeTimeZoneDetectorStrategy();
mFakeServiceConfigAccessor = new FakeServiceConfigAccessor();
mTimeZoneDetectorService = new TimeZoneDetectorService(
mMockContext, mTestHandler, mTestCallerIdentityInjector,
mFakeTimeZoneDetectorStrategy);
mFakeServiceConfigAccessor, mFakeTimeZoneDetectorStrategy);
}
@After
@@ -114,7 +116,7 @@ public class TimeZoneDetectorServiceTest {
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
mFakeServiceConfigAccessor.initializeConfiguration(configuration);
assertEquals(configuration.createCapabilitiesAndConfig(),
mTimeZoneDetectorService.getCapabilitiesAndConfig());
@@ -160,7 +162,7 @@ public class TimeZoneDetectorServiceTest {
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
mFakeServiceConfigAccessor.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);

View File

@@ -32,17 +32,13 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
@@ -88,7 +84,7 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_HIGHEST),
};
private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED =
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
@@ -98,7 +94,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(false)
.build();
private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED =
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
@@ -108,7 +104,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(true)
.build();
private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED =
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
@@ -118,17 +114,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(false)
.build();
private static final ConfigurationInternal CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
.build();
private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
@@ -138,7 +124,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(false)
.build();
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
@@ -148,7 +134,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(false)
.build();
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
@@ -158,18 +144,8 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabled(true)
.build();
private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
createConfig(false /* autoDetection */, null);
private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
createConfig(true /* autoDetection */, null);
private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
createConfig(null, true /* geoDetection */);
private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
createConfig(null, false /* geoDetection */);
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
private FakeEnvironment mFakeEnvironment;
private MockConfigChangeListener mMockConfigChangeListener;
// A fake source of time for suggestions. This will typically be incremented after every use.
@ElapsedRealtimeLong private long mElapsedRealtimeMillis;
@@ -177,132 +153,8 @@ public class TimeZoneDetectorStrategyImplTest {
@Before
public void setUp() {
mFakeEnvironment = new FakeEnvironment();
mMockConfigChangeListener = new MockConfigChangeListener();
mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
}
@Test
public void testGetCurrentUserConfiguration() {
new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
ConfigurationInternal expectedConfiguration =
mFakeEnvironment.getConfigurationInternal(USER_ID);
assertEquals(expectedConfiguration,
mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
}
@Test
public void testUpdateConfiguration_unrestricted() {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Set the configuration with auto detection enabled.
script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// Nothing should have happened: it was initialized in this state.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection disabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
// Update the configuration with auto detection enabled.
script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
}
@Test
public void testUpdateConfiguration_restricted() {
Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
// Try to update the configuration with auto detection disabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Try to update the configuration with auto detection enabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Try to update the configuration to enable geolocation time zone detection: this should
// succeed, the geolocation time zone detection setting is not covered by the restriction).
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */);
// The settings should have been changed.
ConfigurationInternal expectedConfig = new ConfigurationInternal.Builder(
CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED)
.setGeoDetectionEnabled(false)
.build();
script.verifyConfigurationChangedAndReset(expectedConfig);
}
@Test
public void testUpdateConfiguration_autoDetectNotSupported() {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
// Try to update the configuration with auto detection disabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Try to update the configuration with auto detection enabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
}
@Test
public void testUpdateConfiguration_autoDetectSupportedGeoNotSupported() {
Script script = new Script().initializeConfig(
CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED);
// Update the configuration with auto detection disabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
ConfigurationInternal expectedConfig =
new ConfigurationInternal.Builder(CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED)
.setAutoDetectionEnabled(false)
.build();
script.verifyConfigurationChangedAndReset(expectedConfig);
// Try to update the configuration with geo detection disabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capability to modify
// the setting when the feature is disabled.
script.verifyConfigurationNotChanged();
// Try to update the configuration with geo detection enabled.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capability to modify
// the setting when the feature is disabled.
script.verifyConfigurationNotChanged();
}
@Test
@@ -312,8 +164,9 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
createEmptySlotIndex2Suggestion();
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
.verifyTimeZoneNotChanged();
@@ -357,7 +210,9 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTestCase testCase2 = newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
Script script = new Script()
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
// A low quality suggestions will not be taken: The device time zone setting is left
// uninitialized.
@@ -422,8 +277,9 @@ public class TimeZoneDetectorStrategyImplTest {
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
// Start with the device in a known state.
script.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
TelephonyTimeZoneSuggestion suggestion =
testCase.createSuggestion(SLOT_INDEX1, "Europe/London");
@@ -442,8 +298,7 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting on should cause the device setting to be set.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
script.simulateSetAutoMode(true);
// When time zone detection is already enabled the suggestion (if it scores highly
// enough) should be set immediately.
@@ -460,8 +315,7 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting should off should do nothing.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
script.simulateSetAutoMode(false)
.verifyTimeZoneNotChanged();
// Assert internal service state.
@@ -475,8 +329,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testTelephonySuggestionsSingleSlotId() {
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
makeSlotIndex1SuggestionAndCheckState(script, testCase);
@@ -540,8 +395,10 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_NONE);
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking()
// Initialize the latest suggestions as empty so we don't need to worry about nulls
// below for the first loop.
.simulateTelephonyTimeZoneSuggestion(emptySlotIndex1Suggestion)
@@ -625,7 +482,9 @@ public class TimeZoneDetectorStrategyImplTest {
*/
@Test
public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
Script script = new Script()
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
TelephonyTestCase testCase = newTelephonyTestCase(
MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -643,20 +502,18 @@ public class TimeZoneDetectorStrategyImplTest {
// Toggling time zone detection should set the device time zone only if the current setting
// value is different from the most recent telephony suggestion.
script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
script.simulateSetAutoMode(false)
.verifyTimeZoneNotChanged()
.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.simulateSetAutoMode(true)
.verifyTimeZoneNotChanged();
// Simulate a user turning auto detection off, a new suggestion being made while auto
// detection is off, and the user turning it on again.
script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
script.simulateSetAutoMode(false)
.simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
.verifyTimeZoneNotChanged();
// Latest suggestion should be used.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
script.simulateSetAutoMode(true)
.verifyTimeZoneChangedAndReset(newYorkSuggestion);
}
@@ -673,12 +530,13 @@ public class TimeZoneDetectorStrategyImplTest {
private void checkManualSuggestion_unrestricted_autoDetectionEnabled(
boolean geoDetectionEnabled) {
ConfigurationInternal geoTzEnabledConfig =
new ConfigurationInternal.Builder(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.setGeoDetectionEnabled(geoDetectionEnabled)
.build();
Script script = new Script()
.initializeConfig(geoTzEnabledConfig)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(geoTzEnabledConfig)
.resetConfigurationTracking();
// Auto time zone detection is enabled so the manual suggestion should be ignored.
script.simulateManualTimeZoneSuggestion(
@@ -691,8 +549,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
Script script = new Script()
.initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_ENABLED)
.resetConfigurationTracking();
// User is restricted so the manual suggestion should be ignored.
script.simulateManualTimeZoneSuggestion(
@@ -705,8 +564,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
// Auto time zone detection is disabled so the manual suggestion should be used.
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
@@ -720,8 +580,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
.initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_DISABLED)
.resetConfigurationTracking();
// Restricted users do not have the capability.
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
@@ -735,8 +596,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_autoDetectNotSupported() {
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_DETECT_NOT_SUPPORTED)
.resetConfigurationTracking();
// Unrestricted users have the capability.
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
@@ -749,8 +611,10 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_uncertain() {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
@@ -765,8 +629,9 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_noZones() {
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
@@ -783,8 +648,9 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
script.simulateGeolocationTimeZoneSuggestion(suggestion)
.verifyTimeZoneChangedAndReset(suggestion);
@@ -808,8 +674,9 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/Paris");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
.verifyTimeZoneChangedAndReset(londonOnlySuggestion);
@@ -848,8 +715,9 @@ public class TimeZoneDetectorStrategyImplTest {
"Europe/Paris");
Script script = new Script()
.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
// Add suggestions. Nothing should happen as time zone detection is disabled.
script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
@@ -867,19 +735,17 @@ public class TimeZoneDetectorStrategyImplTest {
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
// disabled.
script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
script.simulateSetAutoMode(true)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
// Changing the detection to enable geo detection will cause the device tz setting to
// change to use the latest geolocation suggestion.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
script.simulateSetGeoDetectionEnabled(true)
.verifyTimeZoneChangedAndReset(geolocationSuggestion);
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
script.simulateUpdateConfiguration(
USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
script.simulateSetGeoDetectionEnabled(false)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
assertEquals(geolocationSuggestion,
@@ -888,12 +754,13 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGenerateMetricsState() {
ConfigurationInternal expectedInternalConfig = CONFIG_INT_AUTO_DISABLED_GEO_DISABLED;
ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
String expectedDeviceTimeZoneId = "InitialZoneId";
Script script = new Script()
.initializeConfig(expectedInternalConfig)
.initializeTimeZoneSetting(expectedDeviceTimeZoneId);
.initializeTimeZoneSetting(expectedDeviceTimeZoneId)
.simulateConfigurationInternalChange(expectedInternalConfig)
.resetConfigurationTracking();
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, null, null,
null, MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
@@ -908,9 +775,7 @@ public class TimeZoneDetectorStrategyImplTest {
manualSuggestion, null, null,
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
// With time zone auto detection off, telephony suggestions will be recorded, but geo
// suggestions won't out of an abundance of caution around respecting user privacy when
// geo detection is off.
// With time zone auto detection off, telephony and geo suggestions will be recorded.
TelephonyTimeZoneSuggestion telephonySuggestion =
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
@@ -926,15 +791,13 @@ public class TimeZoneDetectorStrategyImplTest {
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
// Update the config and confirm that the config metrics state updates also.
TimeZoneConfiguration configUpdate =
createConfig(true /* autoDetection */, true /* geoDetection */);
expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig)
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true)
.build();
expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
script.simulateUpdateConfiguration(USER_ID, configUpdate, true /* expectedResult */)
script.simulateConfigurationInternalChange(expectedInternalConfig)
.verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId);
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
@@ -1016,26 +879,14 @@ public class TimeZoneDetectorStrategyImplTest {
return suggestion;
}
private static TimeZoneConfiguration createConfig(
@Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder();
if (autoDetection != null) {
builder.setAutoDetectionEnabled(autoDetection);
}
if (geoDetection != null) {
builder.setGeoDetectionEnabled(geoDetection);
}
return builder.build();
}
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
private final TestState<String> mTimeZoneId = new TestState<>();
private ConfigurationChangeListener mConfigChangeListener;
private ConfigurationInternal mConfigurationInternal;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
void initializeConfig(ConfigurationInternal configurationInternal) {
mConfigurationInternal.init(configurationInternal);
mConfigurationInternal = configurationInternal;
}
void initializeTimeZoneSetting(String zoneId) {
@@ -1043,22 +894,13 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Override
public void setConfigChangeListener(ConfigurationChangeListener listener) {
mConfigChangeListener = listener;
public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
mConfigurationInternalChangeListener = listener;
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
ConfigurationInternal configuration = mConfigurationInternal.getLatest();
if (userId != configuration.getUserId()) {
fail("FakeCallback does not support multiple users.");
}
return configuration;
}
@Override
public int getCurrentUserId() {
return mConfigurationInternal.getLatest().getUserId();
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mConfigurationInternal;
}
@Override
@@ -1076,26 +918,9 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneId.set(zoneId);
}
@Override
public void storeConfiguration(
@UserIdInt int userId, TimeZoneConfiguration newConfiguration) {
ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
if (userId != oldConfiguration.getUserId()) {
fail("FakeCallback does not support multiple users");
}
ConfigurationInternal mergedConfiguration = oldConfiguration.merge(newConfiguration);
if (!mergedConfiguration.equals(oldConfiguration)) {
mConfigurationInternal.set(mergedConfiguration);
// Note: Unlike the real callback impl, the listener is invoked synchronously.
mConfigChangeListener.onChange();
}
}
void assertKnownUser(int userId) {
assertEquals("FakeCallback does not support multiple users",
mConfigurationInternal.getLatest().getUserId(), userId);
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
mConfigurationInternalChangeListener.onChange();
}
void assertTimeZoneNotChanged() {
@@ -1110,7 +935,6 @@ public class TimeZoneDetectorStrategyImplTest {
void commitAllChanges() {
mTimeZoneId.commitLatest();
mConfigurationInternal.commitLatest();
}
}
@@ -1120,24 +944,40 @@ public class TimeZoneDetectorStrategyImplTest {
*/
private class Script {
Script initializeConfig(ConfigurationInternal configuration) {
mFakeEnvironment.initializeConfig(configuration);
return this;
}
Script initializeTimeZoneSetting(String zoneId) {
mFakeEnvironment.initializeTimeZoneSetting(zoneId);
return this;
}
/**
* Simulates the time zone detection strategy receiving an updated configuration and checks
* the return value.
* Simulates the user / user's configuration changing.
*/
Script simulateUpdateConfiguration(
int userId, TimeZoneConfiguration configuration, boolean expectedResult) {
assertEquals(expectedResult,
mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
return this;
}
/**
* Simulates automatic time zone detection being set to the specified value.
*/
Script simulateSetAutoMode(boolean autoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
return this;
}
/**
* Simulates automatic geolocation time zone detection being set to the specified value.
*/
Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
.setGeoDetectionEnabled(geoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
return this;
}
@@ -1154,7 +994,6 @@ public class TimeZoneDetectorStrategyImplTest {
Script simulateManualTimeZoneSuggestion(
@UserIdInt int userId, ManualTimeZoneSuggestion manualTimeZoneSuggestion,
boolean expectedResult) {
mFakeEnvironment.assertKnownUser(userId);
boolean actualResult = mTimeZoneDetectorStrategy.suggestManualTimeZone(
userId, manualTimeZoneSuggestion);
assertEquals(expectedResult, actualResult);
@@ -1205,32 +1044,6 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
/**
* Verifies that the configuration has been changed to the expected value.
*/
Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
mFakeEnvironment.mConfigurationInternal.assertHasBeenSet();
assertEquals(expected, mFakeEnvironment.mConfigurationInternal.getLatest());
mFakeEnvironment.commitAllChanges();
// Also confirm the listener triggered.
mMockConfigChangeListener.verifyOnChangeCalled();
mMockConfigChangeListener.reset();
return this;
}
/**
* Verifies that no underlying settings associated with the properties from the
* {@link TimeZoneConfiguration} have been changed.
*/
Script verifyConfigurationNotChanged() {
mFakeEnvironment.mConfigurationInternal.assertHasNotBeenSet();
// Also confirm the listener did not trigger.
mMockConfigChangeListener.verifyOnChangeNotCalled();
return this;
}
Script resetConfigurationTracking() {
mFakeEnvironment.commitAllChanges();
return this;
@@ -1261,25 +1074,4 @@ public class TimeZoneDetectorStrategyImplTest {
@MatchType int matchType, @Quality int quality, int expectedScore) {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
private static class MockConfigChangeListener implements ConfigurationChangeListener {
private boolean mOnChangeCalled;
@Override
public void onChange() {
mOnChangeCalled = true;
}
void verifyOnChangeCalled() {
assertTrue(mOnChangeCalled);
}
void verifyOnChangeNotCalled() {
assertFalse(mOnChangeCalled);
}
void reset() {
mOnChangeCalled = false;
}
}
}

View File

@@ -1202,7 +1202,7 @@ public class ControllerImplTest {
if (Objects.equals(oldConfig, newConfig)) {
fail("Bad test? No config change when one was expected");
}
mController.onConfigChanged();
mController.onConfigurationInternalChanged();
}
}