Merge "Implement user-scoped geolocation configuration"
This commit is contained in:
@@ -20,5 +20,5 @@ import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
|
||||
/** {@hide} */
|
||||
oneway interface ITimeZoneConfigurationListener {
|
||||
void onChange(in TimeZoneConfiguration configuration);
|
||||
void onChange();
|
||||
}
|
||||
@@ -32,17 +32,15 @@ import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
* this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
|
||||
* for more complete documentation.
|
||||
*
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface ITimeZoneDetectorService {
|
||||
TimeZoneCapabilities getCapabilities();
|
||||
|
||||
TimeZoneConfiguration getConfiguration();
|
||||
boolean updateConfiguration(in TimeZoneConfiguration configuration);
|
||||
void addConfigurationListener(ITimeZoneConfigurationListener listener);
|
||||
void removeConfigurationListener(ITimeZoneConfigurationListener listener);
|
||||
|
||||
boolean updateConfiguration(in TimeZoneConfiguration configuration);
|
||||
|
||||
boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
|
||||
void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
package android.app.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
|
||||
import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
@@ -38,9 +41,9 @@ import java.util.Objects;
|
||||
*
|
||||
* <p>Actions have associated methods, see the documentation for each action for details.
|
||||
*
|
||||
* <p>For configuration capabilities, the associated current configuration value can be retrieved
|
||||
* using {@link TimeZoneDetector#getConfiguration()} and may be changed using
|
||||
* {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)}.
|
||||
* <p>For configuration settings capabilities, the associated settings value can be found via
|
||||
* {@link #getConfiguration()} and may be changed using {@link
|
||||
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
|
||||
*
|
||||
* <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
|
||||
*
|
||||
@@ -60,7 +63,8 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
public static final int CAPABILITY_NOT_SUPPORTED = 10;
|
||||
|
||||
/**
|
||||
* Indicates that a capability is supported on this device, but not allowed for the user.
|
||||
* Indicates that a capability is supported on this device, but not allowed for the user, e.g.
|
||||
* if the capability relates to the ability to modify settings the user is not able to.
|
||||
* This could be because of the user's type (e.g. maybe it applies to the primary user only) or
|
||||
* device policy. Depending on the capability, this could mean the associated UI
|
||||
* should be hidden, or displayed but disabled.
|
||||
@@ -68,9 +72,11 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
public static final int CAPABILITY_NOT_ALLOWED = 20;
|
||||
|
||||
/**
|
||||
* Indicates that a capability is possessed but not applicable, e.g. if it is configuration,
|
||||
* the current configuration or device state renders it irrelevant. The associated UI may be
|
||||
* hidden, disabled, or left visible (but ineffective) depending on requirements.
|
||||
* Indicates that a capability is possessed but not currently applicable, e.g. if the
|
||||
* capability relates to the ability to modify settings, the user has the ability to modify
|
||||
* it, but it is currently rendered irrelevant by other settings or other device state (flags,
|
||||
* resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
|
||||
* ineffective) depending on requirements.
|
||||
*/
|
||||
public static final int CAPABILITY_NOT_APPLICABLE = 30;
|
||||
|
||||
@@ -89,13 +95,13 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
};
|
||||
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
@NonNull private final TimeZoneConfiguration mConfiguration;
|
||||
private final @CapabilityState int mConfigureAutoDetectionEnabled;
|
||||
private final @CapabilityState int mConfigureGeoDetectionEnabled;
|
||||
private final @CapabilityState int mSuggestManualTimeZone;
|
||||
|
||||
private TimeZoneCapabilities(@NonNull Builder builder) {
|
||||
this.mUserId = builder.mUserId;
|
||||
this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
|
||||
this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
|
||||
this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
|
||||
this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
|
||||
@@ -103,7 +109,8 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
|
||||
@NonNull
|
||||
private static TimeZoneCapabilities createFromParcel(Parcel in) {
|
||||
return new TimeZoneCapabilities.Builder(in.readInt())
|
||||
return new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(in.readParcelable(null))
|
||||
.setConfigureAutoDetectionEnabled(in.readInt())
|
||||
.setConfigureGeoDetectionEnabled(in.readInt())
|
||||
.setSuggestManualTimeZone(in.readInt())
|
||||
@@ -112,21 +119,24 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mUserId);
|
||||
dest.writeParcelable(mConfiguration, flags);
|
||||
dest.writeInt(mConfigureAutoDetectionEnabled);
|
||||
dest.writeInt(mConfigureGeoDetectionEnabled);
|
||||
dest.writeInt(mSuggestManualTimeZone);
|
||||
}
|
||||
|
||||
/** Returns the user ID the capabilities are for. */
|
||||
public @UserIdInt int getUserId() {
|
||||
return mUserId;
|
||||
/**
|
||||
* Returns the user's time zone behavior configuration.
|
||||
*/
|
||||
public @NonNull TimeZoneConfiguration getConfiguration() {
|
||||
return mConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user's capability state for controlling whether automatic time zone detection is
|
||||
* enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
|
||||
* TimeZoneConfiguration#isAutoDetectionEnabled()}.
|
||||
* Returns the capability state associated with the user's ability to modify the automatic time
|
||||
* zone detection setting. The setting can be updated via {@link
|
||||
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
|
||||
* #getConfiguration()}.
|
||||
*/
|
||||
@CapabilityState
|
||||
public int getConfigureAutoDetectionEnabled() {
|
||||
@@ -134,9 +144,10 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user's capability state for controlling whether geolocation can be used to detect
|
||||
* time zone via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
|
||||
* TimeZoneConfiguration#isGeoDetectionEnabled()}.
|
||||
* Returns the capability state associated with the user's ability to modify the geolocation
|
||||
* detection setting. The setting can be updated via {@link
|
||||
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
|
||||
* #getConfiguration()}.
|
||||
*/
|
||||
@CapabilityState
|
||||
public int getConfigureGeoDetectionEnabled() {
|
||||
@@ -144,8 +155,8 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user's capability state for manually setting the time zone on a device via
|
||||
* {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
|
||||
* Returns the capability state associated with the user's ability to manually set the time zone
|
||||
* on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
|
||||
*
|
||||
* <p>The suggestion will be ignored in all cases unless the value is {@link
|
||||
* #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
|
||||
@@ -155,6 +166,38 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
return mSuggestManualTimeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
|
||||
* {@code requestedChanges}, if the current capabilities allow. The new configuration is
|
||||
* returned and the capabilities are left unchanged. If the capabilities do not permit one or
|
||||
* more of the changes then {@code null} is returned.
|
||||
*/
|
||||
@Nullable
|
||||
public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
|
||||
if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
|
||||
throw new IllegalArgumentException("User does not match:"
|
||||
+ " this=" + mConfiguration + ", other=" + requestedChanges);
|
||||
}
|
||||
|
||||
TimeZoneConfiguration.Builder newConfigBuilder =
|
||||
new TimeZoneConfiguration.Builder(mConfiguration);
|
||||
if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
|
||||
if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
|
||||
return null;
|
||||
}
|
||||
newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
|
||||
}
|
||||
|
||||
if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
|
||||
if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
|
||||
return null;
|
||||
}
|
||||
newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
|
||||
}
|
||||
|
||||
return newConfigBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -169,7 +212,7 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
TimeZoneCapabilities that = (TimeZoneCapabilities) o;
|
||||
return mUserId == that.mUserId
|
||||
return Objects.equals(mConfiguration, that.mConfiguration)
|
||||
&& mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
|
||||
&& mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
|
||||
&& mSuggestManualTimeZone == that.mSuggestManualTimeZone;
|
||||
@@ -177,7 +220,7 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUserId,
|
||||
return Objects.hash(mConfiguration,
|
||||
mConfigureAutoDetectionEnabled,
|
||||
mConfigureGeoDetectionEnabled,
|
||||
mSuggestManualTimeZone);
|
||||
@@ -186,7 +229,7 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimeZoneDetectorCapabilities{"
|
||||
+ "mUserId=" + mUserId
|
||||
+ "mConfiguration=" + mConfiguration
|
||||
+ ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
|
||||
+ ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
|
||||
+ ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
|
||||
@@ -196,16 +239,18 @@ public final class TimeZoneCapabilities implements Parcelable {
|
||||
/** @hide */
|
||||
public static class Builder {
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
private TimeZoneConfiguration mConfiguration;
|
||||
private @CapabilityState int mConfigureAutoDetectionEnabled;
|
||||
private @CapabilityState int mConfigureGeoDetectionEnabled;
|
||||
private @CapabilityState int mSuggestManualTimeZone;
|
||||
|
||||
/**
|
||||
* Creates a new Builder with no properties set.
|
||||
*/
|
||||
public Builder(@UserIdInt int userId) {
|
||||
mUserId = userId;
|
||||
/** Sets the user-visible configuration settings. */
|
||||
public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
|
||||
if (!configuration.isComplete()) {
|
||||
throw new IllegalArgumentException(configuration + " is not complete");
|
||||
}
|
||||
this.mConfiguration = configuration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the state for the automatic time zone detection enabled config. */
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.app.timezonedetector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.StringDef;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -27,21 +28,20 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Configuration that controls the behavior of the time zone detector associated with a specific
|
||||
* user.
|
||||
* User visible settings that control the behavior of the time zone detector / manual time zone
|
||||
* entry.
|
||||
*
|
||||
* <p>Configuration consists of a set of known properties. When reading configuration via
|
||||
* {@link TimeZoneDetector#getConfiguration()} values for all known properties will be provided. In
|
||||
* some cases, such as when the configuration relies on optional hardware, the values may be
|
||||
* meaningless / defaulted to safe values.
|
||||
* <p>When reading the configuration, values for all settings will be provided. In some cases, such
|
||||
* as when the device behavior relies on optional hardware / OEM configuration, or the value of
|
||||
* several settings, the device behavior may not be directly affected by the setting value.
|
||||
*
|
||||
* <p>Configuration properties can be left absent when updating configuration via {@link
|
||||
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those values will not be
|
||||
* changed. Not all configuration properties can be modified by all users. See {@link
|
||||
* TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities}.
|
||||
* <p>Settings can be left absent when updating configuration via {@link
|
||||
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
|
||||
* changed. Not all configuration settings can be modified by all users: see {@link
|
||||
* TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
|
||||
*
|
||||
* <p>See {@link #isComplete()} to tell if all known properties are present, and {@link
|
||||
* #hasProperty(String)} with {@code PROPERTY_} constants for testing individual properties.
|
||||
* <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
|
||||
* of individual settings.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -59,80 +59,82 @@ public final class TimeZoneConfiguration implements Parcelable {
|
||||
};
|
||||
|
||||
/** All configuration properties */
|
||||
@StringDef(PROPERTY_AUTO_DETECTION_ENABLED)
|
||||
@StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface Property {}
|
||||
@interface Setting {}
|
||||
|
||||
/** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
|
||||
@Property
|
||||
public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
|
||||
@Setting
|
||||
public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
|
||||
|
||||
/** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
|
||||
@Property
|
||||
public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
|
||||
@Setting
|
||||
public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
|
||||
|
||||
private final Bundle mBundle;
|
||||
private final @UserIdInt int mUserId;
|
||||
@NonNull private final Bundle mBundle;
|
||||
|
||||
private TimeZoneConfiguration(Builder builder) {
|
||||
this.mBundle = builder.mBundle;
|
||||
this.mUserId = builder.mUserId;
|
||||
this.mBundle = Objects.requireNonNull(builder.mBundle);
|
||||
}
|
||||
|
||||
private static TimeZoneConfiguration createFromParcel(Parcel in) {
|
||||
return new TimeZoneConfiguration.Builder()
|
||||
return new TimeZoneConfiguration.Builder(in.readInt())
|
||||
.setPropertyBundleInternal(in.readBundle())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mUserId);
|
||||
dest.writeBundle(mBundle);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if all known properties are set. */
|
||||
public boolean isComplete() {
|
||||
return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
|
||||
&& hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
|
||||
/** Returns the ID of the user this configuration is associated with. */
|
||||
public @UserIdInt int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
/** Returns true if the specified property is set. */
|
||||
public boolean hasProperty(@Property String property) {
|
||||
return mBundle.containsKey(property);
|
||||
/** Returns {@code true} if all known settings are present. */
|
||||
public boolean isComplete() {
|
||||
return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
|
||||
&& hasSetting(SETTING_GEO_DETECTION_ENABLED);
|
||||
}
|
||||
|
||||
/** Returns true if the specified setting is set. */
|
||||
public boolean hasSetting(@Setting String setting) {
|
||||
return mBundle.containsKey(setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@link #PROPERTY_AUTO_DETECTION_ENABLED} property. This
|
||||
* Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This
|
||||
* controls whether a device will attempt to determine the time zone automatically using
|
||||
* contextual information.
|
||||
* contextual information if the device supports auto detection.
|
||||
*
|
||||
* @throws IllegalStateException if the field has not been set
|
||||
* <p>This setting is global and can be updated by some users.
|
||||
*
|
||||
* @throws IllegalStateException if the setting has not been set
|
||||
*/
|
||||
public boolean isAutoDetectionEnabled() {
|
||||
if (!mBundle.containsKey(PROPERTY_AUTO_DETECTION_ENABLED)) {
|
||||
throw new IllegalStateException(PROPERTY_AUTO_DETECTION_ENABLED + " is not set");
|
||||
}
|
||||
return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
|
||||
enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
|
||||
return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@link #PROPERTY_GEO_DETECTION_ENABLED} property. This
|
||||
* controls whether a device can use location to determine time zone. Only used when
|
||||
* {@link #isAutoDetectionEnabled()} is true.
|
||||
* Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
|
||||
* controls whether a device can use geolocation to determine time zone. Only used when
|
||||
* {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
|
||||
* location to be used.
|
||||
*
|
||||
* @throws IllegalStateException if the field has not been set
|
||||
* <p>This setting is user-scoped and can be updated by some users.
|
||||
* See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
|
||||
*
|
||||
* @throws IllegalStateException if the setting has not been set
|
||||
*/
|
||||
public boolean isGeoDetectionEnabled() {
|
||||
if (!mBundle.containsKey(PROPERTY_GEO_DETECTION_ENABLED)) {
|
||||
throw new IllegalStateException(PROPERTY_GEO_DETECTION_ENABLED + " is not set");
|
||||
}
|
||||
return mBundle.getBoolean(PROPERTY_GEO_DETECTION_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to merge this with another. The argument configuration properties have
|
||||
* precedence.
|
||||
*/
|
||||
public TimeZoneConfiguration with(TimeZoneConfiguration other) {
|
||||
return new Builder(this).mergeProperties(other).build();
|
||||
enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
|
||||
return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -149,43 +151,61 @@ public final class TimeZoneConfiguration implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
TimeZoneConfiguration that = (TimeZoneConfiguration) o;
|
||||
return mBundle.kindofEquals(that.mBundle);
|
||||
return mUserId == that.mUserId
|
||||
&& mBundle.kindofEquals(that.mBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mBundle);
|
||||
return Objects.hash(mUserId, mBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimeZoneDetectorConfiguration{"
|
||||
+ "mUserId=" + mUserId
|
||||
+ "mBundle=" + mBundle
|
||||
+ '}';
|
||||
}
|
||||
|
||||
private void enforceSettingPresent(@Setting String setting) {
|
||||
if (!mBundle.containsKey(setting)) {
|
||||
throw new IllegalStateException(setting + " is not set");
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static class Builder {
|
||||
|
||||
private Bundle mBundle = new Bundle();
|
||||
private final @UserIdInt int mUserId;
|
||||
private final Bundle mBundle = new Bundle();
|
||||
|
||||
/**
|
||||
* Creates a new Builder with no properties set.
|
||||
* Creates a new Builder for a userId with no settings held.
|
||||
*/
|
||||
public Builder() {}
|
||||
public Builder(@UserIdInt int userId) {
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder by copying properties from an existing instance.
|
||||
* Creates a new Builder by copying the user ID and settings from an existing instance.
|
||||
*/
|
||||
public Builder(TimeZoneConfiguration toCopy) {
|
||||
this.mUserId = toCopy.mUserId;
|
||||
mergeProperties(toCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges {@code other} properties into this instances, replacing existing values in this
|
||||
* where the properties appear in both.
|
||||
* Merges {@code other} settings into this instances, replacing existing values in this
|
||||
* where the settings appear in both.
|
||||
*/
|
||||
public Builder mergeProperties(TimeZoneConfiguration other) {
|
||||
if (mUserId != other.mUserId) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot merge configurations for different user IDs."
|
||||
+ " this.mUserId=" + this.mUserId
|
||||
+ ", other.mUserId=" + other.mUserId);
|
||||
}
|
||||
this.mBundle.putAll(other.mBundle);
|
||||
return this;
|
||||
}
|
||||
@@ -195,15 +215,19 @@ public final class TimeZoneConfiguration implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the desired state of the automatic time zone detection property. */
|
||||
/**
|
||||
* Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
|
||||
*/
|
||||
public Builder setAutoDetectionEnabled(boolean enabled) {
|
||||
this.mBundle.putBoolean(PROPERTY_AUTO_DETECTION_ENABLED, enabled);
|
||||
this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the desired state of the geolocation time zone detection enabled property. */
|
||||
/**
|
||||
* Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
|
||||
*/
|
||||
public Builder setGeoDetectionEnabled(boolean enabled) {
|
||||
this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
|
||||
this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,38 +36,30 @@ public interface TimeZoneDetector {
|
||||
@NonNull
|
||||
TimeZoneCapabilities getCapabilities();
|
||||
|
||||
/**
|
||||
* Returns the current user's complete time zone configuration. See {@link
|
||||
* TimeZoneConfiguration}.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|
||||
@NonNull
|
||||
TimeZoneConfiguration getConfiguration();
|
||||
|
||||
/**
|
||||
* Modifies the time zone detection configuration.
|
||||
*
|
||||
* <p>Configuration properties vary in scope: some may be device-wide, others may be specific to
|
||||
* the current user.
|
||||
* <p>Configuration settings vary in scope: some may be global (affect all users), others may be
|
||||
* specific to the current user.
|
||||
*
|
||||
* <p>The ability to modify configuration properties can be subject to restrictions. For
|
||||
* <p>The ability to modify configuration settings can be subject to restrictions. For
|
||||
* example, they may be determined by device hardware, general policy (i.e. only the primary
|
||||
* user can set them), or by a managed device policy. See {@link #getCapabilities()} to obtain
|
||||
* user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
|
||||
* information at runtime about the user's capabilities.
|
||||
*
|
||||
* <p>Attempts to set configuration with capabilities that are {@link
|
||||
* <p>Attempts to modify configuration settings with capabilities that are {@link
|
||||
* TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
|
||||
* TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
|
||||
* will be returned. Setting configuration with capabilities that are {@link
|
||||
* will be returned. Modifying configuration settings with capabilities that are {@link
|
||||
* TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
|
||||
* TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
|
||||
* TimeZoneCapabilities} for further details.
|
||||
*
|
||||
* <p>If the configuration is not "complete", then only the specified properties will be
|
||||
* updated (where the user's capabilities allow) and other settings will be left unchanged. See
|
||||
* {@link TimeZoneConfiguration#isComplete()}.
|
||||
* <p>If the supplied configuration only has some values set, then only the specified settings
|
||||
* will be updated (where the user's capabilities allow) and other settings will be left
|
||||
* unchanged.
|
||||
*
|
||||
* @return {@code true} if all the configuration properties specified have been set to the
|
||||
* @return {@code true} if all the configuration settings specified have been set to the
|
||||
* new values, {@code false} if none have
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|
||||
@@ -76,14 +68,20 @@ public interface TimeZoneDetector {
|
||||
/**
|
||||
* An interface that can be used to listen for changes to the time zone detector configuration.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface TimeZoneConfigurationListener {
|
||||
/** Called when the configuration changes. There are no guarantees about the thread used. */
|
||||
void onChange(@NonNull TimeZoneConfiguration configuration);
|
||||
/**
|
||||
* Called when something about the time zone configuration on the device has changed.
|
||||
* This could be because the current user has changed, one of the device's relevant settings
|
||||
* has changed, or something that could affect a user's capabilities has changed.
|
||||
* There are no guarantees about the thread used.
|
||||
*/
|
||||
void onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener that will be informed when the configuration changes. The complete
|
||||
* configuration is passed to the listener, not just the properties that have changed.
|
||||
* Registers a listener that will be informed when something about the time zone configuration
|
||||
* changes.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|
||||
void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
|
||||
|
||||
@@ -56,19 +56,6 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public TimeZoneConfiguration getConfiguration() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getConfiguration called");
|
||||
}
|
||||
try {
|
||||
return mITimeZoneDetectorService.getConfiguration();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
|
||||
if (DEBUG) {
|
||||
@@ -94,8 +81,8 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
|
||||
ITimeZoneConfigurationListener iListener =
|
||||
new ITimeZoneConfigurationListener.Stub() {
|
||||
@Override
|
||||
public void onChange(@NonNull TimeZoneConfiguration configuration) {
|
||||
notifyConfigurationListeners(configuration);
|
||||
public void onChange() {
|
||||
notifyConfigurationListeners();
|
||||
}
|
||||
};
|
||||
mConfigurationReceiver = iListener;
|
||||
@@ -116,14 +103,14 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) {
|
||||
private void notifyConfigurationListeners() {
|
||||
final ArraySet<TimeZoneConfigurationListener> configurationListeners;
|
||||
synchronized (this) {
|
||||
configurationListeners = new ArraySet<>(mConfigurationListeners);
|
||||
}
|
||||
int size = configurationListeners.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
configurationListeners.valueAt(i).onChange(configuration);
|
||||
configurationListeners.valueAt(i).onChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6403,6 +6403,17 @@ public final class Settings {
|
||||
@SystemApi
|
||||
public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
|
||||
|
||||
/**
|
||||
* The current location time zone detection enabled state for the user.
|
||||
*
|
||||
* See {@link
|
||||
* android.app.timezonedetector.TimeZoneDetector#getCapabilities} for access. See {@link
|
||||
* android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update.
|
||||
* @hide
|
||||
*/
|
||||
public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED =
|
||||
"location_time_zone_detection_enabled";
|
||||
|
||||
/**
|
||||
* The accuracy in meters used for coarsening location for clients with only the coarse
|
||||
* location permission.
|
||||
|
||||
@@ -22,6 +22,7 @@ import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSE
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -31,11 +32,22 @@ public class TimeZoneCapabilitiesTest {
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
|
||||
TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.setGeoDetectionEnabled(false)
|
||||
.build();
|
||||
|
||||
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(configuration1)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
|
||||
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
|
||||
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(configuration1)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
|
||||
@@ -45,6 +57,20 @@ public class TimeZoneCapabilitiesTest {
|
||||
assertEquals(one, two);
|
||||
}
|
||||
|
||||
builder2.setConfiguration(configuration2);
|
||||
{
|
||||
TimeZoneCapabilities one = builder1.build();
|
||||
TimeZoneCapabilities two = builder2.build();
|
||||
assertNotEquals(one, two);
|
||||
}
|
||||
|
||||
builder1.setConfiguration(configuration2);
|
||||
{
|
||||
TimeZoneCapabilities one = builder1.build();
|
||||
TimeZoneCapabilities two = builder2.build();
|
||||
assertEquals(one, two);
|
||||
}
|
||||
|
||||
builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
|
||||
{
|
||||
TimeZoneCapabilities one = builder1.build();
|
||||
@@ -90,7 +116,12 @@ public class TimeZoneCapabilitiesTest {
|
||||
|
||||
@Test
|
||||
public void testParcelable() {
|
||||
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
|
||||
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(configuration)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
|
||||
@@ -105,4 +136,51 @@ public class TimeZoneCapabilitiesTest {
|
||||
builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
|
||||
assertRoundTripParcelable(builder.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyUpdate_permitted() {
|
||||
TimeZoneConfiguration oldConfiguration =
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(oldConfiguration)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_POSSESSED)
|
||||
.build();
|
||||
assertEquals(oldConfiguration, capabilities.getConfiguration());
|
||||
|
||||
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
|
||||
TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
assertEquals(expected, capabilities.applyUpdate(configChange));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyUpdate_notPermitted() {
|
||||
TimeZoneConfiguration oldConfiguration =
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(oldConfiguration)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
|
||||
.build();
|
||||
assertEquals(oldConfiguration, capabilities.getConfiguration());
|
||||
|
||||
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
|
||||
assertNull(capabilities.applyUpdate(configChange));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,14 @@ import org.junit.Test;
|
||||
|
||||
public class TimeZoneConfigurationTest {
|
||||
|
||||
private static final int ARBITRARY_USER_ID = 9876;
|
||||
|
||||
@Test
|
||||
public void testBuilder_copyConstructor() {
|
||||
TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true);
|
||||
TimeZoneConfiguration.Builder builder1 =
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true);
|
||||
TimeZoneConfiguration configuration1 = builder1.build();
|
||||
|
||||
TimeZoneConfiguration configuration2 =
|
||||
@@ -41,28 +44,28 @@ public class TimeZoneConfigurationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsComplete() {
|
||||
TimeZoneConfiguration.Builder builder =
|
||||
new TimeZoneConfiguration.Builder();
|
||||
assertFalse(builder.build().isComplete());
|
||||
public void testIntrospectionMethods() {
|
||||
TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
|
||||
assertFalse(empty.isComplete());
|
||||
assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
|
||||
|
||||
builder.setAutoDetectionEnabled(true);
|
||||
assertFalse(builder.build().isComplete());
|
||||
|
||||
builder.setGeoDetectionEnabled(true);
|
||||
assertTrue(builder.build().isComplete());
|
||||
TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
assertTrue(completeConfig.isComplete());
|
||||
assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilder_mergeProperties() {
|
||||
TimeZoneConfiguration configuration1 =
|
||||
new TimeZoneConfiguration.Builder()
|
||||
.setAutoDetectionEnabled(true)
|
||||
.build();
|
||||
TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.build();
|
||||
|
||||
{
|
||||
TimeZoneConfiguration mergedEmptyAnd1 =
|
||||
new TimeZoneConfiguration.Builder()
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.mergeProperties(configuration1)
|
||||
.build();
|
||||
assertEquals(configuration1, mergedEmptyAnd1);
|
||||
@@ -70,7 +73,7 @@ public class TimeZoneConfigurationTest {
|
||||
|
||||
{
|
||||
TimeZoneConfiguration configuration2 =
|
||||
new TimeZoneConfiguration.Builder()
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
|
||||
@@ -87,14 +90,22 @@ public class TimeZoneConfigurationTest {
|
||||
@Test
|
||||
public void testEquals() {
|
||||
TimeZoneConfiguration.Builder builder1 =
|
||||
new TimeZoneConfiguration.Builder();
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
|
||||
{
|
||||
TimeZoneConfiguration one = builder1.build();
|
||||
assertEquals(one, one);
|
||||
}
|
||||
|
||||
{
|
||||
TimeZoneConfiguration.Builder differentUserBuilder =
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
|
||||
TimeZoneConfiguration one = builder1.build();
|
||||
TimeZoneConfiguration two = differentUserBuilder.build();
|
||||
assertNotEquals(one, two);
|
||||
}
|
||||
|
||||
TimeZoneConfiguration.Builder builder2 =
|
||||
new TimeZoneConfiguration.Builder();
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
|
||||
{
|
||||
TimeZoneConfiguration one = builder1.build();
|
||||
TimeZoneConfiguration two = builder2.build();
|
||||
@@ -148,7 +159,7 @@ public class TimeZoneConfigurationTest {
|
||||
@Test
|
||||
public void testParcelable() {
|
||||
TimeZoneConfiguration.Builder builder =
|
||||
new TimeZoneConfiguration.Builder();
|
||||
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
|
||||
assertRoundTripParcelable(builder.build());
|
||||
|
||||
builder.setAutoDetectionEnabled(true);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 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 android.annotation.UserIdInt;
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
|
||||
/**
|
||||
* An interface to wrap various difficult-to-intercept calls that services make to access / manage
|
||||
* caller identity, e.g. {@link Binder#clearCallingIdentity()}.
|
||||
*/
|
||||
public interface CallerIdentityInjector {
|
||||
|
||||
/** A singleton for the real implementation of {@link CallerIdentityInjector}. */
|
||||
CallerIdentityInjector REAL = new Real();
|
||||
|
||||
/** A {@link UserHandle#getCallingUserId()} call. */
|
||||
@UserIdInt int getCallingUserId();
|
||||
|
||||
/** A {@link Binder#clearCallingIdentity()} call. */
|
||||
long clearCallingIdentity();
|
||||
|
||||
/** A {@link Binder#restoreCallingIdentity(long)} ()} call. */
|
||||
void restoreCallingIdentity(long token);
|
||||
|
||||
/** The real implementation of {@link CallerIdentityInjector}. */
|
||||
class Real implements CallerIdentityInjector {
|
||||
|
||||
protected Real() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCallingUserId() {
|
||||
return UserHandle.getCallingUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clearCallingIdentity() {
|
||||
return Binder.clearCallingIdentity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreCallingIdentity(long token) {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 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;
|
||||
|
||||
/**
|
||||
* A listener used to receive notification that time zone configuration has changed.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ConfigurationChangeListener {
|
||||
/** Called when the current user or a configuration value has changed. */
|
||||
void onChange();
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Holds all configuration values that affect time zone behavior and some associated logic, e.g.
|
||||
* {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
|
||||
* #createCapabilities()}.
|
||||
*/
|
||||
public final class ConfigurationInternal {
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
private final boolean mUserConfigAllowed;
|
||||
private final boolean mAutoDetectionSupported;
|
||||
private final boolean mAutoDetectionEnabled;
|
||||
private final boolean mLocationEnabled;
|
||||
private final boolean mGeoDetectionEnabled;
|
||||
|
||||
private ConfigurationInternal(Builder builder) {
|
||||
mUserId = builder.mUserId;
|
||||
mUserConfigAllowed = builder.mUserConfigAllowed;
|
||||
mAutoDetectionSupported = builder.mAutoDetectionSupported;
|
||||
mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
|
||||
mLocationEnabled = builder.mLocationEnabled;
|
||||
mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
|
||||
}
|
||||
|
||||
/** Returns the ID of the user this configuration is associated with. */
|
||||
public @UserIdInt int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
/** Returns true if the user allowed to modify time zone configuration. */
|
||||
public boolean isUserConfigAllowed() {
|
||||
return mUserConfigAllowed;
|
||||
}
|
||||
|
||||
/** Returns true if the device supports some form of auto time zone detection. */
|
||||
public boolean isAutoDetectionSupported() {
|
||||
return mAutoDetectionSupported;
|
||||
}
|
||||
|
||||
/** Returns the value of the auto time zone detection enabled setting. */
|
||||
public boolean getAutoDetectionEnabledSetting() {
|
||||
return mAutoDetectionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if auto time zone detection behavior is actually enabled, which can be distinct
|
||||
* from the raw setting value. */
|
||||
public boolean getAutoDetectionEnabledBehavior() {
|
||||
return mAutoDetectionSupported && mAutoDetectionEnabled;
|
||||
}
|
||||
|
||||
/** Returns true if user's location can be used generally. */
|
||||
public boolean isLocationEnabled() {
|
||||
return mLocationEnabled;
|
||||
}
|
||||
|
||||
/** Returns the value of the geolocation time zone detection enabled setting. */
|
||||
public boolean getGeoDetectionEnabledSetting() {
|
||||
return mGeoDetectionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if geolocation time zone detection behavior is actually enabled, which can be
|
||||
* distinct from the raw setting value.
|
||||
*/
|
||||
public boolean getGeoDetectionEnabledBehavior() {
|
||||
if (getAutoDetectionEnabledBehavior()) {
|
||||
return mLocationEnabled && mGeoDetectionEnabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
|
||||
public TimeZoneCapabilities createCapabilities() {
|
||||
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
|
||||
.setConfiguration(asConfiguration());
|
||||
|
||||
boolean allowConfigDateTime = isUserConfigAllowed();
|
||||
|
||||
// Automatic time zone detection is only supported on devices if there is a telephony
|
||||
// network available or geolocation time zone detection is possible.
|
||||
boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
|
||||
|
||||
final int configureAutoDetectionEnabledCapability;
|
||||
if (!deviceHasTimeZoneDetection) {
|
||||
configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
|
||||
} else if (!allowConfigDateTime) {
|
||||
configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
|
||||
} else {
|
||||
configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
|
||||
}
|
||||
builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
|
||||
|
||||
final int configureGeolocationDetectionEnabledCapability;
|
||||
if (!deviceHasTimeZoneDetection) {
|
||||
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
|
||||
} else if (!allowConfigDateTime) {
|
||||
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
|
||||
} else if (!isLocationEnabled()) {
|
||||
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
|
||||
} else {
|
||||
configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
|
||||
}
|
||||
builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
|
||||
|
||||
// The ability to make manual time zone suggestions can also be restricted by policy. With
|
||||
// the current logic above, this could lead to a situation where a device hardware does not
|
||||
// support auto detection, the device has been forced into "auto" mode by an admin and the
|
||||
// user is unable to disable auto detection.
|
||||
final int suggestManualTimeZoneCapability;
|
||||
if (!allowConfigDateTime) {
|
||||
suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED;
|
||||
} else if (getAutoDetectionEnabledBehavior()) {
|
||||
suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE;
|
||||
} else {
|
||||
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
|
||||
}
|
||||
builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
|
||||
public TimeZoneConfiguration asConfiguration() {
|
||||
return new TimeZoneConfiguration.Builder(mUserId)
|
||||
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
|
||||
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the configuration values from this with any properties set in {@code
|
||||
* newConfiguration}. The new configuration has precedence. Used to apply user updates to
|
||||
* internal configuration.
|
||||
*/
|
||||
public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
|
||||
Builder builder = new Builder(this);
|
||||
if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
|
||||
builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
|
||||
}
|
||||
if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
|
||||
builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ConfigurationInternal that = (ConfigurationInternal) o;
|
||||
return mUserId == that.mUserId
|
||||
&& mUserConfigAllowed == that.mUserConfigAllowed
|
||||
&& mAutoDetectionSupported == that.mAutoDetectionSupported
|
||||
&& mAutoDetectionEnabled == that.mAutoDetectionEnabled
|
||||
&& mLocationEnabled == that.mLocationEnabled
|
||||
&& mGeoDetectionEnabled == that.mGeoDetectionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
|
||||
mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimeZoneDetectorConfiguration{"
|
||||
+ "mUserId=" + mUserId
|
||||
+ "mUserConfigAllowed=" + mUserConfigAllowed
|
||||
+ "mAutoDetectionSupported=" + mAutoDetectionSupported
|
||||
+ "mAutoDetectionEnabled=" + mAutoDetectionEnabled
|
||||
+ "mLocationEnabled=" + mLocationEnabled
|
||||
+ "mGeoDetectionEnabled=" + mGeoDetectionEnabled
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* A Builder for {@link ConfigurationInternal}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
private boolean mUserConfigAllowed;
|
||||
private boolean mAutoDetectionSupported;
|
||||
private boolean mAutoDetectionEnabled;
|
||||
private boolean mLocationEnabled;
|
||||
private boolean mGeoDetectionEnabled;
|
||||
|
||||
/**
|
||||
* Creates a new Builder with only the userId set.
|
||||
*/
|
||||
public Builder(@UserIdInt int userId) {
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder by copying values from an existing instance.
|
||||
*/
|
||||
public Builder(ConfigurationInternal toCopy) {
|
||||
this.mUserId = toCopy.mUserId;
|
||||
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
|
||||
this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
|
||||
this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
|
||||
this.mLocationEnabled = toCopy.mLocationEnabled;
|
||||
this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the user is allowed to configure time zone settings on this device.
|
||||
*/
|
||||
public Builder setUserConfigAllowed(boolean configAllowed) {
|
||||
mUserConfigAllowed = configAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether automatic time zone detection is supported on this device.
|
||||
*/
|
||||
public Builder setAutoDetectionSupported(boolean supported) {
|
||||
mAutoDetectionSupported = supported;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the automatic time zone detection enabled setting for this device.
|
||||
*/
|
||||
public Builder setAutoDetectionEnabled(boolean enabled) {
|
||||
mAutoDetectionEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the location mode setting for this user.
|
||||
*/
|
||||
public Builder setLocationEnabled(boolean enabled) {
|
||||
mLocationEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the geolocation time zone detection setting for this user.
|
||||
*/
|
||||
public Builder setGeoDetectionEnabled(boolean enabled) {
|
||||
mGeoDetectionEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new {@link ConfigurationInternal}. */
|
||||
@NonNull
|
||||
public ConfigurationInternal build() {
|
||||
return new ConfigurationInternal(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,24 +16,30 @@
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
|
||||
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.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.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.net.ConnectivityManager;
|
||||
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.server.LocalServices;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -42,103 +48,87 @@ import java.util.Objects;
|
||||
*/
|
||||
public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
|
||||
|
||||
private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
|
||||
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
|
||||
|
||||
private final Context mContext;
|
||||
private final ContentResolver mCr;
|
||||
private final UserManager mUserManager;
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final ContentResolver mCr;
|
||||
@NonNull private final UserManager mUserManager;
|
||||
@NonNull private final boolean mGeoDetectionFeatureEnabled;
|
||||
@NonNull private final LocationManager mLocationManager;
|
||||
// @NonNull after setConfigChangeListener() is called.
|
||||
private ConfigurationChangeListener mConfigChangeListener;
|
||||
|
||||
TimeZoneDetectorCallbackImpl(Context context) {
|
||||
mContext = context;
|
||||
TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
|
||||
boolean geoDetectionFeatureEnabled) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
mHandler = Objects.requireNonNull(handler);
|
||||
mCr = context.getContentResolver();
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
mLocationManager = context.getSystemService(LocationManager.class);
|
||||
mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
|
||||
|
||||
// Wire up the change listener. All 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();
|
||||
contentResolver.registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
|
||||
new ContentObserver(mHandler) {
|
||||
public void onChange(boolean selfChange) {
|
||||
handleConfigChangeOnHandlerThread();
|
||||
}
|
||||
});
|
||||
|
||||
// Add async callbacks for user scoped location settings being changed.
|
||||
contentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
|
||||
true,
|
||||
new ContentObserver(mHandler) {
|
||||
public void onChange(boolean selfChange) {
|
||||
handleConfigChangeOnHandlerThread();
|
||||
}
|
||||
}, UserHandle.USER_ALL);
|
||||
}
|
||||
|
||||
private void handleConfigChangeOnHandlerThread() {
|
||||
if (mConfigChangeListener == null) {
|
||||
Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
|
||||
}
|
||||
mConfigChangeListener.onChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
|
||||
UserHandle userHandle = UserHandle.of(userId);
|
||||
boolean disallowConfigDateTime =
|
||||
mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
|
||||
|
||||
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userId);
|
||||
|
||||
// Automatic time zone detection is only supported (currently) on devices if there is a
|
||||
// telephony network available.
|
||||
if (!deviceHasTelephonyNetwork()) {
|
||||
builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
|
||||
} else if (disallowConfigDateTime) {
|
||||
builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
|
||||
} else {
|
||||
builder.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED);
|
||||
}
|
||||
|
||||
// TODO(b/149014708) Replace this with real logic when the settings storage is fully
|
||||
// implemented.
|
||||
builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
|
||||
|
||||
// The ability to make manual time zone suggestions can also be restricted by policy. With
|
||||
// the current logic above, this could lead to a situation where a device hardware does not
|
||||
// support auto detection, the device has been forced into "auto" mode by an admin and the
|
||||
// user is unable to disable auto detection.
|
||||
if (disallowConfigDateTime) {
|
||||
builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
|
||||
} else if (isAutoDetectionEnabled()) {
|
||||
builder.setSuggestManualTimeZone(CAPABILITY_NOT_APPLICABLE);
|
||||
} else {
|
||||
builder.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
|
||||
}
|
||||
return builder.build();
|
||||
public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
|
||||
mConfigChangeListener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
|
||||
return new TimeZoneConfiguration.Builder()
|
||||
public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
|
||||
return new ConfigurationInternal.Builder(userId)
|
||||
.setUserConfigAllowed(isUserConfigAllowed(userId))
|
||||
.setAutoDetectionSupported(isAutoDetectionSupported())
|
||||
.setAutoDetectionEnabled(isAutoDetectionEnabled())
|
||||
.setGeoDetectionEnabled(isGeoDetectionEnabled())
|
||||
.setLocationEnabled(isLocationEnabled(userId))
|
||||
.setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(
|
||||
@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
|
||||
Objects.requireNonNull(configuration);
|
||||
if (!configuration.isComplete()) {
|
||||
throw new IllegalArgumentException("configuration=" + configuration + " not complete");
|
||||
}
|
||||
|
||||
// Avoid writing auto detection config for devices that do not support auto time zone
|
||||
// detection: if we wrote it down then we'd set the default explicitly. That might influence
|
||||
// what happens on later releases that do support auto detection on the same hardware.
|
||||
if (isAutoDetectionSupported()) {
|
||||
final int autoEnabledValue = configuration.isAutoDetectionEnabled() ? 1 : 0;
|
||||
Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, autoEnabledValue);
|
||||
|
||||
final boolean geoTzDetectionEnabledValue = configuration.isGeoDetectionEnabled();
|
||||
// TODO(b/149014708) Write this down to user-scoped settings once implemented.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoDetectionEnabled() {
|
||||
// To ensure that TimeZoneConfiguration is "complete" for simplicity, devices that do not
|
||||
// support auto detection have safe, hard coded configuration values that make it look like
|
||||
// auto detection is turned off. It is therefore important that false is returned from this
|
||||
// method for devices that do not support auto time zone detection. Such devices will not
|
||||
// have a UI to turn the auto detection on/off. Returning true could prevent the user
|
||||
// entering information manually. On devices that do support auto time detection the default
|
||||
// is to turn auto detection on.
|
||||
if (isAutoDetectionSupported()) {
|
||||
return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeoDetectionEnabled() {
|
||||
// TODO(b/149014708) Read this from user-scoped settings once implemented. The user's
|
||||
// location toggle will act as an override for this setting, i.e. so that the setting will
|
||||
// return false if the location toggle is disabled.
|
||||
return false;
|
||||
public @UserIdInt int getCurrentUserId() {
|
||||
return LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,8 +155,55 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
|
||||
alarmManager.setTimeZone(zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeConfiguration(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 (isAutoDetectionSupported()) {
|
||||
final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
|
||||
setAutoDetectionEnabled(autoDetectionEnabled);
|
||||
|
||||
final int userId = configuration.getUserId();
|
||||
final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
|
||||
setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUserConfigAllowed(@UserIdInt int userId) {
|
||||
UserHandle userHandle = UserHandle.of(userId);
|
||||
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
|
||||
}
|
||||
|
||||
private boolean isAutoDetectionSupported() {
|
||||
return deviceHasTelephonyNetwork();
|
||||
return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
|
||||
}
|
||||
|
||||
private boolean isAutoDetectionEnabled() {
|
||||
return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
|
||||
}
|
||||
|
||||
private void setAutoDetectionEnabled(boolean 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) {
|
||||
final boolean locationEnabled = isLocationEnabled(userId);
|
||||
return Settings.Secure.getIntForUser(mCr,
|
||||
Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
|
||||
locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
|
||||
}
|
||||
|
||||
private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
|
||||
Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
|
||||
enabled ? 1 : 0, userId);
|
||||
}
|
||||
|
||||
private boolean deviceHasTelephonyNetwork() {
|
||||
@@ -174,4 +211,4 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
|
||||
return mContext.getSystemService(ConnectivityManager.class)
|
||||
.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ import android.annotation.NonNull;
|
||||
*/
|
||||
public interface TimeZoneDetectorInternal extends Dumpable.Container {
|
||||
|
||||
/** Adds a listener that will be invoked when time zone detection configuration is changed. */
|
||||
void addConfigurationListener(ConfigurationChangeListener listener);
|
||||
|
||||
/** Returns the {@link ConfigurationInternal} for the current user. */
|
||||
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,8 @@ import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -34,18 +34,26 @@ 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<>();
|
||||
|
||||
static TimeZoneDetectorInternalImpl create(@NonNull Context context, @NonNull Handler handler,
|
||||
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
|
||||
return new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
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
|
||||
@@ -53,6 +61,19 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
|
||||
mTimeZoneDetectorStrategy.addDumpable(dumpable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigurationListener(ConfigurationChangeListener listener) {
|
||||
synchronized (mConfigurationListeners) {
|
||||
mConfigurationListeners.add(Objects.requireNonNull(listener));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ConfigurationInternal getCurrentUserConfigurationInternal() {
|
||||
return mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestGeolocationTimeZone(
|
||||
@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
|
||||
|
||||
@@ -24,32 +24,24 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.timezonedetector.TimeZoneDetectorStrategy.StrategyListener;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -65,6 +57,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
|
||||
private static final String TAG = "TimeZoneDetectorService";
|
||||
|
||||
/** A compile time switch for enabling / disabling geolocation-based time zone detection. */
|
||||
private static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
|
||||
|
||||
/**
|
||||
* Handles the service lifecycle for {@link TimeZoneDetectorService} and
|
||||
* {@link TimeZoneDetectorInternalImpl}.
|
||||
@@ -80,18 +75,20 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
// Obtain / create the shared dependencies.
|
||||
Context context = getContext();
|
||||
Handler handler = FgThread.getHandler();
|
||||
|
||||
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
|
||||
TimeZoneDetectorStrategyImpl.create(context);
|
||||
TimeZoneDetectorStrategyImpl.create(
|
||||
context, handler, GEOLOCATION_TIME_ZONE_DETECTION_ENABLED);
|
||||
|
||||
// Create and publish the local service for use by internal callers.
|
||||
TimeZoneDetectorInternal internal =
|
||||
TimeZoneDetectorInternalImpl.create(context, handler, timeZoneDetectorStrategy);
|
||||
new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
|
||||
publishLocalService(TimeZoneDetectorInternal.class, internal);
|
||||
|
||||
// Publish the binder service so it can be accessed from other (appropriately
|
||||
// permissioned) processes.
|
||||
TimeZoneDetectorService service =
|
||||
TimeZoneDetectorService.create(context, handler, timeZoneDetectorStrategy);
|
||||
TimeZoneDetectorService service = TimeZoneDetectorService.create(
|
||||
context, handler, timeZoneDetectorStrategy);
|
||||
publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
|
||||
}
|
||||
}
|
||||
@@ -102,53 +99,39 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
|
||||
@NonNull
|
||||
private final CallerIdentityInjector mCallerIdentityInjector;
|
||||
|
||||
@NonNull
|
||||
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
|
||||
|
||||
/**
|
||||
* This sparse array acts as a map from userId to listeners running as that userId. User scoped
|
||||
* as time zone detection configuration is partially user-specific, so different users can
|
||||
* get different configuration.
|
||||
*/
|
||||
@GuardedBy("mConfigurationListeners")
|
||||
@NonNull
|
||||
private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners =
|
||||
new SparseArray<>();
|
||||
private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
|
||||
new ArrayList<>();
|
||||
|
||||
private static TimeZoneDetectorService create(
|
||||
@NonNull Context context, @NonNull Handler handler,
|
||||
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
|
||||
|
||||
TimeZoneDetectorService service =
|
||||
new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
contentResolver.registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
|
||||
new ContentObserver(handler) {
|
||||
public void onChange(boolean selfChange) {
|
||||
service.handleAutoTimeZoneConfigChanged();
|
||||
}
|
||||
});
|
||||
// TODO(b/149014708) Listen for changes to geolocation time zone detection enabled config.
|
||||
// This should also include listening to the current user and the current user's location
|
||||
// toggle since the config is user-scoped and the location toggle overrides the geolocation
|
||||
// time zone enabled setting.
|
||||
CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
|
||||
TimeZoneDetectorService service = new TimeZoneDetectorService(
|
||||
context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
|
||||
return service;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
|
||||
@NonNull CallerIdentityInjector callerIdentityInjector,
|
||||
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
mHandler = Objects.requireNonNull(handler);
|
||||
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
|
||||
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
|
||||
mTimeZoneDetectorStrategy.setStrategyListener(new StrategyListener() {
|
||||
@Override
|
||||
public void onConfigurationChanged() {
|
||||
handleConfigurationChanged();
|
||||
}
|
||||
});
|
||||
|
||||
// Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
|
||||
// the configuration changes for any reason.
|
||||
mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,26 +139,12 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
public TimeZoneCapabilities getCapabilities() {
|
||||
enforceManageTimeZoneDetectorConfigurationPermission();
|
||||
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
long token = Binder.clearCallingIdentity();
|
||||
int userId = mCallerIdentityInjector.getCallingUserId();
|
||||
long token = mCallerIdentityInjector.clearCallingIdentity();
|
||||
try {
|
||||
return mTimeZoneDetectorStrategy.getCapabilities(userId);
|
||||
return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public TimeZoneConfiguration getConfiguration() {
|
||||
enforceManageTimeZoneDetectorConfigurationPermission();
|
||||
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mTimeZoneDetectorStrategy.getConfiguration(userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
mCallerIdentityInjector.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,12 +153,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
enforceManageTimeZoneDetectorConfigurationPermission();
|
||||
Objects.requireNonNull(configuration);
|
||||
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
long token = Binder.clearCallingIdentity();
|
||||
int callingUserId = mCallerIdentityInjector.getCallingUserId();
|
||||
if (callingUserId != configuration.getUserId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long token = mCallerIdentityInjector.clearCallingIdentity();
|
||||
try {
|
||||
return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
|
||||
return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
mCallerIdentityInjector.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,25 +170,17 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
|
||||
enforceManageTimeZoneDetectorConfigurationPermission();
|
||||
Objects.requireNonNull(listener);
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
|
||||
synchronized (mConfigurationListeners) {
|
||||
ArrayList<ITimeZoneConfigurationListener> listeners =
|
||||
mConfigurationListeners.get(userId);
|
||||
if (listeners != null && listeners.contains(listener)) {
|
||||
if (mConfigurationListeners.contains(listener)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<>(1);
|
||||
mConfigurationListeners.put(userId, listeners);
|
||||
}
|
||||
|
||||
// Ensure the reference to the listener will be removed if the client process dies.
|
||||
listener.asBinder().linkToDeath(this, 0 /* flags */);
|
||||
|
||||
// Only add the listener if we can linkToDeath().
|
||||
listeners.add(listener);
|
||||
mConfigurationListeners.add(listener);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
|
||||
}
|
||||
@@ -226,19 +191,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
|
||||
enforceManageTimeZoneDetectorConfigurationPermission();
|
||||
Objects.requireNonNull(listener);
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
|
||||
synchronized (mConfigurationListeners) {
|
||||
boolean removedListener = false;
|
||||
ArrayList<ITimeZoneConfigurationListener> userListeners =
|
||||
mConfigurationListeners.get(userId);
|
||||
if (userListeners.remove(listener)) {
|
||||
if (mConfigurationListeners.remove(listener)) {
|
||||
// Stop listening for the client process to die.
|
||||
listener.asBinder().unlinkToDeath(this, 0 /* flags */);
|
||||
removedListener = true;
|
||||
}
|
||||
if (!removedListener) {
|
||||
Slog.w(TAG, "Client asked to remove listenener=" + listener
|
||||
Slog.w(TAG, "Client asked to remove listener=" + listener
|
||||
+ ", but no listeners were removed."
|
||||
+ " mConfigurationListeners=" + mConfigurationListeners);
|
||||
}
|
||||
@@ -259,19 +221,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
public void binderDied(IBinder who) {
|
||||
synchronized (mConfigurationListeners) {
|
||||
boolean removedListener = false;
|
||||
final int userCount = mConfigurationListeners.size();
|
||||
for (int i = 0; i < userCount; i++) {
|
||||
ArrayList<ITimeZoneConfigurationListener> userListeners =
|
||||
mConfigurationListeners.valueAt(i);
|
||||
Iterator<ITimeZoneConfigurationListener> userListenerIterator =
|
||||
userListeners.iterator();
|
||||
while (userListenerIterator.hasNext()) {
|
||||
ITimeZoneConfigurationListener userListener = userListenerIterator.next();
|
||||
if (userListener.asBinder().equals(who)) {
|
||||
userListenerIterator.remove();
|
||||
removedListener = true;
|
||||
break;
|
||||
}
|
||||
final int listenerCount = mConfigurationListeners.size();
|
||||
for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
|
||||
ITimeZoneConfigurationListener listener =
|
||||
mConfigurationListeners.get(listenerIndex);
|
||||
if (listener.asBinder().equals(who)) {
|
||||
mConfigurationListeners.remove(listenerIndex);
|
||||
removedListener = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!removedListener) {
|
||||
@@ -283,42 +240,25 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
}
|
||||
|
||||
void handleConfigurationChanged() {
|
||||
// Note: we could trigger an async time zone detection operation here via a call to
|
||||
// handleAutoTimeZoneConfigChanged(), but that is triggered in response to the underlying
|
||||
// setting value changing so it is currently unnecessary. If we get to a point where all
|
||||
// configuration changes are guaranteed to happen in response to an updateConfiguration()
|
||||
// call, then we can remove that path and call it here instead.
|
||||
|
||||
// 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.
|
||||
synchronized (mConfigurationListeners) {
|
||||
final int userCount = mConfigurationListeners.size();
|
||||
for (int userIndex = 0; userIndex < userCount; userIndex++) {
|
||||
int userId = mConfigurationListeners.keyAt(userIndex);
|
||||
TimeZoneConfiguration configuration =
|
||||
mTimeZoneDetectorStrategy.getConfiguration(userId);
|
||||
|
||||
ArrayList<ITimeZoneConfigurationListener> listeners =
|
||||
mConfigurationListeners.valueAt(userIndex);
|
||||
final int listenerCount = listeners.size();
|
||||
for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
|
||||
ITimeZoneConfigurationListener listener = listeners.get(listenerIndex);
|
||||
try {
|
||||
listener.onChange(configuration);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Unable to notify listener=" + listener
|
||||
+ " for userId=" + userId
|
||||
+ " of updated configuration=" + configuration, e);
|
||||
}
|
||||
final int listenerCount = mConfigurationListeners.size();
|
||||
for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
|
||||
ITimeZoneConfigurationListener listener =
|
||||
mConfigurationListeners.get(listenerIndex);
|
||||
try {
|
||||
listener.onChange();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Unable to notify listener=" + listener, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provided for command-line access. This is not exposed as a binder API. */
|
||||
void suggestGeolocationTimeZone(
|
||||
@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
|
||||
void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
|
||||
enforceSuggestGeolocationTimeZonePermission();
|
||||
Objects.requireNonNull(timeZoneSuggestion);
|
||||
|
||||
@@ -331,12 +271,12 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
enforceSuggestManualTimeZonePermission();
|
||||
Objects.requireNonNull(timeZoneSuggestion);
|
||||
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
long token = Binder.clearCallingIdentity();
|
||||
int userId = mCallerIdentityInjector.getCallingUserId();
|
||||
long token = mCallerIdentityInjector.clearCallingIdentity();
|
||||
try {
|
||||
return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
mCallerIdentityInjector.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,12 +298,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
ipw.flush();
|
||||
}
|
||||
|
||||
/** Internal method for handling the auto time zone configuration being changed. */
|
||||
@VisibleForTesting
|
||||
public void handleAutoTimeZoneConfigChanged() {
|
||||
mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneConfigChanged);
|
||||
}
|
||||
|
||||
private void enforceManageTimeZoneDetectorConfigurationPermission() {
|
||||
// TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
|
||||
mContext.enforceCallingPermission(
|
||||
|
||||
@@ -19,51 +19,84 @@ import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
/**
|
||||
* The interface for the class that implements the time detection algorithm used by the
|
||||
* {@link TimeZoneDetectorService}.
|
||||
* The interface for the class that is responsible for setting the time zone on a device, used by
|
||||
* {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
|
||||
*
|
||||
* <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
|
||||
* and what to set it to.
|
||||
* <p>The strategy receives suggestions, which it may use to modify the device's time zone setting.
|
||||
* Suggestions are acted on or ignored as needed, depending on previously received suggestions and
|
||||
* the current user's configuration (see {@link ConfigurationInternal}).
|
||||
*
|
||||
* <p>Most calls will be handled by a single thread, but that is not true for all calls. For example
|
||||
* {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread concurrently
|
||||
* with other operations so implementations must still handle thread safety.
|
||||
* <p>Devices can have zero, one or two automatic time zone detection algorithm available at any
|
||||
* point in time.
|
||||
*
|
||||
* <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
|
||||
* availability and use depends on several factors:
|
||||
* <ul>
|
||||
* <li>Telephony is only available on devices with a telephony stack.
|
||||
* <li>Geolocation is also optional and configured at image creation time. When enabled on a
|
||||
* device, its availability depends on the current user's settings, so switching between users can
|
||||
* change the automatic algorithm used by the device.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If there are no automatic time zone detections algorithms available then the user can usually
|
||||
* change the device time zone manually. Under most circumstances the current user can turn
|
||||
* automatic time zone detection on or off, or choose the algorithm via settings.
|
||||
*
|
||||
* <p>Telephony detection is independent of the current user. The device keeps track of the most
|
||||
* recent telephony suggestion from each slotIndex. When telephony detection is in use, the highest
|
||||
* scoring suggestion is used to set the device time zone based on a scoring algorithm. If several
|
||||
* slotIndexes provide the same score then the slotIndex with the lowest numeric value "wins". If
|
||||
* the situation changes and it is no longer possible to be confident about the time zone,
|
||||
* slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
|
||||
* suggestion otherwise it will remain in use.
|
||||
*
|
||||
* <p>Geolocation detection is dependent on the current user and their settings. The device retains
|
||||
* at most one geolocation suggestion. Generally, use of a device's location is dependent on the
|
||||
* user's "location toggle", but even when that is enabled the user may choose to enable / disable
|
||||
* the use of geolocation for device time zone detection. If the current user changes to one that
|
||||
* does not have geolocation detection enabled, or the user turns off geolocation detection, then
|
||||
* the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
|
||||
* have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
|
||||
* will remain in use.
|
||||
*
|
||||
* <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()}, and debug
|
||||
* calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread
|
||||
* concurrently with other operations.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
|
||||
|
||||
/** A listener for strategy events. */
|
||||
interface StrategyListener {
|
||||
/**
|
||||
* Invoked when configuration has been changed.
|
||||
*/
|
||||
void onConfigurationChanged();
|
||||
}
|
||||
/**
|
||||
* Sets a listener that will be triggered whenever time zone detection configuration is
|
||||
* changed.
|
||||
*/
|
||||
void addConfigChangeListener(@NonNull ConfigurationChangeListener listener);
|
||||
|
||||
/** Sets the listener that enables the strategy to communicate with the surrounding service. */
|
||||
void setStrategyListener(@NonNull StrategyListener listener);
|
||||
|
||||
/** Returns the user's time zone capabilities. */
|
||||
/** Returns the user's time zone configuration. */
|
||||
@NonNull
|
||||
TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
|
||||
ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
|
||||
|
||||
/**
|
||||
* Returns the configuration that controls time zone detector behavior.
|
||||
* Returns the configuration that controls time zone detector behavior for the current user.
|
||||
*/
|
||||
@NonNull
|
||||
TimeZoneConfiguration getConfiguration(@UserIdInt int userId);
|
||||
ConfigurationInternal getCurrentUserConfigurationInternal();
|
||||
|
||||
/**
|
||||
* Updates the configuration settings that control time zone detector behavior.
|
||||
* 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);
|
||||
boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
|
||||
|
||||
/**
|
||||
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
|
||||
@@ -85,9 +118,4 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
|
||||
* suggestion.
|
||||
*/
|
||||
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
|
||||
|
||||
/**
|
||||
* Called when there has been a change to the automatic time zone detection configuration.
|
||||
*/
|
||||
void handleAutoTimeZoneConfigChanged();
|
||||
}
|
||||
|
||||
@@ -20,10 +20,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYP
|
||||
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
|
||||
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
|
||||
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
|
||||
import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_AUTO_DETECTION_ENABLED;
|
||||
import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_GEO_DETECTION_ENABLED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -33,6 +30,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
@@ -45,15 +43,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
|
||||
* suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
|
||||
* zone detection" setting.
|
||||
*
|
||||
* <p>For automatic detection, it keeps track of the most recent telephony suggestion from each
|
||||
* slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes
|
||||
* provide the same score then the slotIndex with the lowest numeric value "wins". If the situation
|
||||
* changes and it is no longer possible to be confident about the time zone, slotIndexes must have
|
||||
* an empty suggestion submitted in order to "withdraw" their previous suggestion.
|
||||
* The real implementation of {@link TimeZoneDetectorStrategy}.
|
||||
*
|
||||
* <p>Most public methods are marked synchronized to ensure thread safety around internal state.
|
||||
*/
|
||||
@@ -61,48 +51,27 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
|
||||
/**
|
||||
* Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
|
||||
* / system properties. It can be faked for testing different scenarios.
|
||||
* / system properties. It can be faked for testing.
|
||||
*
|
||||
* <p>Note: Because the settings / system properties-derived values can currently be modified
|
||||
* independently and from different threads (and processes!), their use are prone to race
|
||||
* conditions. That will be true until the responsibility for setting their values is moved to
|
||||
* {@link TimeZoneDetectorStrategyImpl} (which is thread safe).
|
||||
* independently and from different threads (and processes!), their use is prone to race
|
||||
* conditions.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public interface Callback {
|
||||
|
||||
/**
|
||||
* Returns the capabilities for the user.
|
||||
* 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.
|
||||
*/
|
||||
@NonNull
|
||||
TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
|
||||
void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
|
||||
|
||||
/**
|
||||
* Returns the configuration for the user.
|
||||
* @param userId
|
||||
*/
|
||||
@NonNull
|
||||
TimeZoneConfiguration getConfiguration(int userId);
|
||||
/** Returns the current user at the instant it is called. */
|
||||
@UserIdInt int getCurrentUserId();
|
||||
|
||||
/**
|
||||
* Sets the configuration for the user. This method handles storage only, the configuration
|
||||
* must have been validated by the caller and be complete.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@link TimeZoneConfiguration#isComplete()}
|
||||
* returns {@code false}
|
||||
*/
|
||||
void setConfiguration(@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
|
||||
|
||||
/**
|
||||
* Returns true if automatic time zone detection is currently enabled.
|
||||
*/
|
||||
boolean isAutoDetectionEnabled();
|
||||
|
||||
/**
|
||||
* Returns whether geolocation can be used for time zone detection when {@link
|
||||
* #isAutoDetectionEnabled()} returns {@code true}.
|
||||
*/
|
||||
boolean isGeoDetectionEnabled();
|
||||
/** Returns the {@link ConfigurationInternal} for the specified user. */
|
||||
ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
|
||||
|
||||
/**
|
||||
* Returns true if the device has had an explicit time zone set.
|
||||
@@ -118,6 +87,13 @@ 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(TimeZoneConfiguration newConfiguration);
|
||||
}
|
||||
|
||||
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
|
||||
@@ -189,9 +165,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
@NonNull
|
||||
private final Callback mCallback;
|
||||
|
||||
/** Non-null after {@link #setStrategyListener(StrategyListener)} is called. */
|
||||
@Nullable
|
||||
private StrategyListener mListener;
|
||||
@GuardedBy("this")
|
||||
@NonNull
|
||||
private List<ConfigurationChangeListener> mConfigChangeListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* A log that records the decisions / decision metadata that affected the device's time zone.
|
||||
@@ -211,7 +187,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
|
||||
|
||||
/**
|
||||
* The latest geolocation suggestion received.
|
||||
* The latest geolocation suggestion received. If the user disabled geolocation time zone
|
||||
* detection then the latest suggestion is cleared.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
|
||||
@@ -223,113 +200,120 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
/**
|
||||
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
|
||||
*/
|
||||
public static TimeZoneDetectorStrategyImpl create(Context context) {
|
||||
Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
|
||||
return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
|
||||
public static TimeZoneDetectorStrategyImpl create(
|
||||
@NonNull Context context, @NonNull Handler handler,
|
||||
boolean geolocationTimeZoneDetectionEnabled) {
|
||||
|
||||
TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
|
||||
context, handler, geolocationTimeZoneDetectionEnabled);
|
||||
return new TimeZoneDetectorStrategyImpl(callback);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public TimeZoneDetectorStrategyImpl(Callback callback) {
|
||||
public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
|
||||
mCallback = Objects.requireNonNull(callback);
|
||||
mCallback.setConfigChangeListener(this::handleConfigChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener that allows the strategy to communicate with the surrounding service. This
|
||||
* must be called before the instance is used and must only be called once.
|
||||
* 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 setStrategyListener(@NonNull StrategyListener listener) {
|
||||
if (mListener != null) {
|
||||
throw new IllegalStateException("Strategy already has a listener");
|
||||
}
|
||||
mListener = Objects.requireNonNull(listener);
|
||||
public synchronized void addConfigChangeListener(
|
||||
@NonNull ConfigurationChangeListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
mConfigChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public synchronized TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
|
||||
return mCallback.getCapabilities(userId);
|
||||
public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
|
||||
return mCallback.getConfigurationInternal(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public synchronized TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
|
||||
return mCallback.getConfiguration(userId);
|
||||
public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
|
||||
int currentUserId = mCallback.getCurrentUserId();
|
||||
return getConfigurationInternal(currentUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean updateConfiguration(
|
||||
@UserIdInt int userId, @NonNull TimeZoneConfiguration configurationChanges) {
|
||||
Objects.requireNonNull(configurationChanges);
|
||||
@NonNull TimeZoneConfiguration requestedConfiguration) {
|
||||
Objects.requireNonNull(requestedConfiguration);
|
||||
|
||||
// Validate the requested configuration changes before applying any of them.
|
||||
TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
|
||||
boolean canManageTimeZoneDetection =
|
||||
capabilities.getConfigureAutoDetectionEnabled() >= CAPABILITY_NOT_APPLICABLE;
|
||||
if (!canManageTimeZoneDetection
|
||||
&& containsAutoTimeDetectionProperties(configurationChanges)) {
|
||||
int userId = requestedConfiguration.getUserId();
|
||||
TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
|
||||
|
||||
// Create a new configuration builder, and copy across the mutable properties users are
|
||||
// able to modify. Other properties are therefore ignored.
|
||||
final TimeZoneConfiguration newConfiguration =
|
||||
capabilities.applyUpdate(requestedConfiguration);
|
||||
if (newConfiguration == null) {
|
||||
// The changes could not be made due to
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a complete configuration by merging the existing and new (possibly partial)
|
||||
// configuration.
|
||||
final TimeZoneConfiguration oldConfiguration = mCallback.getConfiguration(userId);
|
||||
final TimeZoneConfiguration newConfiguration =
|
||||
new TimeZoneConfiguration.Builder(oldConfiguration)
|
||||
.mergeProperties(configurationChanges)
|
||||
.build();
|
||||
// Store the configuration / notify as needed. This will cause the mCallback to invoke
|
||||
// handleConfigChanged() asynchronously.
|
||||
mCallback.storeConfiguration(newConfiguration);
|
||||
|
||||
// Set the configuration / notify as needed.
|
||||
boolean configurationChanged = !oldConfiguration.equals(newConfiguration);
|
||||
if (configurationChanged) {
|
||||
mCallback.setConfiguration(userId, newConfiguration);
|
||||
|
||||
String logMsg = "Configuration changed:"
|
||||
+ "oldConfiguration=" + oldConfiguration
|
||||
+ ", configuration=" + configurationChanges
|
||||
+ ", newConfiguration=" + newConfiguration;
|
||||
mTimeZoneChangesLog.log(logMsg);
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, logMsg);
|
||||
}
|
||||
mListener.onConfigurationChanged();
|
||||
TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
|
||||
String logMsg = "Configuration changed:"
|
||||
+ " oldConfiguration=" + oldConfiguration
|
||||
+ ", newConfiguration=" + newConfiguration;
|
||||
mTimeZoneChangesLog.log(logMsg);
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, logMsg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean containsAutoTimeDetectionProperties(
|
||||
@NonNull TimeZoneConfiguration configuration) {
|
||||
return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
|
||||
|| configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void suggestGeolocationTimeZone(
|
||||
@NonNull GeolocationTimeZoneSuggestion suggestion) {
|
||||
|
||||
int currentUserId = mCallback.getCurrentUserId();
|
||||
ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
|
||||
Slog.d(LOG_TAG, "Geolocation suggestion received."
|
||||
+ " currentUserConfig=" + currentUserConfig
|
||||
+ " newSuggestion=" + suggestion);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(suggestion);
|
||||
mLatestGeoLocationSuggestion.set(suggestion);
|
||||
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the time
|
||||
// zone setting.
|
||||
if (mCallback.isGeoDetectionEnabled()) {
|
||||
if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
|
||||
// Only store a geolocation suggestion if geolocation detection is currently enabled.
|
||||
mLatestGeoLocationSuggestion.set(suggestion);
|
||||
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the
|
||||
// time zone setting.
|
||||
String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
|
||||
doAutoTimeZoneDetection(reason);
|
||||
doAutoTimeZoneDetection(currentUserConfig, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean suggestManualTimeZone(
|
||||
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
|
||||
|
||||
int currentUserId = mCallback.getCurrentUserId();
|
||||
if (userId != currentUserId) {
|
||||
Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId
|
||||
+ " suggestion=" + suggestion);
|
||||
|
||||
// Only listen to changes from the current user.
|
||||
return false;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(suggestion);
|
||||
|
||||
String timeZoneId = suggestion.getZoneId();
|
||||
String cause = "Manual time suggestion received: suggestion=" + suggestion;
|
||||
|
||||
TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
|
||||
TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
|
||||
if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
|
||||
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
|
||||
+ ", capabilities=" + capabilities
|
||||
@@ -345,8 +329,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
@Override
|
||||
public synchronized void suggestTelephonyTimeZone(
|
||||
@NonNull TelephonyTimeZoneSuggestion suggestion) {
|
||||
|
||||
int currentUserId = mCallback.getCurrentUserId();
|
||||
ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion);
|
||||
Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
|
||||
+ " newSuggestion=" + suggestion);
|
||||
}
|
||||
Objects.requireNonNull(suggestion);
|
||||
|
||||
@@ -360,9 +348,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the time
|
||||
// zone setting.
|
||||
if (!mCallback.isGeoDetectionEnabled()) {
|
||||
if (!currentUserConfig.getGeoDetectionEnabledBehavior()) {
|
||||
String reason = "New telephony time zone suggested. suggestion=" + suggestion;
|
||||
doAutoTimeZoneDetection(reason);
|
||||
doAutoTimeZoneDetection(currentUserConfig, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,15 +380,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
* Performs automatic time zone detection.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
|
||||
if (!mCallback.isAutoDetectionEnabled()) {
|
||||
// Avoid doing unnecessary work with this (race-prone) check.
|
||||
private void doAutoTimeZoneDetection(
|
||||
@NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
|
||||
if (!currentUserConfig.getAutoDetectionEnabledBehavior()) {
|
||||
// Avoid doing unnecessary work.
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the right suggestions based on the current configuration. This check is potentially
|
||||
// race-prone until this value is set via a call to TimeZoneDetectorStrategy.
|
||||
if (mCallback.isGeoDetectionEnabled()) {
|
||||
// Use the right suggestions based on the current configuration.
|
||||
if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
|
||||
doGeolocationTimeZoneDetection(detectionReason);
|
||||
} else {
|
||||
doTelephonyTimeZoneDetection(detectionReason);
|
||||
@@ -480,35 +468,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
|
||||
// Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
|
||||
// zone ID.
|
||||
String newZoneId = bestTelephonySuggestion.suggestion.getZoneId();
|
||||
if (newZoneId == null) {
|
||||
String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
|
||||
if (zoneId == null) {
|
||||
Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
|
||||
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
|
||||
+ " detectionReason=" + detectionReason);
|
||||
return;
|
||||
}
|
||||
|
||||
String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
|
||||
String cause = "Found good suggestion."
|
||||
+ ", bestTelephonySuggestion=" + bestTelephonySuggestion
|
||||
+ ", detectionReason=" + detectionReason;
|
||||
setAutoDeviceTimeZoneIfRequired(zoneId, cause);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void setAutoDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
|
||||
Objects.requireNonNull(newZoneId);
|
||||
Objects.requireNonNull(cause);
|
||||
|
||||
if (!mCallback.isAutoDetectionEnabled()) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
|
||||
+ ", newZoneId=" + newZoneId
|
||||
+ ", cause=" + cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setDeviceTimeZoneIfRequired(newZoneId, cause);
|
||||
setDeviceTimeZoneIfRequired(zoneId, cause);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@@ -582,13 +553,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
return findBestTelephonySuggestion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleAutoTimeZoneConfigChanged() {
|
||||
private synchronized void handleConfigChanged() {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
|
||||
Slog.d(LOG_TAG, "handleConfigChanged()");
|
||||
}
|
||||
|
||||
doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
|
||||
clearGeolocationSuggestionIfNeeded();
|
||||
|
||||
for (ConfigurationChangeListener listener : mConfigChangeListeners) {
|
||||
listener.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void clearGeolocationSuggestionIfNeeded() {
|
||||
// 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 = mCallback.getCurrentUserId();
|
||||
ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
|
||||
|
||||
GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
|
||||
mLatestGeoLocationSuggestion.get();
|
||||
if (latestGeoLocationSuggestion != null
|
||||
&& !currentUserConfig.getGeoDetectionEnabledBehavior()) {
|
||||
// The current user's config has geodetection disabled, so clear the latest suggestion.
|
||||
// This is done to ensure we only ever keep a geolocation suggestion if the user has
|
||||
// said it is ok to do so.
|
||||
mLatestGeoLocationSuggestion.set(null);
|
||||
mTimeZoneChangesLog.log(
|
||||
"clearGeolocationSuggestionIfNeeded: Cleared latest Geolocation suggestion.");
|
||||
}
|
||||
|
||||
doAutoTimeZoneDetection(currentUserConfig, "clearGeolocationSuggestionIfNeeded()");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -604,11 +601,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
ipw.println("TimeZoneDetectorStrategy:");
|
||||
|
||||
ipw.increaseIndent(); // level 1
|
||||
ipw.println("mCallback.isAutoDetectionEnabled()=" + mCallback.isAutoDetectionEnabled());
|
||||
int currentUserId = mCallback.getCurrentUserId();
|
||||
ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
|
||||
ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
|
||||
ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
|
||||
ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
|
||||
ipw.println("mCallback.isDeviceTimeZoneInitialized()="
|
||||
+ mCallback.isDeviceTimeZoneInitialized());
|
||||
ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
|
||||
ipw.println("mCallback.isGeoDetectionEnabled()=" + mCallback.isGeoDetectionEnabled());
|
||||
|
||||
ipw.println("Time zone change log:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
|
||||
* {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
|
||||
*/
|
||||
public class ConfigurationInternalTest {
|
||||
|
||||
private static final int ARBITRARY_USER_ID = 99999;
|
||||
|
||||
/**
|
||||
* Tests when {@link ConfigurationInternal#isUserConfigAllowed()} and
|
||||
* {@link ConfigurationInternal#isAutoDetectionSupported()} are both true.
|
||||
*/
|
||||
@Test
|
||||
public void test_unrestricted() {
|
||||
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionSupported(true)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setLocationEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
{
|
||||
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.build();
|
||||
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
|
||||
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
|
||||
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
|
||||
{
|
||||
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
|
||||
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests when {@link ConfigurationInternal#isUserConfigAllowed()} is false */
|
||||
@Test
|
||||
public void test_restricted() {
|
||||
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
|
||||
.setUserConfigAllowed(false)
|
||||
.setAutoDetectionSupported(true)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setLocationEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
{
|
||||
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.build();
|
||||
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
|
||||
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
|
||||
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
|
||||
{
|
||||
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
|
||||
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is false. */
|
||||
@Test
|
||||
public void test_autoDetectNotSupported() {
|
||||
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionSupported(false)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.setLocationEnabled(true)
|
||||
.setGeoDetectionEnabled(true)
|
||||
.build();
|
||||
{
|
||||
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(true)
|
||||
.build();
|
||||
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
|
||||
assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
|
||||
assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
{
|
||||
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
|
||||
.setAutoDetectionEnabled(false)
|
||||
.build();
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
|
||||
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
|
||||
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
|
||||
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
|
||||
|
||||
TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
|
||||
assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
|
||||
assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
|
||||
assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
|
||||
assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
|
||||
assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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;
|
||||
@@ -32,56 +33,64 @@ import java.util.List;
|
||||
|
||||
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
|
||||
private StrategyListener mListener;
|
||||
private ConfigurationChangeListener mConfigurationChangeListener;
|
||||
|
||||
// Fake state
|
||||
private TimeZoneCapabilities mCapabilities;
|
||||
private TimeZoneConfiguration mConfiguration;
|
||||
private ConfigurationInternal mConfigurationInternal;
|
||||
|
||||
// Call tracking.
|
||||
private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
|
||||
private ManualTimeZoneSuggestion mLastManualSuggestion;
|
||||
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
|
||||
private boolean mHandleAutoTimeZoneConfigChangedCalled;
|
||||
private boolean mDumpCalled;
|
||||
private final List<Dumpable> mDumpables = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void setStrategyListener(@NonNull StrategyListener listener) {
|
||||
mListener = listener;
|
||||
public void addConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
|
||||
if (mConfigurationChangeListener != null) {
|
||||
fail("Fake only supports one listener");
|
||||
}
|
||||
mConfigurationChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
|
||||
return mCapabilities;
|
||||
public ConfigurationInternal getConfigurationInternal(int userId) {
|
||||
if (mConfigurationInternal.getUserId() != userId) {
|
||||
fail("Fake only supports one user");
|
||||
}
|
||||
return mConfigurationInternal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateConfiguration(
|
||||
@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
|
||||
assertNotNull(mConfiguration);
|
||||
assertNotNull(configuration);
|
||||
public ConfigurationInternal getCurrentUserConfigurationInternal() {
|
||||
return mConfigurationInternal;
|
||||
}
|
||||
|
||||
// Simulate the strategy's behavior: the new configuration will be the old configuration
|
||||
// merged with the new.
|
||||
TimeZoneConfiguration oldConfiguration = mConfiguration;
|
||||
TimeZoneConfiguration newConfiguration =
|
||||
new TimeZoneConfiguration.Builder(mConfiguration)
|
||||
.mergeProperties(configuration)
|
||||
.build();
|
||||
@Override
|
||||
public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
|
||||
assertNotNull(mConfigurationInternal);
|
||||
assertNotNull(requestedChanges);
|
||||
|
||||
if (newConfiguration.equals(oldConfiguration)) {
|
||||
// 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.
|
||||
TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
|
||||
TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
|
||||
if (newConfiguration == null) {
|
||||
return false;
|
||||
}
|
||||
mConfiguration = newConfiguration;
|
||||
mListener.onConfigurationChanged();
|
||||
|
||||
if (!newConfiguration.equals(capabilities.getConfiguration())) {
|
||||
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
|
||||
|
||||
// Note: Unlike the real strategy, the listeners is invoked synchronously.
|
||||
mConfigurationChangeListener.onChange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
|
||||
return mConfiguration;
|
||||
public void simulateConfigurationChangeForTests() {
|
||||
mConfigurationChangeListener.onChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,11 +111,6 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
mLastTelephonySuggestion = timeZoneSuggestion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleAutoTimeZoneConfigChanged() {
|
||||
mHandleAutoTimeZoneConfigChangedCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDumpable(Dumpable dumpable) {
|
||||
mDumpables.add(dumpable);
|
||||
@@ -117,19 +121,14 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
mDumpCalled = true;
|
||||
}
|
||||
|
||||
void initializeConfiguration(TimeZoneConfiguration configuration) {
|
||||
mConfiguration = configuration;
|
||||
}
|
||||
|
||||
void initializeCapabilities(TimeZoneCapabilities capabilities) {
|
||||
mCapabilities = capabilities;
|
||||
void initializeConfiguration(ConfigurationInternal configurationInternal) {
|
||||
mConfigurationInternal = configurationInternal;
|
||||
}
|
||||
|
||||
void resetCallTracking() {
|
||||
mLastGeolocationSuggestion = null;
|
||||
mLastManualSuggestion = null;
|
||||
mLastTelephonySuggestion = null;
|
||||
mHandleAutoTimeZoneConfigChangedCalled = false;
|
||||
mDumpCalled = false;
|
||||
}
|
||||
|
||||
@@ -146,10 +145,6 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
assertEquals(expectedSuggestion, mLastTelephonySuggestion);
|
||||
}
|
||||
|
||||
void verifyHandleAutoTimeZoneConfigChangedCalled() {
|
||||
assertTrue(mHandleAutoTimeZoneConfigChangedCalled);
|
||||
}
|
||||
|
||||
void verifyDumpCalled() {
|
||||
assertTrue(mDumpCalled);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 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 android.annotation.UserIdInt;
|
||||
|
||||
/** A fake {@link CallerIdentityInjector} used in tests. */
|
||||
public class TestCallerIdentityInjector implements CallerIdentityInjector {
|
||||
|
||||
private long mToken = 9999L;
|
||||
private int mCallingUserId;
|
||||
private Integer mCurrentCallingUserId;
|
||||
|
||||
public void initializeCallingUserId(@UserIdInt int userId) {
|
||||
mCallingUserId = userId;
|
||||
mCurrentCallingUserId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCallingUserId() {
|
||||
assertNotNull("callingUserId has been cleared", mCurrentCallingUserId);
|
||||
return mCurrentCallingUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clearCallingIdentity() {
|
||||
mCurrentCallingUserId = null;
|
||||
return mToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreCallingIdentity(long token) {
|
||||
assertEquals(token, mToken);
|
||||
mCurrentCallingUserId = mCallingUserId;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -85,6 +86,18 @@ public class TimeZoneDetectorInternalImplTest {
|
||||
mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
|
||||
}
|
||||
|
||||
@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 new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -36,7 +34,6 @@ import static org.mockito.Mockito.when;
|
||||
import android.app.timezonedetector.ITimeZoneConfigurationListener;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TimeZoneCapabilities;
|
||||
import android.app.timezonedetector.TimeZoneConfiguration;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -65,6 +62,7 @@ public class TimeZoneDetectorServiceTest {
|
||||
private TimeZoneDetectorService mTimeZoneDetectorService;
|
||||
private HandlerThread mHandlerThread;
|
||||
private TestHandler mTestHandler;
|
||||
private TestCallerIdentityInjector mTestCallerIdentityInjector;
|
||||
|
||||
|
||||
@Before
|
||||
@@ -76,10 +74,14 @@ public class TimeZoneDetectorServiceTest {
|
||||
mHandlerThread.start();
|
||||
mTestHandler = new TestHandler(mHandlerThread.getLooper());
|
||||
|
||||
mTestCallerIdentityInjector = new TestCallerIdentityInjector();
|
||||
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
|
||||
|
||||
mFakeTimeZoneDetectorStrategy = new FakeTimeZoneDetectorStrategy();
|
||||
|
||||
mTimeZoneDetectorService = new TimeZoneDetectorService(
|
||||
mMockContext, mTestHandler, mFakeTimeZoneDetectorStrategy);
|
||||
mMockContext, mTestHandler, mTestCallerIdentityInjector,
|
||||
mFakeTimeZoneDetectorStrategy);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -107,40 +109,12 @@ public class TimeZoneDetectorServiceTest {
|
||||
public void testGetCapabilities() {
|
||||
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
|
||||
|
||||
TimeZoneCapabilities capabilities = createTimeZoneCapabilities();
|
||||
mFakeTimeZoneDetectorStrategy.initializeCapabilities(capabilities);
|
||||
|
||||
assertEquals(capabilities, mTimeZoneDetectorService.getCapabilities());
|
||||
|
||||
verify(mMockContext).enforceCallingPermission(
|
||||
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
|
||||
anyString());
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException.class)
|
||||
public void testGetConfiguration_withoutPermission() {
|
||||
doThrow(new SecurityException("Mock"))
|
||||
.when(mMockContext).enforceCallingPermission(anyString(), any());
|
||||
|
||||
try {
|
||||
mTimeZoneDetectorService.getConfiguration();
|
||||
fail();
|
||||
} finally {
|
||||
verify(mMockContext).enforceCallingPermission(
|
||||
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
|
||||
anyString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConfiguration() {
|
||||
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
|
||||
|
||||
TimeZoneConfiguration configuration =
|
||||
createTimeZoneConfiguration(false /* autoDetectionEnabled */);
|
||||
ConfigurationInternal configuration =
|
||||
createConfigurationInternal(true /* autoDetectionEnabled*/);
|
||||
mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
|
||||
|
||||
assertEquals(configuration, mTimeZoneDetectorService.getConfiguration());
|
||||
assertEquals(configuration.createCapabilities(),
|
||||
mTimeZoneDetectorService.getCapabilities());
|
||||
|
||||
verify(mMockContext).enforceCallingPermission(
|
||||
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
|
||||
@@ -181,10 +155,9 @@ public class TimeZoneDetectorServiceTest {
|
||||
|
||||
@Test
|
||||
public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
|
||||
TimeZoneConfiguration autoDetectDisabledConfiguration =
|
||||
createTimeZoneConfiguration(false /* autoDetectionEnabled */);
|
||||
|
||||
mFakeTimeZoneDetectorStrategy.initializeConfiguration(autoDetectDisabledConfiguration);
|
||||
ConfigurationInternal initialConfiguration =
|
||||
createConfigurationInternal(false /* autoDetectionEnabled */);
|
||||
mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
|
||||
|
||||
IBinder mockListenerBinder = mock(IBinder.class);
|
||||
ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
|
||||
@@ -210,13 +183,12 @@ public class TimeZoneDetectorServiceTest {
|
||||
// Simulate the configuration being changed and verify the mockListener was notified.
|
||||
TimeZoneConfiguration autoDetectEnabledConfiguration =
|
||||
createTimeZoneConfiguration(true /* autoDetectionEnabled */);
|
||||
|
||||
mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
|
||||
|
||||
verify(mMockContext).enforceCallingPermission(
|
||||
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
|
||||
anyString());
|
||||
verify(mockListener).onChange(autoDetectEnabledConfiguration);
|
||||
verify(mockListener).onChange();
|
||||
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
|
||||
reset(mockListenerBinder, mockListener, mMockContext);
|
||||
}
|
||||
@@ -242,12 +214,14 @@ public class TimeZoneDetectorServiceTest {
|
||||
{
|
||||
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
|
||||
|
||||
TimeZoneConfiguration autoDetectDisabledConfiguration =
|
||||
createTimeZoneConfiguration(false /* autoDetectionEnabled */);
|
||||
mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
|
||||
|
||||
verify(mMockContext).enforceCallingPermission(
|
||||
eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
|
||||
anyString());
|
||||
verify(mockListener, never()).onChange(any());
|
||||
verify(mockListener, never()).onChange();
|
||||
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
|
||||
reset(mockListenerBinder, mockListener, mMockContext);
|
||||
}
|
||||
@@ -379,33 +353,22 @@ public class TimeZoneDetectorServiceTest {
|
||||
mFakeTimeZoneDetectorStrategy.verifyDumpCalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleAutoTimeZoneConfigChanged() throws Exception {
|
||||
mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
|
||||
mTestHandler.assertTotalMessagesEnqueued(1);
|
||||
mTestHandler.waitForMessagesToBeProcessed();
|
||||
mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
|
||||
|
||||
mFakeTimeZoneDetectorStrategy.resetCallTracking();
|
||||
|
||||
mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
|
||||
mTestHandler.assertTotalMessagesEnqueued(2);
|
||||
mTestHandler.waitForMessagesToBeProcessed();
|
||||
mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
|
||||
}
|
||||
|
||||
private static TimeZoneConfiguration createTimeZoneConfiguration(
|
||||
boolean autoDetectionEnabled) {
|
||||
return new TimeZoneConfiguration.Builder()
|
||||
private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
|
||||
return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
|
||||
.setAutoDetectionEnabled(autoDetectionEnabled)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static TimeZoneCapabilities createTimeZoneCapabilities() {
|
||||
return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
|
||||
.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
|
||||
.setSuggestManualTimeZone(CAPABILITY_POSSESSED)
|
||||
private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
|
||||
// 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)
|
||||
.setAutoDetectionSupported(true)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionEnabled(autoDetectionEnabled)
|
||||
.setLocationEnabled(geoDetectionEnabled)
|
||||
.setGeoDetectionEnabled(geoDetectionEnabled)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user