Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a223581e authored by Neil Fuller's avatar Neil Fuller
Browse files

Implement user-scoped geolocation configuration

Implement user-scoped geolocation configuration / behavior.

The main changes:

1) For simplicity, configuration change listener is no longer passed the
latest config. It is left to the listener to request the latest config.
This removes some of the previous complexity that required a SparseArray
to track listeners by userId.

2) The Callback implementation is now responsible for listening for
configuration / user changes and invoking a listener (which will invoke
other listeners). This is to try to have one class know all the various
settings that affect auto time / geolocation detection behavior, which
has become more complicated with the user-scoped geolocation config. The
TimeZoneDetectorInternal has some listener methods added in preparation
for later steps.

3) The new "geolocation detection enabled" setting is added. This allows
users to selectively disable geolocation time zone detection
independently of the location toggle.

4) The TimeZoneConfiguration now carries the ID of the user it is for,
since it now contains user-specific state.

5) Internally, the TimeZoneConfiguration is replaced by the
ConfigurationInternal class, which includes all properties (not just
user visible settings) that influence time zone behavior on the device.
The ConfigurationInternal generates the TimeZoneCapabilities and
TimeZoneConfiguration.

6) In a change from the initial behavior, any previously received
geolocation suggestion is cleared if geolocation detection is turned
off (or the current user switches to one that has it turned off), and
suggestions received when geolocation detection is off are ignored.
This is trying to be careful to avoid caching the geolocation timezone
if the user has signalled they don't want their device to use it.

Test: atest services/tests/servicestests/src/com/android/server/timezonedetector
Test: atest core/tests/coretests/src/android/app/timezonedetector
Bug: 149014708
Change-Id: Ieac9f8f646e16edcb8b6ea6c8749e45761898ac1
parent e076ada0
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -20,5 +20,5 @@ import android.app.timezonedetector.TimeZoneConfiguration;

/** {@hide} */
oneway interface ITimeZoneConfigurationListener {
    void onChange(in TimeZoneConfiguration configuration);
    void onChange();
}
 No newline at end of file
+2 −4
Original line number Diff line number Diff line
@@ -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);
}
+77 −32
Original line number Diff line number Diff line
@@ -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. */
+86 −62
Original line number Diff line number Diff line
@@ -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. */
    /** Returns the ID of the user this configuration is associated with. */
    public @UserIdInt int getUserId() {
        return mUserId;
    }

    /** Returns {@code true} if all known settings are present. */
    public boolean isComplete() {
        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
                && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
        return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
                && hasSetting(SETTING_GEO_DETECTION_ENABLED);
    }

    /** Returns true if the specified property is set. */
    public boolean hasProperty(@Property String property) {
        return mBundle.containsKey(property);
    /** 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.
     *
     * <p>This setting is global and can be updated by some users.
     *
     * @throws IllegalStateException if the field has not been set
     * @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.
     *
     * <p>This setting is user-scoped and can be updated by some users.
     * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
     *
     * @throws IllegalStateException if the field has not been set
     * @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;
        }

+20 −22

File changed.

Preview size limit exceeded, changes collapsed.

Loading