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

Commit 2537e5d5 authored by Neil Fuller's avatar Neil Fuller
Browse files

Implement proposed device initialization APIs

APIs to improve time / time zone initialization flow.

Firstly, this change provides a way for SetUp Wizard and similar apps to
ask how confident the device is about time and time zone. This will
remove the need for SUW to watch for changes to the time / time zone
made by the system and infer confidence from that. This SUW "watching"
behavior means that the system cannot set low confidence time / time
zones while SUW is running, as the SUW will interpret them as meaning
the user doesn't need to confirm them.

The remaining methods are to remove the need for SUW and similar apps to
use low-level APIs to force the device's time / time zone into the state
the user wants:

Auto time / time zone detection is usually on by default when SUW runs.
Using low-level APIs to set the time and time zone bypass the system
server time and time zone services' automatic detection behavior. This
leads to either:
(a) the user not getting what they want, i.e. if SUW sets a value, the
detection systems may override them immediately because those auto
detection systems are still active.
(b) the auto detection systems leaving the device set to the values the
user provided incorrectly because they are not aware the low-level APIs
were used.  Typically, the detection systems will kick in at some point
later, leading to an unexpected "random" change if the SUW has forced an
"incorrect" setting.

Instead, what SUW should do is either:
1) Confirm the existing settings.
2) If the user wishes to diverge from the current time or time zone
   settings, there are two cases:
   2a) The device is currently set incorrectly because time / time zone
   detection has not yet determined values yet or has determined them
   incorrectly.
   2b) The user wishes to deliberately set the device to incorrect
   values.

For (1) there are new, dedicated "confirm" APIs. These explicitly handle
if the time / time zone changes while the user is looking at the UI; the
user would presumably be asked to reflect on their choice if they try to
confirm and it now differs from what they are confirming, i.e.  because
it has become case (2).

For (2), the current value is considered wrong by a user. New "get
state" APIs can be used by SUW to understand device confidence. For (2b)
the user is overriding a "confident" value and so SUW may want to inform
the user / ask if they are sure and and leave auto detection turned off
after setting the incorrect value. For a user to set a new value, the
high level APIs only allows this if auto detection is off. Auto
detection state can be determined by APIs exposed here. Auto detection
can be turned off via the APIs too and "manual" values can be set via
other APIs. Auto detection can then be turned back on (if required).[*]

[*] At any point after auto detection goes back on the device can choose
a different time / time zone from the one the user chose. The SUW could
wait for a short period to "settle" and re-check or just ignore this
possibility and proceed to the next step immediately.

Test: atest services/tests/servicestests/src/com/android/server/timedetector
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector
Test: atest core/tests/coretests/src/android/app/time
Bug: 236612872
Change-Id: I1903569907e7e268155814193379b7eef2e75a8d
parent c5a6044a
Loading
Loading
Loading
Loading
+19 −18
Original line number Diff line number Diff line
@@ -57,21 +57,21 @@ public final class TimeCapabilities implements Parcelable {
    @NonNull
    private final UserHandle mUserHandle;
    private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
    private final @CapabilityState int mSuggestManualTimeCapability;
    private final @CapabilityState int mSetManualTimeCapability;

    private TimeCapabilities(@NonNull Builder builder) {
        this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
        this.mConfigureAutoDetectionEnabledCapability =
                builder.mConfigureAutoDetectionEnabledCapability;
        this.mSuggestManualTimeCapability = builder.mSuggestManualTimeCapability;
        this.mSetManualTimeCapability = builder.mSetManualTimeCapability;
    }

    @NonNull
    private static TimeCapabilities createFromParcel(Parcel in) {
    private static TimeCapabilities createFromParcel(@NonNull Parcel in) {
        UserHandle userHandle = UserHandle.readFromParcel(in);
        return new TimeCapabilities.Builder(userHandle)
                .setConfigureAutoDetectionEnabledCapability(in.readInt())
                .setSuggestManualTimeCapability(in.readInt())
                .setSetManualTimeCapability(in.readInt())
                .build();
    }

@@ -79,7 +79,7 @@ public final class TimeCapabilities implements Parcelable {
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        UserHandle.writeToParcel(mUserHandle, dest);
        dest.writeInt(mConfigureAutoDetectionEnabledCapability);
        dest.writeInt(mSuggestManualTimeCapability);
        dest.writeInt(mSetManualTimeCapability);
    }

    /**
@@ -94,11 +94,12 @@ public final class TimeCapabilities implements Parcelable {

    /**
     * Returns the capability state associated with the user's ability to manually set time on a
     * device.
     * device. The setting can be updated via {@link
     * TimeManager#updateTimeConfiguration(TimeConfiguration)}.
     */
    @CapabilityState
    public int getSuggestManualTimeCapability() {
        return mSuggestManualTimeCapability;
    public int getSetManualTimeCapability() {
        return mSetManualTimeCapability;
    }

    /**
@@ -136,14 +137,14 @@ public final class TimeCapabilities implements Parcelable {
        TimeCapabilities that = (TimeCapabilities) o;
        return mConfigureAutoDetectionEnabledCapability
                == that.mConfigureAutoDetectionEnabledCapability
                && mSuggestManualTimeCapability == that.mSuggestManualTimeCapability
                && mSetManualTimeCapability == that.mSetManualTimeCapability
                && mUserHandle.equals(that.mUserHandle);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
                mSuggestManualTimeCapability);
                mSetManualTimeCapability);
    }

    @Override
@@ -152,7 +153,7 @@ public final class TimeCapabilities implements Parcelable {
                + "mUserHandle=" + mUserHandle
                + ", mConfigureAutoDetectionEnabledCapability="
                + mConfigureAutoDetectionEnabledCapability
                + ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability
                + ", mSetManualTimeCapability=" + mSetManualTimeCapability
                + '}';
    }

@@ -165,7 +166,7 @@ public final class TimeCapabilities implements Parcelable {

        @NonNull private final UserHandle mUserHandle;
        private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
        private @CapabilityState int mSuggestManualTimeCapability;
        private @CapabilityState int mSetManualTimeCapability;

        public Builder(@NonNull UserHandle userHandle) {
            this.mUserHandle = Objects.requireNonNull(userHandle);
@@ -176,18 +177,18 @@ public final class TimeCapabilities implements Parcelable {
            this.mUserHandle = timeCapabilities.mUserHandle;
            this.mConfigureAutoDetectionEnabledCapability =
                    timeCapabilities.mConfigureAutoDetectionEnabledCapability;
            this.mSuggestManualTimeCapability = timeCapabilities.mSuggestManualTimeCapability;
            this.mSetManualTimeCapability = timeCapabilities.mSetManualTimeCapability;
        }

        /** Sets the state for automatic time detection config. */
        /** Sets the value for the "configure automatic time detection" capability. */
        public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
            this.mConfigureAutoDetectionEnabledCapability = value;
            return this;
        }

        /** Sets the state for manual time change. */
        public Builder setSuggestManualTimeCapability(@CapabilityState int value) {
            this.mSuggestManualTimeCapability = value;
        /** Sets the value for the "set manual time" capability. */
        public Builder setSetManualTimeCapability(@CapabilityState int value) {
            this.mSetManualTimeCapability = value;
            return this;
        }

@@ -195,7 +196,7 @@ public final class TimeCapabilities implements Parcelable {
        public TimeCapabilities build() {
            verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
                    "configureAutoDetectionEnabledCapability");
            verifyCapabilitySet(mSuggestManualTimeCapability, "mSuggestManualTimeCapability");
            verifyCapabilitySet(mSetManualTimeCapability, "mSetManualTimeCapability");
            return new TimeCapabilities(this);
        }

+0 −4
Original line number Diff line number Diff line
@@ -71,8 +71,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {

    /**
     * Returns the user's time behaviour capabilities.
     *
     * @hide
     */
    @NonNull
    public TimeCapabilities getCapabilities() {
@@ -81,8 +79,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {

    /**
     * Returns the user's time behaviour configuration.
     *
     * @hide
     */
    @NonNull
    public TimeConfiguration getConfiguration() {
+6 −0
Original line number Diff line number Diff line
@@ -55,10 +55,16 @@ public final class TimeConfiguration implements Parcelable {
                }
            };

    /**
     * All configuration properties
     *
     * @hide
     */
    @StringDef(SETTING_AUTO_DETECTION_ENABLED)
    @Retention(RetentionPolicy.SOURCE)
    @interface Setting {}

    /** See {@link TimeConfiguration#isAutoDetectionEnabled()} for details. */
    @Setting
    private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";

+151 −0
Original line number Diff line number Diff line
@@ -21,11 +21,14 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.TimestampedValue;
import android.util.ArrayMap;
import android.util.Log;

@@ -274,4 +277,152 @@ public final class TimeManager {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns a snapshot of the device's current system clock time state. See also {@link
     * #confirmTime(UnixEpochTime)} for how this information can be used.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    @NonNull
    public TimeState getTimeState() {
        if (DEBUG) {
            Log.d(TAG, "getTimeState called");
        }
        try {
            return mITimeDetectorService.getTimeState();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Confirms the device's current time during device setup, raising the system's confidence in
     * the time if needed. Unlike {@link #setManualTime(UnixEpochTime)}, which can only be used when
     * automatic time detection is currently disabled, this method can be used regardless of the
     * automatic time detection setting, but only to confirm the current time (which may have been
     * set via automatic means). Use {@link #getTimeState()} to obtain the time state to confirm.
     *
     * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
     * confirmed is no longer the time the device is currently set to. Confirming a time
     * in which the system already has high confidence will return {@code true}.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
        if (DEBUG) {
            Log.d(TAG, "confirmTime called: " + unixEpochTime);
        }
        try {
            return mITimeDetectorService.confirmTime(unixEpochTime);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Attempts to set the device's time, expected to be determined from the user's manually entered
     * information.
     *
     * <p>Returns {@code false} if the time is invalid, or the device configuration / user
     * capabilities prevents the time being accepted, e.g. if the device is currently set to
     * "automatic time detection". This method returns {@code true} if the time was accepted even
     * if it is the same as the current device time.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
        if (DEBUG) {
            Log.d(TAG, "setTime called: " + unixEpochTime);
        }
        try {
            TimestampedValue<Long> manualTime = new TimestampedValue<>(
                    unixEpochTime.getElapsedRealtimeMillis(),
                    unixEpochTime.getUnixEpochTimeMillis());
            ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(manualTime);
            manualTimeSuggestion.addDebugInfo("TimeManager.setTime()");
            manualTimeSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
            manualTimeSuggestion.addDebugInfo("UserHandle: " + android.os.Process.myUserHandle());
            manualTimeSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
            return mITimeDetectorService.setManualTime(manualTimeSuggestion);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns a snapshot of the device's current time zone state. See also {@link
     * #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
     * be used.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    @NonNull
    public TimeZoneState getTimeZoneState() {
        if (DEBUG) {
            Log.d(TAG, "getTimeZoneState called");
        }
        try {
            return mITimeZoneDetectorService.getTimeZoneState();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Confirms the device's current time zone ID, raising the system's confidence in the time zone
     * if needed. Unlike {@link #setManualTimeZone(String)}, which can only be used when automatic
     * time zone detection is currently disabled, this method can be used regardless of the
     * automatic time zone detection setting, but only to confirm the current value (which may have
     * been set via automatic means).
     *
     * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
     * confirmed is no longer the time zone ID the device is currently set to. Confirming a time
     * zone ID in which the system already has high confidence returns {@code true}.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    public boolean confirmTimeZone(@NonNull String timeZoneId) {
        if (DEBUG) {
            Log.d(TAG, "confirmTimeZone called: " + timeZoneId);
        }
        try {
            return mITimeZoneDetectorService.confirmTimeZone(timeZoneId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Attempts to set the device's time zone, expected to be determined from a user's manually
     * entered information.
     *
     * <p>Returns {@code false} if the time zone is invalid, or the device configuration / user
     * capabilities prevents the time zone being accepted, e.g. if the device is currently set to
     * "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
     * time zone that is accepted and matches the current device time zone returns {@code true}.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
    public boolean setManualTimeZone(@NonNull String timeZoneId) {
        if (DEBUG) {
            Log.d(TAG, "setManualTimeZone called: " + timeZoneId);
        }
        try {
            ManualTimeZoneSuggestion manualTimeZoneSuggestion =
                    new ManualTimeZoneSuggestion(timeZoneId);
            manualTimeZoneSuggestion.addDebugInfo("TimeManager.setManualTimeZone()");
            manualTimeZoneSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
            manualTimeZoneSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
            return mITimeZoneDetectorService.setManualTimeZone(manualTimeZoneSuggestion);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.time;

parcelable TimeState;
Loading