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

Commit 7c770920 authored by Neil Fuller's avatar Neil Fuller
Browse files

Make TimeZoneDetector own its config

Make TimeZoneDetector responsible for configuration of time
zone detection behavior on device and have it enforce user capabilities.

This provides a set of configuration/capability classes, listeners and
tests that should allow SettingsUI apps to switch over to using them
while maintaining existing behavior and restrictions.

Ultimately, this should get us to a point where all time zone detection
configuration is conducted via TimeZoneDetector.updateConfiguration()
rather than modifying / listening to android.provider.Settings directly.

The listener support is intended for any users that currently watch
settings directly.

The TimeZoneCapabilities and TimeZoneConfiguration are designed to be
extensible so that more configuration properties can be added as the
time zone detector becomes more sophisticated. updateConfiguration()
supports partial configs so that clients only need to be explicit about
the config properties they know about and want to change.

This change is also a step towards removing race conditions in time zone
detection that could occur if settings are changed mid-way through a
time zone detection cycle: synchronization in
TimeZoneDetectorStrategyImpl ensures that the configuration won't change
unexpectedly.

Test: atest services/tests/servicestests/src/com/android/server/timezonedetector
Test: atest core/tests/coretests/src/android/app/timezonedetector
Change-Id: I3283b7fb7aa978df44a27ab7cd8cacdbe042b17b
parent 472e277c
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
/*
 * 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 android.app.timezonedetector;

import android.app.timezonedetector.TimeZoneConfiguration;

/** {@hide} */
oneway interface ITimeZoneConfigurationListener {
    void onChange(in TimeZoneConfiguration configuration);
}
 No newline at end of file
+9 −0
Original line number Diff line number Diff line
@@ -16,8 +16,11 @@

package android.app.timezonedetector;

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;

/**
 * System private API to communicate with time zone detector service.
@@ -33,6 +36,12 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 * {@hide}
 */
interface ITimeZoneDetectorService {
  TimeZoneCapabilities getCapabilities();

  TimeZoneConfiguration getConfiguration();
  boolean updateConfiguration(in TimeZoneConfiguration configuration);
  void addConfigurationListener(ITimeZoneConfigurationListener listener);

  boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
  void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
}
+19 −0
Original line number Diff line number Diff line
/*
 * 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 android.app.timezonedetector;

parcelable TimeZoneCapabilities;
+217 −0
Original line number Diff line number Diff line
/*
 * 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 android.app.timezonedetector;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * Time zone-related capabilities for a user. A capability is the ability for the user to configure
 * something or perform an action. This information is exposed so that system apps like SettingsUI
 * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
 * / available to the user.
 *
 * <p>Capabilities have states that users cannot change directly. They may influence some
 * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
 * by changing the configuration. See the {@code CAPABILITY_} constants for details.
 *
 * <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>Note: Capabilities are independent of app permissions required to call the associated APIs.
 *
 * @hide
 */
public final class TimeZoneCapabilities implements Parcelable {

    @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
            CAPABILITY_POSSESSED })
    @Retention(RetentionPolicy.SOURCE)
    public @interface CapabilityState {}

    /**
     * Indicates that a capability is not supported on this device, e.g. because of form factor or
     * hardware. The associated UI should usually not be shown to the user.
     */
    public static final int CAPABILITY_NOT_SUPPORTED = 10;

    /**
     * Indicates that a capability is supported on this device, but not allowed for the user.
     * 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.
     */
    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.
     */
    public static final int CAPABILITY_NOT_APPLICABLE = 30;

    /** Indicates that a capability is possessed by the user. */
    public static final int CAPABILITY_POSSESSED = 40;

    public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
            new Creator<TimeZoneCapabilities>() {
                public TimeZoneCapabilities createFromParcel(Parcel in) {
                    return TimeZoneCapabilities.createFromParcel(in);
                }

                public TimeZoneCapabilities[] newArray(int size) {
                    return new TimeZoneCapabilities[size];
                }
            };


    private final @UserIdInt int mUserId;
    private final @CapabilityState int mConfigureAutoDetectionEnabled;
    private final @CapabilityState int mSuggestManualTimeZone;

    private TimeZoneCapabilities(@NonNull Builder builder) {
        this.mUserId = builder.mUserId;
        this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
        this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
    }

    @NonNull
    private static TimeZoneCapabilities createFromParcel(Parcel in) {
        return new TimeZoneCapabilities.Builder(in.readInt())
                .setConfigureAutoDetectionEnabled(in.readInt())
                .setSuggestManualTimeZone(in.readInt())
                .build();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mUserId);
        dest.writeInt(mConfigureAutoDetectionEnabled);
        dest.writeInt(mSuggestManualTimeZone);
    }

    /** Returns the user ID the capabilities are for. */
    public @UserIdInt int getUserId() {
        return mUserId;
    }

    /**
     * Returns the user's capability state for controlling automatic time zone detection via
     * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
     * TimeZoneConfiguration#isAutoDetectionEnabled()}.
     */
    @CapabilityState
    public int getConfigureAutoDetectionEnabled() {
        return mConfigureAutoDetectionEnabled;
    }

    /**
     * Returns the user's capability state for manually setting 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()}.
     */
    @CapabilityState
    public int getSuggestManualTimeZone() {
        return mSuggestManualTimeZone;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        TimeZoneCapabilities that = (TimeZoneCapabilities) o;
        return mUserId == that.mUserId
                && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
                && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mUserId, mConfigureAutoDetectionEnabled, mSuggestManualTimeZone);
    }

    @Override
    public String toString() {
        return "TimeZoneDetectorCapabilities{"
                + "mUserId=" + mUserId
                + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
                + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
                + '}';
    }

    /** @hide */
    public static class Builder {

        private final @UserIdInt int mUserId;
        private @CapabilityState int mConfigureAutoDetectionEnabled;
        private @CapabilityState int mSuggestManualTimeZone;

        /**
         * Creates a new Builder with no properties set.
         */
        public Builder(@UserIdInt int userId) {
            mUserId = userId;
        }

        /** Sets the state for the automatic time zone detection enabled config. */
        public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) {
            this.mConfigureAutoDetectionEnabled = value;
            return this;
        }

        /** Sets the state for the suggestManualTimeZone action. */
        public Builder setSuggestManualTimeZone(@CapabilityState int value) {
            this.mSuggestManualTimeZone = value;
            return this;
        }

        /** Returns the {@link TimeZoneCapabilities}. */
        @NonNull
        public TimeZoneCapabilities build() {
            verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
            verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
            return new TimeZoneCapabilities(this);
        }

        private void verifyCapabilitySet(int value, String name) {
            if (value == 0) {
                throw new IllegalStateException(name + " not set");
            }
        }
    }
}
+19 −0
Original line number Diff line number Diff line
/*
 * 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 android.app.timezonedetector;

parcelable TimeZoneConfiguration;
Loading