Rewire config for time zone detection services
All configuration-related logic (static, server flags, user settings) for time zone detector services has been consolidated in the ServiceConfigAccessor. Previously, some configuration, the parts that were most associated with time_zone_detector and mostly user-facing was located in the TimeZoneDetectorStrategyImpl, which meant that there was quite a lot of plumbing logic to get the user config info / events to the location-based detection code which was added in S. The ServiceConfigAccessor was already a singleton that several of the classes involved were referencing. The baroque listener chaining has been simplified: everything just talks to the ServiceConfigAccessor rather than being wired (sometimes indirectly) to the TimeZoneDetectorStrategy. The interface for ServiceConfigAccessor has been extracted to enable easier unit testing, the implementation is now ServiceConfigAccessorImpl. This refactor simplifies the TimeZoneDetectorStrategyImpl, which no longer deals with user configuration, and is just another client of the ServiceConfigAccessor. ServiceConfigAccessor is now a larger lump of hard-to-unit-test logic, but coverage of the detection algorithm logic itself hasn't dropped significantly, as can be seen from the test changes. Bug: 197624972 Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/ Test: atest cts/hostsidetests/time/host/src/android/time/cts/host/ Change-Id: Ib70e6a37a1ebfc894840a3e02224522e8cefdac3
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