Merge "Use ServiceConfigAccessor directly"

This commit is contained in:
Neil Fuller
2022-11-03 13:11:33 +00:00
committed by Android (Google) Code Review
26 changed files with 683 additions and 291 deletions

View File

@@ -28,7 +28,7 @@ import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.util.Objects;
@@ -60,10 +60,10 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
@Override
public void setConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
ConfigurationChangeListener configurationChangeListener =
@NonNull StateChangeListener listener) {
StateChangeListener stateChangeListener =
() -> mHandler.post(listener::onChange);
mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
}
@Override

View File

@@ -25,8 +25,8 @@ import android.provider.DeviceConfig;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ServiceConfigAccessor;
import com.android.server.timezonedetector.StateChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@ public final class ServerFlags {
* ensure O(1) lookup performance when working out whether a listener should trigger.
*/
@GuardedBy("mListeners")
private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
new ArrayMap<>();
private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
private static final Object SLOCK = new Object();
@@ -213,7 +212,7 @@ public final class ServerFlags {
private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mListeners) {
for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
: mListeners.entrySet()) {
// It's unclear which set of the following two Sets is going to be larger in the
// average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@ public final class ServerFlags {
* <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
* associated remove method.
*/
public void addListener(@NonNull ConfigurationChangeListener listener,
public void addListener(@NonNull StateChangeListener listener,
@NonNull Set<String> keys) {
Objects.requireNonNull(listener);
Objects.requireNonNull(keys);

View File

@@ -19,7 +19,7 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeConfiguration;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.StateChangeListener;
/**
* An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@ public interface ServiceConfigAccessor {
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
* #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
* #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* 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
* #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull

View File

@@ -49,7 +49,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.StateChangeListener;
import java.time.Instant;
import java.util.ArrayList;
@@ -104,8 +104,8 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
@GuardedBy("this")
@NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
new ArrayList<>();
@NonNull
private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
@NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
@NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}

View File

@@ -44,8 +44,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timezonedetector.ArrayMapWithHistory;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ReferenceWithHistory;
import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.time.Duration;
@@ -136,11 +136,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
public interface Environment {
/**
* Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
* changes that could affect the content of {@link ConfigurationInternal}.
* Sets a {@link StateChangeListener} that will be invoked when there are any changes that
* could affect the content of {@link ConfigurationInternal}.
* This is invoked during system server setup.
*/
void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/** Returns the {@link ConfigurationInternal} for the current user. */
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();

View File

@@ -26,7 +26,6 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.Capabilities.CapabilityState;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.os.UserHandle;
@@ -75,7 +74,7 @@ public final class ConfigurationInternal {
mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
mUserId = builder.mUserId;
mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
mUserConfigAllowed = builder.mUserConfigAllowed;
mLocationEnabledSetting = builder.mLocationEnabledSetting;
mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@ public final class ConfigurationInternal {
* Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
* to device policy (enterprise).
*
* <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
* are ignored.
* <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
*/
public boolean isUserConfigAllowed() {
return mUserConfigAllowed;
@@ -196,20 +194,8 @@ public final class ConfigurationInternal {
|| getGeoDetectionRunInBackgroundEnabled());
}
/**
* Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
*
* @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
* policy restrictions that should apply to actual users can be ignored
*/
public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
boolean bypassUserPolicyChecks) {
return new TimeZoneCapabilitiesAndConfig(
asCapabilities(bypassUserPolicyChecks), asConfiguration());
}
@NonNull
private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
UserHandle userHandle = UserHandle.of(mUserId);
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
@@ -262,7 +248,7 @@ public final class ConfigurationInternal {
}
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
private TimeZoneConfiguration asConfiguration() {
public TimeZoneConfiguration asConfiguration() {
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@ public final class ConfigurationInternal {
*/
public static class Builder {
private final @UserIdInt int mUserId;
private @UserIdInt Integer mUserId;
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@ public final class ConfigurationInternal {
private boolean mGeoDetectionEnabledSetting;
/**
* Creates a new Builder with only the userId set.
* Creates a new Builder.
*/
public Builder(@UserIdInt int userId) {
mUserId = userId;
}
public Builder() {}
/**
* Creates a new Builder by copying values from an existing instance.
@@ -370,6 +353,14 @@ public final class ConfigurationInternal {
this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
}
/**
* Sets the user ID the configuration is for.
*/
public Builder setUserId(@UserIdInt int userId) {
mUserId = userId;
return this;
}
/**
* Sets whether the user is allowed to configure time zone settings on this device.
*/

View File

@@ -18,8 +18,6 @@ package com.android.server.timezonedetector;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -29,7 +27,6 @@ import com.android.server.SystemTimeZone;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import java.io.PrintWriter;
import java.util.Objects;
/**
* The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
@NonNull ServiceConfigAccessor serviceConfigAccessor) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
}
@Override
public void setConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
ConfigurationChangeListener configurationChangeListener =
() -> mHandler.post(listener::onChange);
mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
}
@Override
@NonNull
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
EnvironmentImpl() {
}
@Override

View File

@@ -59,20 +59,18 @@ public interface ServiceConfigAccessor {
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
void addConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener);
void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
* #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
* #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
void removeConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener);
void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* 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
* #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull
@@ -104,8 +102,7 @@ public interface ServiceConfigAccessor {
*
* <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
*/
void addLocationTimeZoneManagerConfigListener(
@NonNull ConfigurationChangeListener listener);
void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
/**
* Returns {@code true} if the telephony-based time zone detection feature is supported on the

View File

@@ -22,7 +22,6 @@ 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;
@@ -104,8 +103,8 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@NonNull private final LocationManager mLocationManager;
@GuardedBy("this")
@NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
new ArrayList<>();
@NonNull
private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
@NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
@NonNull ConfigurationChangeListener listener) {
@NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}
@@ -237,10 +236,10 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
Objects.requireNonNull(requestedConfiguration);
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
TimeZoneCapabilities capabilities =
configurationInternal.asCapabilities(bypassUserPolicyChecks);
TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
final TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@Override
@NonNull
public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
return new ConfigurationInternal.Builder(userId)
return new ConfigurationInternal.Builder()
.setUserId(userId)
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@Override
public void addLocationTimeZoneManagerConfigListener(
@NonNull ConfigurationChangeListener listener) {
@NonNull StateChangeListener listener) {
mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
}

View File

@@ -17,11 +17,11 @@
package com.android.server.timezonedetector;
/**
* A listener used to receive notification that configuration has / may have changed (depending on
* A listener used to receive notification that state has / may have changed (depending on
* the usecase).
*/
@FunctionalInterface
public interface ConfigurationChangeListener {
/** Called when the configuration may have changed. */
public interface StateChangeListener {
/** Called when something (may have) changed. */
void onChange();
}

View File

@@ -35,17 +35,14 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
@NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
}
@@ -53,10 +50,9 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
@NonNull
public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
ConfigurationInternal configurationInternal =
mServiceConfigAccessor.getConfigurationInternal(currentUserId);
final boolean bypassUserPolicyChecks = true;
return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
currentUserId, bypassUserPolicyChecks);
}
@Override
@@ -65,7 +61,7 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
final boolean bypassUserPolicyChecks = true;
return mServiceConfigAccessor.updateConfiguration(
return mTimeZoneDetectorStrategy.updateConfiguration(
currentUserId, configuration, bypassUserPolicyChecks);
}

View File

@@ -83,7 +83,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
ServiceConfigAccessor serviceConfigAccessor =
ServiceConfigAccessorImpl.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
DeviceActivityMonitor deviceActivityMonitor =
DeviceActivityMonitorImpl.create(context, handler);
@@ -99,16 +99,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
CurrentUserIdentityInjector currentUserIdentityInjector =
CurrentUserIdentityInjector.REAL;
TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
context, handler, currentUserIdentityInjector, serviceConfigAccessor,
timeZoneDetectorStrategy);
context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
publishLocalService(TimeZoneDetectorInternal.class, internal);
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
TimeZoneDetectorService service = new TimeZoneDetectorService(
context, handler, callerIdentityInjector, serviceConfigAccessor,
timeZoneDetectorStrategy);
context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
// Dump the device activity monitor when the service is dumped.
service.addDumpable(deviceActivityMonitor);
@@ -126,9 +124,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@NonNull
private final CallerIdentityInjector mCallerIdentityInjector;
@NonNull
private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
@@ -150,18 +145,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@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.
mServiceConfigAccessor.addConfigurationInternalChangeListener(
() -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
// the detector state changes for any reason.
mTimeZoneDetectorStrategy.addChangeListener(
() -> mHandler.post(this::handleChangeOnHandlerThread));
}
@Override
@@ -174,12 +167,15 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
enforceManageTimeZoneDetectorPermission();
// Resolve constants like USER_CURRENT to the true user ID as needed.
int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
ConfigurationInternal configurationInternal =
mServiceConfigAccessor.getConfigurationInternal(userId);
final boolean bypassUserPolicyChecks = false;
return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
resolvedUserId, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -204,7 +200,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
final boolean bypassUserPolicyChecks = false;
return mServiceConfigAccessor.updateConfiguration(
return mTimeZoneDetectorStrategy.updateConfiguration(
resolvedUserId, configuration, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
}
void handleConfigurationInternalChangedOnHandlerThread() {
// Configuration has changed, but each user may have a different view of the configuration.
void handleChangeOnHandlerThread() {
// Detector state has changed. Each user may have a different view of the configuration so
// no information is passed; each client must query what they're interested in.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
synchronized (mListeners) {

View File

@@ -17,6 +17,8 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@ import android.util.IndentingPrintWriter;
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
/**
* Adds a listener that will be triggered when something changes that could affect the result
* of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
* includes the current user changing. This is exposed so that (indirect) users like SettingsUI
* can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
* the UI accordingly.
*/
void addChangeListener(StateChangeListener listener);
/**
* Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
*
* <p>The strategy is dependent on device state like current user, settings and device config.
* These updates are usually handled asynchronously, so callers should expect some delay between
* a change being made directly to services like settings and the strategy becoming aware of
* them. Changes made via {@link #updateConfiguration} will be visible immediately.
*
* @param userId the user ID to retrieve the information for
* @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
* policy restrictions that should apply to actual users can be ignored
*/
TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
@UserIdInt int userId, boolean bypassUserPolicyChecks);
/**
* 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.
*
* <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
* subsequent calls.
*
* @param userId the current user ID, supplied to make sure that the asynchronous process
* that happens when users switch is completed when the call is made
* @param configuration the configuration changes
* @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
* policy restrictions that should apply to actual users can be ignored
*/
boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
boolean bypassUserPolicyChecks);
/** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
@NonNull
TimeZoneState getTimeZoneState();

View File

@@ -31,10 +31,10 @@ 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.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
import android.os.Handler;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@ import com.android.server.SystemTimeZone.TimeZoneConfidence;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -67,16 +68,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@VisibleForTesting
public interface Environment {
/**
* Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
* changes that could affect the content of {@link ConfigurationInternal}.
* This is invoked during system server setup.
*/
void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
/** Returns the {@link ConfigurationInternal} for the current user. */
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
/**
* Returns the device's currently configured time zone. May return an empty string.
*/
@@ -206,6 +197,22 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
@NonNull
private final ServiceConfigAccessor mServiceConfigAccessor;
/** The handler used for asynchronous operations triggered by this. */
@NonNull
private final Handler mStateChangeHandler;
@GuardedBy("this")
@NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
/**
* A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
* because it is relatively heavyweight to obtain and is used more often than it is expected to
* change. Because many operations are asynchronous, this value may be out of date but should
* be "eventually consistent".
*/
@GuardedBy("this")
@NonNull
private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,28 +236,92 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
public static TimeZoneDetectorStrategyImpl create(
@NonNull Context context, @NonNull Handler handler,
@NonNull ServiceConfigAccessor serviceConfigAccessor) {
@NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
return new TimeZoneDetectorStrategyImpl(environment);
Environment environment = new EnvironmentImpl();
return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
}
@VisibleForTesting
public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
public TimeZoneDetectorStrategyImpl(
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull Handler handler, @NonNull Environment environment) {
mEnvironment = Objects.requireNonNull(environment);
mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mStateChangeHandler = Objects.requireNonNull(handler);
// Start with telephony fallback enabled.
mTelephonyTimeZoneFallbackEnabled =
new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
synchronized (this) {
mEnvironment.setConfigurationInternalChangeListener(
this::handleConfigurationInternalChanged);
mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
// Listen for config and user changes and get an initial snapshot of configuration.
StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
mCurrentConfigurationInternal =
mServiceConfigAccessor.getCurrentUserConfigurationInternal();
}
}
@Override
public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
@UserIdInt int userId, boolean bypassUserPolicyChecks) {
ConfigurationInternal configurationInternal;
if (mCurrentConfigurationInternal.getUserId() == userId) {
// Use the cached snapshot we have.
configurationInternal = mCurrentConfigurationInternal;
} else {
// This is not a common case: It would be unusual to want the configuration for a user
// other than the "current" user, but it is supported because it is trivial to do so.
// Unlike the current user config, there's no cached copy to worry about so read it
// directly from mServiceConfigAccessor.
configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
}
return new TimeZoneCapabilitiesAndConfig(
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@Override
public synchronized boolean updateConfiguration(
@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
boolean bypassUserPolicyChecks) {
// Write-through
boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
userId, configuration, bypassUserPolicyChecks);
// The update above will trigger config update listeners asynchronously if they are needed,
// but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
// wouldn't see the update. So, handle the cache update and notifications here. When the
// async update listener triggers it will find everything already up to date and do nothing.
if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
ConfigurationInternal configurationInternal =
mServiceConfigAccessor.getConfigurationInternal(userId);
// If the configuration actually changed, update the cached copy synchronously and do
// other necessary house-keeping / (async) listener notifications.
if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
mCurrentConfigurationInternal = configurationInternal;
String logMsg = "updateConfiguration:"
+ " userId=" + userId
+ ", configuration=" + configuration
+ ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+ ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
logTimeZoneDebugInfo(logMsg);
handleConfigurationInternalChanged(logMsg);
}
}
return updateSuccessful;
}
@Override
public synchronized void addChangeListener(StateChangeListener listener) {
mStateChangeListeners.add(listener);
}
@Override
public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
Objects.requireNonNull(timeZoneId);
@@ -334,9 +405,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities =
currentUserConfig.asCapabilities(bypassUserPolicyChecks);
if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ": capabilities=" + capabilities
@@ -735,18 +805,37 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return findBestTelephonySuggestion();
}
private synchronized void handleConfigurationInternalChanged() {
/**
* Handles a configuration change notification.
*/
private synchronized void handleConfigurationInternalMaybeChanged() {
ConfigurationInternal currentUserConfig =
mEnvironment.getCurrentUserConfigurationInternal();
String logMsg = "handleConfigurationInternalChanged:"
+ " oldConfiguration=" + mCurrentConfigurationInternal
+ ", newConfiguration=" + currentUserConfig;
logTimeZoneDebugInfo(logMsg);
mCurrentConfigurationInternal = currentUserConfig;
mServiceConfigAccessor.getCurrentUserConfigurationInternal();
// The configuration change may have changed available suggestions or the way suggestions
// are used, so re-run detection.
doAutoTimeZoneDetection(currentUserConfig, logMsg);
// The configuration may not actually have changed so check before doing anything.
if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
String logMsg = "handleConfigurationInternalMaybeChanged:"
+ " oldConfiguration=" + mCurrentConfigurationInternal
+ ", newConfiguration=" + currentUserConfig;
logTimeZoneDebugInfo(logMsg);
mCurrentConfigurationInternal = currentUserConfig;
handleConfigurationInternalChanged(logMsg);
}
}
/** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
@GuardedBy("this")
private void handleConfigurationInternalChanged(@NonNull String logMsg) {
// Notify change listeners asynchronously.
for (StateChangeListener listener : mStateChangeListeners) {
mStateChangeHandler.post(listener::onChange);
}
// The configuration change may have changed available suggestions or the way
// suggestions are used, so re-run detection.
doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
}
/**
@@ -760,8 +849,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
+ mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
+ "]");
+ mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
+ mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return mTelephonyTimeZoneFallbackEnabled.getValue();
}
@VisibleForTesting
public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
return mCurrentConfigurationInternal;
}
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/

View File

@@ -20,9 +20,9 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.os.SystemClock;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.ServiceConfigAccessor;
import com.android.server.timezonedetector.StateChangeListener;
import java.time.Duration;
import java.util.Objects;
@@ -35,7 +35,7 @@ class LocationTimeZoneProviderControllerEnvironmentImpl
extends LocationTimeZoneProviderController.Environment {
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
@NonNull private final StateChangeListener mConfigurationInternalChangeListener;
LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
@NonNull ServiceConfigAccessor serviceConfigAccessor,

View File

@@ -25,7 +25,7 @@ import android.app.time.TimeCapabilities;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.StateChangeListener;
import java.util.ArrayList;
import java.util.List;
@@ -33,17 +33,17 @@ import java.util.List;
/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
private ConfigurationInternal mConfigurationInternal;
@Override
public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@@ -86,7 +86,7 @@ public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
}
void simulateConfigurationChangeForTests() {
for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
}

View File

@@ -40,7 +40,7 @@ import android.os.TimestampedValue;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.StateChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -1821,7 +1821,7 @@ public class TimeDetectorStrategyImplTest {
private long mElapsedRealtimeMillis;
private long mSystemClockMillis;
private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
private StateChangeListener mConfigurationInternalChangeListener;
// Tracking operations.
private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@ public class TimeDetectorStrategyImplTest {
}
@Override
public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
public void setConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
}

View File

@@ -57,7 +57,8 @@ public class ConfigurationInternalTest {
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectionSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@ public class ConfigurationInternalTest {
assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -117,10 +115,8 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities =
autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -150,7 +146,8 @@ public class ConfigurationInternalTest {
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@ public class ConfigurationInternalTest {
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -205,10 +199,8 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities =
autoOffConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -233,7 +225,8 @@ public class ConfigurationInternalTest {
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@ public class ConfigurationInternalTest {
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -292,10 +282,8 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneCapabilities capabilities =
autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -316,7 +304,8 @@ public class ConfigurationInternalTest {
@Test
public void test_telephonyFallbackSupported() {
ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
ConfigurationInternal config = new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@ public class ConfigurationInternalTest {
/** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
@Test
public void test_geoDetectionRunInBackgroundEnabled() {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)

View File

@@ -16,14 +16,11 @@
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;
@@ -31,76 +28,104 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
/**
* A partially implemented, fake implementation of ServiceConfigAccessor for tests.
*
* <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
* simulate that some settings are global and shared between users. It also delivers config updates
* synchronously.
*/
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
private ConfigurationInternal mConfigurationInternal;
private ConfigurationInternal mCurrentUserConfigurationInternal;
private ConfigurationInternal mOtherUserConfigurationInternal;
@Override
public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mConfigurationInternal;
return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
}
@Override
public boolean updateConfiguration(
@UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
@UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
boolean bypassUserPolicyChecks) {
assertNotNull(mConfigurationInternal);
assertNotNull(mCurrentUserConfigurationInternal);
assertNotNull(requestedChanges);
ConfigurationInternal toUpdate = getConfigurationInternal(userId);
// 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(bypassUserPolicyChecks);
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
// old configuration merged with the new if the user has the capability to update the
// settings. Then, if the configuration changed, the change listener is invoked.
TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
TimeZoneConfiguration configuration = toUpdate.asConfiguration();
TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
if (!newConfiguration.equals(configuration)) {
ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
mCurrentUserConfigurationInternal = updatedConfiguration;
} else if (mOtherUserConfigurationInternal != null
&& updatedConfiguration.getUserId()
== mOtherUserConfigurationInternal.getUserId()) {
mOtherUserConfigurationInternal = updatedConfiguration;
}
// Note: Unlike the real strategy, the listeners are invoked synchronously.
simulateConfigurationChangeForTests();
notifyConfigurationChange();
}
return true;
}
void initializeConfiguration(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
mCurrentUserConfigurationInternal = configurationInternal;
}
void simulateConfigurationChangeForTests() {
for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
mOtherUserConfigurationInternal = configurationInternal;
}
void simulateCurrentUserConfigurationInternalChange(
ConfigurationInternal configurationInternal) {
mCurrentUserConfigurationInternal = configurationInternal;
// Note: Unlike the real strategy, the listeners are invoked synchronously.
notifyConfigurationChange();
}
void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mOtherUserConfigurationInternal = configurationInternal;
// Note: Unlike the real strategy, the listeners are invoked synchronously.
notifyConfigurationChange();
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
assertEquals("Multi-user testing not supported currently",
userId, mConfigurationInternal.getUserId());
return mConfigurationInternal;
if (userId == mCurrentUserConfigurationInternal.getUserId()) {
return mCurrentUserConfigurationInternal;
} else if (mOtherUserConfigurationInternal != null
&& userId == mOtherUserConfigurationInternal.getUserId()) {
return mOtherUserConfigurationInternal;
}
throw new AssertionError("userId not known: " + userId);
}
@Override
public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
failUnimplemented();
}
@@ -206,9 +231,14 @@ public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
failUnimplemented();
}
private void notifyConfigurationChange() {
for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
}
@SuppressWarnings("UnusedReturnValue")
private static <T> T failUnimplemented() {
fail("Unimplemented");
return null;
throw new AssertionError("Unimplemented");
}
}

View File

@@ -15,22 +15,70 @@
*/
package com.android.server.timezonedetector;
import static org.junit.Assert.assertEquals;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
new FakeServiceConfigAccessor();
private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
public FakeTimeZoneDetectorStrategy() {
mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
this::notifyChangeListeners);
}
public void initializeConfiguration(ConfigurationInternal configuration) {
mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
}
@Override
public boolean confirmTimeZone(String timeZoneId) {
return false;
}
@Override
public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
boolean bypassUserPolicyChecks) {
ConfigurationInternal configurationInternal =
mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
assertEquals("Multi-user testing not supported",
configurationInternal.getUserId(), userId);
return new TimeZoneCapabilitiesAndConfig(
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@Override
public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
boolean bypassUserPolicyChecks) {
return mFakeServiceConfigAccessor.updateConfiguration(
userId, requestedChanges, bypassUserPolicyChecks);
}
@Override
public void addChangeListener(StateChangeListener listener) {
mListeners.add(listener);
}
private void notifyChangeListeners() {
for (StateChangeListener listener : mListeners) {
listener.onChange();
}
}
@Override
public TimeZoneState getTimeZoneState() {
return mTimeZoneState;

View File

@@ -161,7 +161,8 @@ public class MetricsTimeZoneDetectorStateTest {
private static ConfigurationInternal createConfigurationInternal(
boolean enhancedMetricsCollectionEnabled) {
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
return new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)

View File

@@ -66,10 +66,14 @@ public class TestHandler extends Handler {
/**
* Waits for all enqueued work to be completed before returning.
*/
public void waitForMessagesToBeProcessed() throws InterruptedException {
public void waitForMessagesToBeProcessed() {
synchronized (mMonitor) {
if (mMessagesSent != mMessagesProcessed) {
mMonitor.wait();
try {
mMonitor.wait();
} catch (InterruptedException e) {
throw new AssertionError("Unexpected exception", e);
}
}
}
}

View File

@@ -50,7 +50,6 @@ public class TimeZoneDetectorInternalImplTest {
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@ public class TimeZoneDetectorInternalImplTest {
mTestHandler = new TestHandler(mHandlerThread.getLooper());
mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -83,17 +81,20 @@ public class TimeZoneDetectorInternalImplTest {
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
final boolean expectedBypassUserPolicyChecks = true;
verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
expectedUserId, expectedBypassUserPolicyChecks);
final boolean bypassUserPolicyChecks = true;
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
new TimeZoneCapabilitiesAndConfig(
testConfig.asCapabilities(expectedBypassUserPolicyChecks),
testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -102,7 +103,7 @@ public class TimeZoneDetectorInternalImplTest {
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@ public class TimeZoneDetectorInternalImplTest {
assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
final boolean expectedBypassUserPolicyChecks = true;
verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
mTestCurrentUserIdentityInjector.getCurrentUserId(),
timeConfiguration,
expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@ public class TimeZoneDetectorInternalImplTest {
}
private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
return new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)

View File

@@ -69,7 +69,6 @@ public class TimeZoneDetectorServiceTest {
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
@@ -85,12 +84,11 @@ public class TimeZoneDetectorServiceTest {
mTestCallerIdentityInjector = new TestCallerIdentityInjector();
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorService = new TimeZoneDetectorService(
mMockContext, mTestHandler, mTestCallerIdentityInjector,
mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -115,7 +113,7 @@ public class TimeZoneDetectorServiceTest {
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@ public class TimeZoneDetectorServiceTest {
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
boolean expectedBypassUserPolicyChecks = false;
verify(mFakeTimeZoneDetectorStrategySpy)
.getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
new TimeZoneCapabilitiesAndConfig(
configuration.asCapabilities(expectedBypassUserPolicyChecks),
configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -160,7 +161,7 @@ public class TimeZoneDetectorServiceTest {
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@ public class TimeZoneDetectorServiceTest {
// Default geo detection settings from auto detection settings - they are not important to
// the tests.
final boolean geoDetectionEnabled = autoDetectionEnabled;
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
return new ConfigurationInternal.Builder()
.setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)

View File

@@ -38,19 +38,28 @@ 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.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
import android.os.HandlerThread;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@ public class TimeZoneDetectorStrategyImplTest {
};
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@ public class TimeZoneDetectorStrategyImplTest {
.build();
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@ public class TimeZoneDetectorStrategyImplTest {
.build();
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@ public class TimeZoneDetectorStrategyImplTest {
.build();
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@ public class TimeZoneDetectorStrategyImplTest {
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@ public class TimeZoneDetectorStrategyImplTest {
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
new ConfigurationInternal.Builder()
.setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(true)
.build();
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
@Before
public void setUp() {
mFakeEnvironment = new FakeEnvironment();
mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
CONFIG_AUTO_DISABLED_GEO_DISABLED);
// Create a thread + handler for processing the work that the strategy posts.
mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
mHandlerThread.start();
mTestHandler = new TestHandler(mHandlerThread.getLooper());
mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
}
@After
public void tearDown() throws Exception {
mHandlerThread.quit();
mHandlerThread.join();
}
@Test
public void testChangeListenerBehavior_currentUser() throws Exception {
ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
// The strategy initializes itself with the current user's config during construction.
assertEquals(currentUserConfig,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
TestStateChangeListener stateChangeListener = new TestStateChangeListener();
mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
boolean bypassUserPolicyChecks = false;
// Report a config change, but not one that actually changes anything.
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_DISABLED_GEO_DISABLED);
mTestHandler.waitForMessagesToBeProcessed();
stateChangeListener.assertNotificationsReceived(0);
assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
// Report a config change that actually changes something.
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_ENABLED_GEO_ENABLED);
mTestHandler.waitForMessagesToBeProcessed();
stateChangeListener.assertNotificationsReceived(1);
stateChangeListener.resetNotificationsReceivedCount();
assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
// Perform a (current user) update via the strategy.
{
TimeZoneConfiguration requestedChanges =
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
USER_ID, requestedChanges, bypassUserPolicyChecks);
mTestHandler.waitForMessagesToBeProcessed();
stateChangeListener.assertNotificationsReceived(1);
stateChangeListener.resetNotificationsReceivedCount();
}
}
// Perform a (not current user) update via the strategy. There's no listener behavior for
// updates to "other" users.
@Test
public void testChangeListenerBehavior_otherUser() throws Exception {
ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
// The strategy initializes itself with the current user's config during construction.
assertEquals(currentUserConfig,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
TestStateChangeListener stateChangeListener = new TestStateChangeListener();
mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
boolean bypassUserPolicyChecks = false;
int otherUserId = currentUserConfig.getUserId() + 1;
ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
.setUserId(otherUserId)
.setGeoDetectionEnabledSetting(true)
.build();
mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
TimeZoneConfiguration requestedChanges =
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
otherUserId, requestedChanges, bypassUserPolicyChecks);
mTestHandler.waitForMessagesToBeProcessed();
// Only changes to the current user's config are notified.
stateChangeListener.assertNotificationsReceived(0);
stateChangeListener.resetNotificationsReceivedCount();
}
// Current user behavior: the strategy caches and returns the latest configuration.
@Test
public void testReadAndWriteConfiguration_currentUser() throws Exception {
ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
currentUserConfig);
int otherUserId = currentUserConfig.getUserId() + 1;
ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
.setUserId(otherUserId)
.setGeoDetectionEnabledSetting(true)
.build();
mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
reset(mFakeServiceConfigAccessorSpy);
final boolean bypassUserPolicyChecks = false;
ConfigurationInternal cachedConfigurationInternal =
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
assertEquals(currentUserConfig, cachedConfigurationInternal);
// Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
{
reset(mFakeServiceConfigAccessorSpy);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
currentUserConfig.getUserId(), bypassUserPolicyChecks);
verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
currentUserConfig.getUserId());
assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
actualCapabilitiesAndConfig.getCapabilities());
assertEquals(currentUserConfig.asConfiguration(),
actualCapabilitiesAndConfig.getConfiguration());
}
// Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
// the cached copy.
{
boolean newGeoDetectionEnabled =
!cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
.setGeoDetectionEnabled(newGeoDetectionEnabled)
.build();
ConfigurationInternal expectedConfigAfterChange =
new ConfigurationInternal.Builder(cachedConfigurationInternal)
.setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
.build();
reset(mFakeServiceConfigAccessorSpy);
mTimeZoneDetectorStrategy.updateConfiguration(
currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
assertEquals(expectedConfigAfterChange,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
}
// Not current user behavior: the strategy reads from the ServiceConfigAccessor.
@Test
public void testReadAndWriteConfiguration_otherUser() throws Exception {
ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
currentUserConfig);
int otherUserId = currentUserConfig.getUserId() + 1;
ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
.setUserId(otherUserId)
.setGeoDetectionEnabledSetting(true)
.build();
mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
reset(mFakeServiceConfigAccessorSpy);
final boolean bypassUserPolicyChecks = false;
// Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
{
reset(mFakeServiceConfigAccessorSpy);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
otherUserId, bypassUserPolicyChecks);
verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
actualCapabilitiesAndConfig.getCapabilities());
assertEquals(otherUserConfig.asConfiguration(),
actualCapabilitiesAndConfig.getConfiguration());
}
// Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
// touch the cached copy.
{
ConfigurationInternal cachedConfigBeforeChange =
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
boolean newGeoDetectionEnabled =
!otherUserConfig.asConfiguration().isGeoDetectionEnabled();
TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
.setGeoDetectionEnabled(newGeoDetectionEnabled)
.build();
reset(mFakeServiceConfigAccessorSpy);
mTimeZoneDetectorStrategy.updateConfiguration(
currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
assertEquals(cachedConfigBeforeChange,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
}
@Test
@@ -1201,19 +1425,13 @@ public class TimeZoneDetectorStrategyImplTest {
private final TestState<String> mTimeZoneId = new TestState<>();
private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
FakeEnvironment() {
// Ensure the fake environment starts with the defaults a fresh device would.
initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
}
void initializeConfig(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
@@ -1227,16 +1445,6 @@ public class TimeZoneDetectorStrategyImplTest {
mElapsedRealtimeMillis++;
}
@Override
public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
mConfigurationInternalChangeListener = listener;
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mConfigurationInternal;
}
@Override
public String getDeviceTimeZone() {
return mTimeZoneId.getLatest();
@@ -1254,11 +1462,6 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneConfidence.set(confidence);
}
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
mConfigurationInternalChangeListener.onChange();
}
void assertTimeZoneNotChanged() {
mTimeZoneId.assertHasNotBeenSet();
mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@ public class TimeZoneDetectorStrategyImplTest {
* Simulates the user / user's configuration changing.
*/
Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
configurationInternal);
return this;
}
@@ -1331,7 +1535,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
Script simulateSetAutoMode(boolean autoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@ public class TimeZoneDetectorStrategyImplTest {
@MatchType int matchType, @Quality int quality, int expectedScore) {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
private static class TestStateChangeListener implements StateChangeListener {
private int mNotificationsReceived;
@Override
public void onChange() {
mNotificationsReceived++;
}
public void resetNotificationsReceivedCount() {
mNotificationsReceived = 0;
}
public void assertNotificationsReceived(int expectedCount) {
assertEquals(expectedCount, mNotificationsReceived);
}
}
}

View File

@@ -42,7 +42,8 @@ final class TestSupport {
private static ConfigurationInternal createUserConfig(
@UserIdInt int userId, boolean geoDetectionEnabledSetting) {
return new ConfigurationInternal.Builder(userId)
return new ConfigurationInternal.Builder()
.setUserId(userId)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)