Merge "Rewire config for time zone detection services"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -122,7 +122,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
}
|
||||
|
||||
@Override
|
||||
void onConfigChanged() {
|
||||
void onConfigurationInternalChanged() {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user