From 2537e5d5b6d1008c3971a9eaa89200b962f69e31 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Wed, 14 Sep 2022 14:18:11 +0100 Subject: [PATCH] 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 --- .../android/app/time/TimeCapabilities.java | 37 ++-- .../app/time/TimeCapabilitiesAndConfig.java | 4 - .../android/app/time/TimeConfiguration.java | 6 + core/java/android/app/time/TimeManager.java | 151 ++++++++++++++ core/java/android/app/time/TimeState.aidl | 19 ++ core/java/android/app/time/TimeState.java | 162 +++++++++++++++ .../app/time/TimeZoneCapabilities.java | 54 ++--- core/java/android/app/time/TimeZoneState.aidl | 19 ++ core/java/android/app/time/TimeZoneState.java | 150 ++++++++++++++ core/java/android/app/time/UnixEpochTime.aidl | 19 ++ core/java/android/app/time/UnixEpochTime.java | 190 ++++++++++++++++++ .../timedetector/ITimeDetectorService.aidl | 21 +- .../app/timedetector/TimeDetector.java | 18 ++ .../ITimeZoneDetectorService.aidl | 13 +- .../timezonedetector/TimeZoneDetector.java | 18 ++ .../app/time/TimeCapabilitiesTest.java | 34 ++-- .../src/android/app/time/TimeStateTest.java | 115 +++++++++++ .../app/time/TimeZoneCapabilitiesTest.java | 26 +-- .../android/app/time/TimeZoneStateTest.java | 106 ++++++++++ .../android/app/time/UnixEpochTimeTest.java | 130 ++++++++++++ .../timedetector/ConfigurationInternal.java | 34 ++-- .../ServiceConfigAccessorImpl.java | 2 +- .../timedetector/TimeDetectorService.java | 54 +++++ .../TimeDetectorShellCommand.java | 39 ++++ .../timedetector/TimeDetectorStrategy.java | 22 ++ .../TimeDetectorStrategyImpl.java | 76 ++++++- .../ConfigurationInternal.java | 2 +- .../TimeZoneDetectorService.java | 52 +++++ .../TimeZoneDetectorShellCommand.java | 57 ++++++ .../TimeZoneDetectorStrategy.java | 19 ++ .../TimeZoneDetectorStrategyImpl.java | 39 +++- .../ConfigurationInternalTest.java | 12 +- .../FakeTimeDetectorStrategy.java | 26 +++ .../timedetector/TimeDetectorServiceTest.java | 122 +++++++++++ .../TimeDetectorStrategyImplTest.java | 149 +++++++++++++- .../ConfigurationInternalTest.java | 16 +- .../FakeTimeZoneDetectorStrategy.java | 32 ++- .../TimeZoneDetectorServiceTest.java | 123 +++++++++++- .../TimeZoneDetectorStrategyImplTest.java | 146 +++++++++++--- 39 files changed, 2151 insertions(+), 163 deletions(-) create mode 100644 core/java/android/app/time/TimeState.aidl create mode 100644 core/java/android/app/time/TimeState.java create mode 100644 core/java/android/app/time/TimeZoneState.aidl create mode 100644 core/java/android/app/time/TimeZoneState.java create mode 100644 core/java/android/app/time/UnixEpochTime.aidl create mode 100644 core/java/android/app/time/UnixEpochTime.java create mode 100644 core/tests/coretests/src/android/app/time/TimeStateTest.java create mode 100644 core/tests/coretests/src/android/app/time/TimeZoneStateTest.java create mode 100644 core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java index 44bc1788fa6ad..76bad58e924ba 100644 --- a/core/java/android/app/time/TimeCapabilities.java +++ b/core/java/android/app/time/TimeCapabilities.java @@ -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); } diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java index be4d01048cee8..b6a0818257572 100644 --- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java @@ -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() { diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java index 11f6ed2a9f880..7d986983160ee 100644 --- a/core/java/android/app/time/TimeConfiguration.java +++ b/core/java/android/app/time/TimeConfiguration.java @@ -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"; diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java index d6acb8cd1076a..6a833fd772afa 100644 --- a/core/java/android/app/time/TimeManager.java +++ b/core/java/android/app/time/TimeManager.java @@ -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. + * + *

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. + * + *

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 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). + * + *

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. + * + *

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(); + } + } } diff --git a/core/java/android/app/time/TimeState.aidl b/core/java/android/app/time/TimeState.aidl new file mode 100644 index 0000000000000..70c31d85bd555 --- /dev/null +++ b/core/java/android/app/time/TimeState.aidl @@ -0,0 +1,19 @@ +/* + * 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; diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java new file mode 100644 index 0000000000000..15411e547814c --- /dev/null +++ b/core/java/android/app/time/TimeState.java @@ -0,0 +1,162 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ShellCommand; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * A snapshot of the system time state. + * + *

{@code mUnixEpochTime} contains a snapshot of the system clock time and elapsed realtime clock + * time. + * + *

{@code mUserShouldConfirmTime} is {@code true} if the system has low confidence in the system + * clock time. + * + * @hide + */ +public final class TimeState implements Parcelable { + + public static final @NonNull Creator CREATOR = new Creator<>() { + public TimeState createFromParcel(Parcel in) { + return TimeState.createFromParcel(in); + } + + public TimeState[] newArray(int size) { + return new TimeState[size]; + } + }; + + @NonNull private final UnixEpochTime mUnixEpochTime; + private final boolean mUserShouldConfirmTime; + + /** @hide */ + public TimeState(@NonNull UnixEpochTime unixEpochTime, boolean userShouldConfirmTime) { + mUnixEpochTime = Objects.requireNonNull(unixEpochTime); + mUserShouldConfirmTime = userShouldConfirmTime; + } + + private static TimeState createFromParcel(Parcel in) { + UnixEpochTime unixEpochTime = in.readParcelable(null, UnixEpochTime.class); + boolean userShouldConfirmId = in.readBoolean(); + return new TimeState(unixEpochTime, userShouldConfirmId); + } + + /** @hide */ + @Nullable + public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) { + Long elapsedRealtimeMillis = null; + Long unixEpochTimeMillis = null; + Boolean userShouldConfirmTime = null; + String opt; + while ((opt = cmd.getNextArg()) != null) { + switch (opt) { + case "--elapsed_realtime": { + elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired()); + break; + } + case "--unix_epoch_time": { + unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired()); + break; + } + case "--user_should_confirm_time": { + userShouldConfirmTime = Boolean.parseBoolean(cmd.getNextArgRequired()); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + } + + if (elapsedRealtimeMillis == null) { + throw new IllegalArgumentException("No elapsedRealtimeMillis specified."); + } + if (unixEpochTimeMillis == null) { + throw new IllegalArgumentException("No unixEpochTimeMillis specified."); + } + if (userShouldConfirmTime == null) { + throw new IllegalArgumentException("No userShouldConfirmTime specified."); + } + + UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis); + return new TimeState(unixEpochTime, userShouldConfirmTime); + } + + /** @hide */ + public static void printCommandLineOpts(@NonNull PrintWriter pw) { + pw.println("TimeState options:"); + pw.println(" --elapsed_realtime "); + pw.println(" --unix_epoch_time "); + pw.println(" --user_should_confirm_time {true|false}"); + pw.println(); + pw.println("See " + TimeState.class.getName() + " for more information"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mUnixEpochTime, 0); + dest.writeBoolean(mUserShouldConfirmTime); + } + + @NonNull + public UnixEpochTime getUnixEpochTime() { + return mUnixEpochTime; + } + + public boolean getUserShouldConfirmTime() { + return mUserShouldConfirmTime; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeState that = (TimeState) o; + return Objects.equals(mUnixEpochTime, that.mUnixEpochTime) + && mUserShouldConfirmTime == that.mUserShouldConfirmTime; + } + + @Override + public int hashCode() { + return Objects.hash(mUnixEpochTime, mUserShouldConfirmTime); + } + + @Override + public String toString() { + return "TimeState{" + + "mUnixEpochTime=" + mUnixEpochTime + + ", mUserShouldConfirmTime=" + mUserShouldConfirmTime + + '}'; + } +} diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java index 895a8e491f8d0..5d4629f814937 100644 --- a/core/java/android/app/time/TimeZoneCapabilities.java +++ b/core/java/android/app/time/TimeZoneCapabilities.java @@ -22,8 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.time.Capabilities.CapabilityState; -import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneDetector; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -61,7 +59,7 @@ public final class TimeZoneCapabilities implements Parcelable { @NonNull private final UserHandle mUserHandle; private final @CapabilityState int mConfigureAutoDetectionEnabledCapability; private final @CapabilityState int mConfigureGeoDetectionEnabledCapability; - private final @CapabilityState int mSuggestManualTimeZoneCapability; + private final @CapabilityState int mSetManualTimeZoneCapability; private TimeZoneCapabilities(@NonNull Builder builder) { this.mUserHandle = Objects.requireNonNull(builder.mUserHandle); @@ -69,16 +67,16 @@ public final class TimeZoneCapabilities implements Parcelable { builder.mConfigureAutoDetectionEnabledCapability; this.mConfigureGeoDetectionEnabledCapability = builder.mConfigureGeoDetectionEnabledCapability; - this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability; + this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability; } @NonNull - private static TimeZoneCapabilities createFromParcel(Parcel in) { + private static TimeZoneCapabilities createFromParcel(@NonNull Parcel in) { UserHandle userHandle = UserHandle.readFromParcel(in); return new TimeZoneCapabilities.Builder(userHandle) .setConfigureAutoDetectionEnabledCapability(in.readInt()) .setConfigureGeoDetectionEnabledCapability(in.readInt()) - .setSuggestManualTimeZoneCapability(in.readInt()) + .setSetManualTimeZoneCapability(in.readInt()) .build(); } @@ -87,7 +85,7 @@ public final class TimeZoneCapabilities implements Parcelable { UserHandle.writeToParcel(mUserHandle, dest); dest.writeInt(mConfigureAutoDetectionEnabledCapability); dest.writeInt(mConfigureGeoDetectionEnabledCapability); - dest.writeInt(mSuggestManualTimeZoneCapability); + dest.writeInt(mSetManualTimeZoneCapability); } /** @@ -112,17 +110,17 @@ public final class TimeZoneCapabilities implements Parcelable { /** * Returns the capability state associated with the user's ability to manually set the time zone - * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}. + * on a device. * - *

The suggestion will be ignored in all cases unless the value is {@link + *

The time zone will be ignored in all cases unless the value is {@link * Capabilities#CAPABILITY_POSSESSED}. See also * {@link TimeZoneConfiguration#isAutoDetectionEnabled()}. * * @hide */ @CapabilityState - public int getSuggestManualTimeZoneCapability() { - return mSuggestManualTimeZoneCapability; + public int getSetManualTimeZoneCapability() { + return mSetManualTimeZoneCapability; } /** @@ -174,13 +172,13 @@ public final class TimeZoneCapabilities implements Parcelable { == that.mConfigureAutoDetectionEnabledCapability && mConfigureGeoDetectionEnabledCapability == that.mConfigureGeoDetectionEnabledCapability - && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability; + && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability; } @Override public int hashCode() { return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability, - mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability); + mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability); } @Override @@ -191,17 +189,21 @@ public final class TimeZoneCapabilities implements Parcelable { + mConfigureAutoDetectionEnabledCapability + ", mConfigureGeoDetectionEnabledCapability=" + mConfigureGeoDetectionEnabledCapability - + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability + + ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability + '}'; } - /** @hide */ + /** + * A builder of {@link TimeZoneCapabilities} objects. + * + * @hide + */ public static class Builder { @NonNull private UserHandle mUserHandle; private @CapabilityState int mConfigureAutoDetectionEnabledCapability; private @CapabilityState int mConfigureGeoDetectionEnabledCapability; - private @CapabilityState int mSuggestManualTimeZoneCapability; + private @CapabilityState int mSetManualTimeZoneCapability; public Builder(@NonNull UserHandle userHandle) { mUserHandle = Objects.requireNonNull(userHandle); @@ -214,25 +216,27 @@ public final class TimeZoneCapabilities implements Parcelable { capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability; mConfigureGeoDetectionEnabledCapability = capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability; - mSuggestManualTimeZoneCapability = - capabilitiesToCopy.mSuggestManualTimeZoneCapability; + mSetManualTimeZoneCapability = + capabilitiesToCopy.mSetManualTimeZoneCapability; } - /** Sets the state for the automatic time zone detection enabled config. */ + /** Sets the value for the "configure automatic time zone detection enabled" capability. */ public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) { this.mConfigureAutoDetectionEnabledCapability = value; return this; } - /** Sets the state for the geolocation time zone detection enabled config. */ + /** + * Sets the value for the "configure geolocation time zone detection enabled" capability. + */ public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) { this.mConfigureGeoDetectionEnabledCapability = value; return this; } - /** Sets the state for the suggestManualTimeZone action. */ - public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) { - this.mSuggestManualTimeZoneCapability = value; + /** Sets the value for the "set manual time zone" capability. */ + public Builder setSetManualTimeZoneCapability(@CapabilityState int value) { + this.mSetManualTimeZoneCapability = value; return this; } @@ -243,8 +247,8 @@ public final class TimeZoneCapabilities implements Parcelable { "configureAutoDetectionEnabledCapability"); verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability, "configureGeoDetectionEnabledCapability"); - verifyCapabilitySet(mSuggestManualTimeZoneCapability, - "suggestManualTimeZoneCapability"); + verifyCapabilitySet(mSetManualTimeZoneCapability, + "mSetManualTimeZoneCapability"); return new TimeZoneCapabilities(this); } diff --git a/core/java/android/app/time/TimeZoneState.aidl b/core/java/android/app/time/TimeZoneState.aidl new file mode 100644 index 0000000000000..fc1962f3d5b91 --- /dev/null +++ b/core/java/android/app/time/TimeZoneState.aidl @@ -0,0 +1,19 @@ +/* + * 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 TimeZoneState; diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java new file mode 100644 index 0000000000000..efdff81b079af --- /dev/null +++ b/core/java/android/app/time/TimeZoneState.java @@ -0,0 +1,150 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ShellCommand; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * A snapshot of the system's time zone state. + * + *

{@code id} contains the system's time zone ID setting, e.g. "America/Los_Angeles". This + * will usually agree with {@code TimeZone.getDefault().getID()} but it can be empty in rare cases. + * + *

{@code userShouldConfirmId} is {@code true} if the system has low confidence in the current + * time zone. + * + * @hide + */ +public final class TimeZoneState implements Parcelable { + + public static final @NonNull Creator CREATOR = new Creator<>() { + public TimeZoneState createFromParcel(Parcel in) { + return TimeZoneState.createFromParcel(in); + } + + public TimeZoneState[] newArray(int size) { + return new TimeZoneState[size]; + } + }; + + @NonNull private final String mId; + private final boolean mUserShouldConfirmId; + + /** @hide */ + public TimeZoneState(@NonNull String id, boolean userShouldConfirmId) { + mId = Objects.requireNonNull(id); + mUserShouldConfirmId = userShouldConfirmId; + } + + private static TimeZoneState createFromParcel(Parcel in) { + String zoneId = in.readString(); + boolean userShouldConfirmId = in.readBoolean(); + return new TimeZoneState(zoneId, userShouldConfirmId); + } + + /** @hide */ + @Nullable + public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) { + String zoneIdString = null; + Boolean userShouldConfirmId = null; + String opt; + while ((opt = cmd.getNextArg()) != null) { + switch (opt) { + case "--zone_id": { + zoneIdString = cmd.getNextArgRequired(); + break; + } + case "--user_should_confirm_id": { + userShouldConfirmId = Boolean.parseBoolean(cmd.getNextArgRequired()); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + } + if (zoneIdString == null) { + throw new IllegalArgumentException("No zoneId specified."); + } + if (userShouldConfirmId == null) { + throw new IllegalArgumentException("No userShouldConfirmId specified."); + } + return new TimeZoneState(zoneIdString, userShouldConfirmId); + } + + /** @hide */ + public static void printCommandLineOpts(@NonNull PrintWriter pw) { + pw.println("TimeZoneState options:"); + pw.println(" --zone_id {}"); + pw.println(" --user_should_confirm_id {true|false}"); + pw.println(); + pw.println("See " + TimeZoneState.class.getName() + " for more information"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + dest.writeBoolean(mUserShouldConfirmId); + } + + @NonNull + public String getId() { + return mId; + } + + public boolean getUserShouldConfirmId() { + return mUserShouldConfirmId; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeZoneState that = (TimeZoneState) o; + return Objects.equals(mId, that.mId) + && mUserShouldConfirmId == that.mUserShouldConfirmId; + } + + @Override + public int hashCode() { + return Objects.hash(mId, mUserShouldConfirmId); + } + + @Override + public String toString() { + return "TimeZoneState{" + + "mZoneId=" + mId + + ", mUserShouldConfirmId=" + mUserShouldConfirmId + + '}'; + } +} diff --git a/core/java/android/app/time/UnixEpochTime.aidl b/core/java/android/app/time/UnixEpochTime.aidl new file mode 100644 index 0000000000000..3392e22580f19 --- /dev/null +++ b/core/java/android/app/time/UnixEpochTime.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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 UnixEpochTime; diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java new file mode 100644 index 0000000000000..2683547e92092 --- /dev/null +++ b/core/java/android/app/time/UnixEpochTime.java @@ -0,0 +1,190 @@ +/* + * 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; + +import android.annotation.ElapsedRealtimeLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ShellCommand; +import android.os.SystemClock; +import android.os.TimestampedValue; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * A Unix epoch time value with an associated reading from the elapsed realtime clock. + * When representing a device's system clock time, the Unix epoch time can be obtained using {@link + * System#currentTimeMillis()}. The Unix epoch time might also come from an external source + * depending on usage. + * + *

The elapsed realtime clock can be obtained using methods like {@link + * SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}. + * + * @hide + */ +public final class UnixEpochTime implements Parcelable { + @ElapsedRealtimeLong private final long mElapsedRealtimeMillis; + private final long mUnixEpochTimeMillis; + + public UnixEpochTime(@ElapsedRealtimeLong long elapsedRealtimeMillis, + long unixEpochTimeMillis) { + mElapsedRealtimeMillis = elapsedRealtimeMillis; + mUnixEpochTimeMillis = unixEpochTimeMillis; + } + + /** @hide */ + @NonNull + public static UnixEpochTime parseCommandLineArgs(ShellCommand cmd) { + Long elapsedRealtimeMillis = null; + Long unixEpochTimeMillis = null; + String opt; + while ((opt = cmd.getNextArg()) != null) { + switch (opt) { + case "--elapsed_realtime": { + elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired()); + break; + } + case "--unix_epoch_time": { + unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired()); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + } + + if (elapsedRealtimeMillis == null) { + throw new IllegalArgumentException("No elapsedRealtimeMillis specified."); + } + if (unixEpochTimeMillis == null) { + throw new IllegalArgumentException("No unixEpochTimeMillis specified."); + } + return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis); + } + + /** @hide */ + public static void printCommandLineOpts(PrintWriter pw) { + pw.println("UnixEpochTime options:\n"); + pw.println(" --elapsed_realtime "); + pw.println(" --unix_epoch_time "); + pw.println(); + pw.println("See " + UnixEpochTime.class.getName() + " for more information"); + } + + /** Returns the elapsed realtime clock value. See {@link UnixEpochTime} for more information. */ + public @ElapsedRealtimeLong long getElapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + /** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */ + @Nullable + public long getUnixEpochTimeMillis() { + return mUnixEpochTimeMillis; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UnixEpochTime that = (UnixEpochTime) o; + return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis + && Objects.equals(mUnixEpochTimeMillis, that.mUnixEpochTimeMillis); + } + + @Override + public int hashCode() { + return Objects.hash(mElapsedRealtimeMillis, mUnixEpochTimeMillis); + } + + @Override + public String toString() { + return "UnixEpochTime{" + + "mElapsedRealtimeTimeMillis=" + mElapsedRealtimeMillis + + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis + + '}'; + } + + public static final @NonNull Creator CREATOR = + new ClassLoaderCreator() { + + @Override + public UnixEpochTime createFromParcel(@NonNull Parcel source) { + return createFromParcel(source, null); + } + + @Override + public UnixEpochTime createFromParcel( + @NonNull Parcel source, @Nullable ClassLoader classLoader) { + long elapsedRealtimeMillis = source.readLong(); + long unixEpochTimeMillis = source.readLong(); + return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis); + } + + @Override + public UnixEpochTime[] newArray(int size) { + return new UnixEpochTime[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mElapsedRealtimeMillis); + dest.writeLong(mUnixEpochTimeMillis); + } + + /** + * Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this + * Unix epoch time by the difference between the elapsed realtime value supplied and the one + * associated with this instance. + * + * @hide + */ + public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) { + long adjustedUnixEpochTimeMillis = + (elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis; + return new UnixEpochTime(elapsedRealtimeTimeMillis, adjustedUnixEpochTimeMillis); + } + + /** + * Returns the difference in milliseconds between two instance's elapsed realtimes. + * + * @hide + */ + public static long elapsedRealtimeDifference( + @NonNull UnixEpochTime one, @NonNull UnixEpochTime two) { + return one.mElapsedRealtimeMillis - two.mElapsedRealtimeMillis; + } + + // TODO(b/246256335) Switch to using UnixEpochTime where possible and remove this method. + /** @hide */ + public TimestampedValue toTimestampedValue() { + return new TimestampedValue<>(mElapsedRealtimeMillis, mUnixEpochTimeMillis); + } +} diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index 0eb2b5470f58a..a0c898ed2904f 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -20,20 +20,23 @@ import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.app.timedetector.TimePoint; /** - * System private API to communicate with time detector service. + * Binder APIs to communicate with the time detector service. * - *

Used by parts of the Android system with signals associated with the device's time to provide - * information to the Time Detector Service. - * - *

Use the {@link android.app.timedetector.TimeDetector} class rather than going through - * this Binder interface directly. See {@link android.app.timedetector.TimeDetectorService} for - * more complete documentation. + *

Used to provide information to the Time Detector Service from other parts of the Android + * system that have access to time-related signals, e.g. telephony. Over time, System APIs have + * been added to support unbundled parts of the platform, e.g. SetUp Wizard. * + *

Use the {@link android.app.timedetector.TimeDetector} (internal API) and + * {@link android.app.time.TimeManager} (system API) classes rather than going through this Binder + * interface directly. See {@link android.app.timedetector.TimeDetectorService} for more complete + * documentation. * * {@hide} */ @@ -44,6 +47,10 @@ interface ITimeDetectorService { boolean updateConfiguration(in TimeConfiguration timeConfiguration); + TimeState getTimeState(); + boolean confirmTime(in UnixEpochTime time); + boolean setManualTime(in ManualTimeSuggestion timeZoneSuggestion); + void suggestExternalTime(in ExternalTimeSuggestion timeSuggestion); boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion); void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion); diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index db1614b32b597..9d996adc051a9 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -84,6 +84,24 @@ public interface TimeDetector { */ String SHELL_COMMAND_SUGGEST_EXTERNAL_TIME = "suggest_external_time"; + /** + * A shell command that retrieves the current system clock time state. + * @hide + */ + String SHELL_COMMAND_GET_TIME_STATE = "get_time_state"; + + /** + * A shell command that sets the current time state for testing. + * @hide + */ + String SHELL_COMMAND_SET_TIME_STATE = "set_time_state_for_tests"; + + /** + * A shell command that sets the confidence in the current time state for testing. + * @hide + */ + String SHELL_COMMAND_CONFIRM_TIME = "confirm_time"; + /** * A shared utility method to create a {@link ManualTimeSuggestion}. * diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl index af0389a14c4bd..47d8e77ae764b 100644 --- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl +++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl @@ -19,16 +19,19 @@ package android.app.timezonedetector; import android.app.time.ITimeZoneDetectorListener; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; /** - * System private API to communicate with time zone detector service. + * Binder APIs to communicate with time zone detector service. * *

Used to provide information to the Time Zone Detector Service from other parts of the Android - * system that have access to time zone-related signals, e.g. telephony. + * system that have access to time zone-related signals, e.g. telephony. Over time, System APIs have + * been added to support unbundled parts of the platform, e.g. SetUp Wizard. * - *

Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through + *

Use the {@link android.app.timezonedetector.TimeZoneDetector} (internal API) and + * {@link android.app.time.TimeManager} (system API) classes rather than going through * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService} * for more complete documentation. * @@ -41,6 +44,10 @@ interface ITimeZoneDetectorService { boolean updateConfiguration(in TimeZoneConfiguration configuration); + TimeZoneState getTimeZoneState(); + boolean confirmTimeZone(in String timeZoneId); + boolean setManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion); + boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion); void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion); } diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index bae1c1c7312d1..0e9e28be88188 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -107,6 +107,24 @@ public interface TimeZoneDetector { */ String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback"; + /** + * A shell command that retrieves the current time zone setting state. + * @hide + */ + String SHELL_COMMAND_GET_TIME_ZONE_STATE = "get_time_zone_state"; + + /** + * A shell command that sets the current time zone state for testing. + * @hide + */ + String SHELL_COMMAND_SET_TIME_ZONE_STATE = "set_time_zone_state_for_tests"; + + /** + * A shell command that sets the confidence in the current time zone state for testing. + * @hide + */ + String SHELL_COMMAND_CONFIRM_TIME_ZONE = "confirm_time_zone"; + /** * A shell command that dumps a {@link * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java index 9d7dde2141bb6..c9b96c6071ee4 100644 --- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java +++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java @@ -48,10 +48,10 @@ public class TimeCapabilitiesTest { public void testEquals() { TimeCapabilities.Builder builder1 = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED); + .setSetManualTimeCapability(CAPABILITY_POSSESSED); TimeCapabilities.Builder builder2 = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED); + .setSetManualTimeCapability(CAPABILITY_POSSESSED); { TimeCapabilities one = builder1.build(); TimeCapabilities two = builder2.build(); @@ -72,14 +72,14 @@ public class TimeCapabilitiesTest { assertEquals(one, two); } - builder2.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED); + builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED); { TimeCapabilities one = builder1.build(); TimeCapabilities two = builder2.build(); assertNotEquals(one, two); } - builder1.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED); + builder1.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED); { TimeCapabilities one = builder1.build(); TimeCapabilities two = builder2.build(); @@ -91,12 +91,12 @@ public class TimeCapabilitiesTest { public void userHandle_notIgnoredInEquals() { TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1)) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .setSetManualTimeCapability(CAPABILITY_POSSESSED) .build(); TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2)) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .setSetManualTimeCapability(CAPABILITY_POSSESSED) .build(); assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities); @@ -106,12 +106,12 @@ public class TimeCapabilitiesTest { public void testBuilder() { TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE) - .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED) + .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED) .build(); assertThat(capabilities.getConfigureAutoDetectionEnabledCapability()) .isEqualTo(CAPABILITY_NOT_APPLICABLE); - assertThat(capabilities.getSuggestManualTimeCapability()) + assertThat(capabilities.getSetManualTimeCapability()) .isEqualTo(CAPABILITY_NOT_SUPPORTED); try { @@ -133,7 +133,7 @@ public class TimeCapabilitiesTest { try { new TimeCapabilities.Builder(TEST_USER_HANDLE) - .setSuggestManualTimeCapability(CAPABILITY_NOT_APPLICABLE) + .setSetManualTimeCapability(CAPABILITY_NOT_APPLICABLE) .build(); fail("Should throw IllegalStateException"); } catch (IllegalStateException ignored) { @@ -145,11 +145,11 @@ public class TimeCapabilitiesTest { public void testParcelable() { TimeCapabilities.Builder builder = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED) - .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED); + .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED); assertRoundTripParcelable(builder.build()); - builder.setSuggestManualTimeCapability(CAPABILITY_POSSESSED); + builder.setSetManualTimeCapability(CAPABILITY_POSSESSED); assertRoundTripParcelable(builder.build()); builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED); @@ -164,7 +164,7 @@ public class TimeCapabilitiesTest { .build(); TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .setSetManualTimeCapability(CAPABILITY_POSSESSED) .build(); TimeConfiguration configChange = new TimeConfiguration.Builder() @@ -185,7 +185,7 @@ public class TimeCapabilitiesTest { .build(); TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED) .build(); TimeConfiguration configChange = new TimeConfiguration.Builder() @@ -199,7 +199,7 @@ public class TimeCapabilitiesTest { public void copyBuilder_copiesAllFields() { TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED) .build(); { @@ -210,7 +210,7 @@ public class TimeCapabilitiesTest { TimeCapabilities expectedCapabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED) .build(); assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); @@ -219,13 +219,13 @@ public class TimeCapabilitiesTest { { TimeCapabilities updatedCapabilities = new TimeCapabilities.Builder(capabilities) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .setSetManualTimeCapability(CAPABILITY_POSSESSED) .build(); TimeCapabilities expectedCapabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeCapability(CAPABILITY_POSSESSED) + .setSetManualTimeCapability(CAPABILITY_POSSESSED) .build(); assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java new file mode 100644 index 0000000000000..a03229060dfb3 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 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; + +import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.os.ShellCommand; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for non-SDK methods on {@link TimeState}. + */ +@RunWith(AndroidJUnit4.class) +public class TimeStateTest { + + @Test + public void testEqualsAndHashcode() { + UnixEpochTime time1 = new UnixEpochTime(1, 1); + TimeState time1False_1 = new TimeState(time1, false); + assertEqualsAndHashCode(time1False_1, time1False_1); + + TimeState time1False_2 = new TimeState(time1, false); + assertEqualsAndHashCode(time1False_1, time1False_2); + + TimeState time1True = new TimeState(time1, true); + assertNotEquals(time1False_1, time1True); + + UnixEpochTime time2 = new UnixEpochTime(2, 2); + TimeState time2False = new TimeState(time2, false); + assertNotEquals(time1False_1, time2False); + } + + private static void assertEqualsAndHashCode(Object one, Object two) { + assertEquals(one, two); + assertEquals(one.hashCode(), two.hashCode()); + } + + @Test + public void testParceling() { + UnixEpochTime time = new UnixEpochTime(1, 2); + TimeState value = new TimeState(time, true); + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(value, 0); + + parcel.setDataPosition(0); + + TimeState stringValueCopy = + parcel.readParcelable(null /* classLoader */, TimeState.class); + assertEquals(value, stringValueCopy); + } finally { + parcel.recycle(); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noElapsedRealtime() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--unix_epoch_time 12345 --user_should_confirm_time true"); + TimeState.parseCommandLineArgs(testShellCommand); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noUnixEpochTime() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321 --user_should_confirm_time true"); + TimeState.parseCommandLineArgs(testShellCommand); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noUserShouldConfirmTime() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--unix_epoch_time 12345 --elapsed_realtime 54321"); + TimeState.parseCommandLineArgs(testShellCommand); + } + + @Test + public void testParseCommandLineArg_validSuggestion() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true"); + TimeState expectedValue = new TimeState(new UnixEpochTime(54321L, 12345L), true); + TimeState actualValue = TimeState.parseCommandLineArgs(testShellCommand); + assertEquals(expectedValue, actualValue); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_unknownArgument() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true" + + " --bad_arg 0"); + TimeState.parseCommandLineArgs(testShellCommand); + } +} diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java index 008272830335a..3f7da8a6fbd06 100644 --- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java +++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java @@ -45,11 +45,11 @@ public class TimeZoneCapabilitiesTest { TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED); TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED); { TimeZoneCapabilities one = builder1.build(); TimeZoneCapabilities two = builder2.build(); @@ -84,14 +84,14 @@ public class TimeZoneCapabilitiesTest { assertEquals(one, two); } - builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + builder2.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); { TimeZoneCapabilities one = builder1.build(); TimeZoneCapabilities two = builder2.build(); assertNotEquals(one, two); } - builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + builder1.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); { TimeZoneCapabilities one = builder1.build(); TimeZoneCapabilities two = builder2.build(); @@ -104,7 +104,7 @@ public class TimeZoneCapabilitiesTest { TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED); assertRoundTripParcelable(builder.build()); builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); @@ -113,7 +113,7 @@ public class TimeZoneCapabilitiesTest { builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); assertRoundTripParcelable(builder.build()); - builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); assertRoundTripParcelable(builder.build()); } @@ -127,7 +127,7 @@ public class TimeZoneCapabilitiesTest { TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED) + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED) .build(); TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder() @@ -150,7 +150,7 @@ public class TimeZoneCapabilitiesTest { TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) .build(); TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder() @@ -165,7 +165,7 @@ public class TimeZoneCapabilitiesTest { TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) .build(); { @@ -177,7 +177,7 @@ public class TimeZoneCapabilitiesTest { new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) .build(); assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); @@ -193,7 +193,7 @@ public class TimeZoneCapabilitiesTest { new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) - .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) + .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) .build(); assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); @@ -202,14 +202,14 @@ public class TimeZoneCapabilitiesTest { { TimeZoneCapabilities updatedCapabilities = new TimeZoneCapabilities.Builder(capabilities) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED) + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED) .build(); TimeZoneCapabilities expectedCapabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED) + .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED) .build(); assertThat(updatedCapabilities).isEqualTo(expectedCapabilities); diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java new file mode 100644 index 0000000000000..9786bb044cb27 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 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; + +import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.os.ShellCommand; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for non-SDK methods on {@link TimeZoneState}. + */ +@RunWith(AndroidJUnit4.class) +public class TimeZoneStateTest { + + @Test + public void testEqualsAndHashcode() { + String zone1 = "Europe/London"; + TimeZoneState zone1False_1 = new TimeZoneState(zone1, false); + assertEqualsAndHashCode(zone1False_1, zone1False_1); + + TimeZoneState zone1False_2 = new TimeZoneState(zone1, false); + assertEqualsAndHashCode(zone1False_1, zone1False_2); + + TimeZoneState zone1True = new TimeZoneState(zone1, true); + assertNotEquals(zone1False_1, zone1True); + + String zone2 = "Europe/Parise"; + TimeZoneState zone2False = new TimeZoneState(zone2, false); + assertNotEquals(zone1False_1, zone2False); + } + + private static void assertEqualsAndHashCode(Object one, Object two) { + assertEquals(one, two); + assertEquals(one.hashCode(), two.hashCode()); + } + + @Test + public void testParceling() { + TimeZoneState value = new TimeZoneState("Europe/London", true); + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(value, 0); + + parcel.setDataPosition(0); + + TimeZoneState stringValueCopy = + parcel.readParcelable(null /* classLoader */, TimeZoneState.class); + assertEquals(value, stringValueCopy); + } finally { + parcel.recycle(); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noZoneId() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--user_should_confirm_id true"); + TimeZoneState.parseCommandLineArgs(testShellCommand); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noUserShouldConfirmId() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--zone_id Europe/London"); + TimeZoneState.parseCommandLineArgs(testShellCommand); + } + + @Test + public void testParseCommandLineArg_validSuggestion() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--zone_id Europe/London --user_should_confirm_id true"); + TimeZoneState expectedValue = new TimeZoneState("Europe/London", true); + TimeZoneState actualValue = TimeZoneState.parseCommandLineArgs(testShellCommand); + assertEquals(expectedValue, actualValue); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_unknownArgument() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--zone_id Europe/London --user_should_confirm_id true --bad_arg 0"); + TimeZoneState.parseCommandLineArgs(testShellCommand); + } +} diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java new file mode 100644 index 0000000000000..cd753489b50e8 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 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; + +import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.os.ShellCommand; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for non-SDK methods on {@link UnixEpochTime}. + */ +@RunWith(AndroidJUnit4.class) +public class UnixEpochTimeTest { + + @Test + public void testEqualsAndHashcode() { + UnixEpochTime one1000one = new UnixEpochTime(1000, 1); + assertEqualsAndHashCode(one1000one, one1000one); + + UnixEpochTime one1000two = new UnixEpochTime(1000, 1); + assertEqualsAndHashCode(one1000one, one1000two); + + UnixEpochTime two1000 = new UnixEpochTime(1000, 2); + assertNotEquals(one1000one, two1000); + + UnixEpochTime one2000 = new UnixEpochTime(2000, 1); + assertNotEquals(one1000one, one2000); + } + + private static void assertEqualsAndHashCode(Object one, Object two) { + assertEquals(one, two); + assertEquals(one.hashCode(), two.hashCode()); + } + + @Test + public void testParceling() { + UnixEpochTime value = new UnixEpochTime(1000, 1); + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(value, 0); + + parcel.setDataPosition(0); + + UnixEpochTime stringValueCopy = + parcel.readParcelable(null /* classLoader */, UnixEpochTime.class); + assertEquals(value, stringValueCopy); + } finally { + parcel.recycle(); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noElapsedRealtime() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--unix_epoch_time 12345"); + UnixEpochTime.parseCommandLineArgs(testShellCommand); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_noUnixEpochTime() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321"); + UnixEpochTime.parseCommandLineArgs(testShellCommand); + } + + @Test + public void testParseCommandLineArg_validSuggestion() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321 --unix_epoch_time 12345"); + UnixEpochTime expectedValue = new UnixEpochTime(54321L, 12345L); + UnixEpochTime actualValue = UnixEpochTime.parseCommandLineArgs(testShellCommand); + assertEquals(expectedValue, actualValue); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseCommandLineArg_unknownArgument() { + ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( + "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0"); + UnixEpochTime.parseCommandLineArgs(testShellCommand); + } + + @Test + public void testAt() { + long timeMillis = 1000L; + int elapsedRealtimeMillis = 100; + UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis); + // Reference time is after the timestamp. + UnixEpochTime at125 = unixEpochTime.at(125); + assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis()); + assertEquals(125, at125.getElapsedRealtimeMillis()); + + // Reference time is before the timestamp. + UnixEpochTime at75 = unixEpochTime.at(75); + assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis()); + assertEquals(75, at75.getElapsedRealtimeMillis()); + } + + @Test + public void testElapsedRealtimeDifference() { + UnixEpochTime value1 = new UnixEpochTime(1000, 123L); + assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1)); + + UnixEpochTime value2 = new UnixEpochTime(1, 321L); + assertEquals(999, UnixEpochTime.elapsedRealtimeDifference(value1, value2)); + assertEquals(-999, UnixEpochTime.elapsedRealtimeDifference(value2, value1)); + } +} diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java index 46f335ef9fa2d..d9a4266e28127 100644 --- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java @@ -46,7 +46,7 @@ public final class ConfigurationInternal { private final boolean mAutoDetectionSupported; private final int mSystemClockUpdateThresholdMillis; - private final int mSystemClockConfidenceUpgradeThresholdMillis; + private final int mSystemClockConfidenceThresholdMillis; private final Instant mAutoSuggestionLowerBound; private final Instant mManualSuggestionLowerBound; private final Instant mSuggestionUpperBound; @@ -58,8 +58,8 @@ public final class ConfigurationInternal { private ConfigurationInternal(Builder builder) { mAutoDetectionSupported = builder.mAutoDetectionSupported; mSystemClockUpdateThresholdMillis = builder.mSystemClockUpdateThresholdMillis; - mSystemClockConfidenceUpgradeThresholdMillis = - builder.mSystemClockConfidenceUpgradeThresholdMillis; + mSystemClockConfidenceThresholdMillis = + builder.mSystemClockConfidenceThresholdMillis; mAutoSuggestionLowerBound = Objects.requireNonNull(builder.mAutoSuggestionLowerBound); mManualSuggestionLowerBound = Objects.requireNonNull(builder.mManualSuggestionLowerBound); mSuggestionUpperBound = Objects.requireNonNull(builder.mSuggestionUpperBound); @@ -85,14 +85,14 @@ public final class ConfigurationInternal { } /** - * Return the absolute threshold at/below which the system clock confidence can be upgraded. - * i.e. if the detector receives a high-confidence time and the current system clock is +/- this - * value from that time and the confidence in the time is low, then the device's confidence in - * the current system clock time can be upgraded. This needs to be an amount users would - * consider "close enough". + * Return the absolute threshold for Unix epoch time comparison at/below which the system clock + * confidence can be said to be "close enough", e.g. if the detector receives a high-confidence + * time and the current system clock is +/- this value from that time and the current confidence + * in the time is low, then the device's confidence in the current system clock time can be + * upgraded. */ - public int getSystemClockConfidenceUpgradeThresholdMillis() { - return mSystemClockConfidenceUpgradeThresholdMillis; + public int getSystemClockConfidenceThresholdMillis() { + return mSystemClockConfidenceThresholdMillis; } /** @@ -194,7 +194,7 @@ public final class ConfigurationInternal { } else { suggestManualTimeZoneCapability = CAPABILITY_POSSESSED; } - builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability); + builder.setSetManualTimeCapability(suggestManualTimeZoneCapability); return builder.build(); } @@ -256,8 +256,8 @@ public final class ConfigurationInternal { return "ConfigurationInternal{" + "mAutoDetectionSupported=" + mAutoDetectionSupported + ", mSystemClockUpdateThresholdMillis=" + mSystemClockUpdateThresholdMillis - + ", mSystemClockConfidenceUpgradeThresholdMillis=" - + mSystemClockConfidenceUpgradeThresholdMillis + + ", mSystemClockConfidenceThresholdMillis=" + + mSystemClockConfidenceThresholdMillis + ", mAutoSuggestionLowerBound=" + mAutoSuggestionLowerBound + "(" + mAutoSuggestionLowerBound.toEpochMilli() + ")" + ", mManualSuggestionLowerBound=" + mManualSuggestionLowerBound @@ -274,7 +274,7 @@ public final class ConfigurationInternal { static final class Builder { private boolean mAutoDetectionSupported; private int mSystemClockUpdateThresholdMillis; - private int mSystemClockConfidenceUpgradeThresholdMillis; + private int mSystemClockConfidenceThresholdMillis; @NonNull private Instant mAutoSuggestionLowerBound; @NonNull private Instant mManualSuggestionLowerBound; @NonNull private Instant mSuggestionUpperBound; @@ -321,9 +321,9 @@ public final class ConfigurationInternal { return this; } - /** See {@link ConfigurationInternal#getSystemClockConfidenceUpgradeThresholdMillis()}. */ - public Builder setSystemClockConfidenceUpgradeThresholdMillis(int thresholdMillis) { - mSystemClockConfidenceUpgradeThresholdMillis = thresholdMillis; + /** See {@link ConfigurationInternal#getSystemClockConfidenceThresholdMillis()}. */ + public Builder setSystemClockConfidenceThresholdMillis(int thresholdMillis) { + mSystemClockConfidenceThresholdMillis = thresholdMillis; return this; } diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java index 0ea5f7a105d13..84013a7550354 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java @@ -245,7 +245,7 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { .setAutoDetectionSupported(isAutoDetectionSupported()) .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting()) .setSystemClockUpdateThresholdMillis(getSystemClockUpdateThresholdMillis()) - .setSystemClockConfidenceUpgradeThresholdMillis( + .setSystemClockConfidenceThresholdMillis( getSystemClockConfidenceUpgradeThresholdMillis()) .setAutoSuggestionLowerBound(getAutoSuggestionLowerBound()) .setManualSuggestionLowerBound(timeDetectorHelper.getManualSuggestionLowerBound()) diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 5c47abfff09eb..4186330fd23c2 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -24,6 +24,8 @@ import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; @@ -271,6 +273,58 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } + @Override + public TimeState getTimeState() { + enforceManageTimeDetectorPermission(); + + final long token = Binder.clearCallingIdentity(); + try { + return mTimeDetectorStrategy.getTimeState(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + void setTimeState(@NonNull TimeState timeState) { + enforceManageTimeDetectorPermission(); + + final long token = Binder.clearCallingIdentity(); + try { + mTimeDetectorStrategy.setTimeState(timeState); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean confirmTime(@NonNull UnixEpochTime time) { + enforceManageTimeDetectorPermission(); + Objects.requireNonNull(time); + + final long token = Binder.clearCallingIdentity(); + try { + return mTimeDetectorStrategy.confirmTime(time); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setManualTime(@NonNull ManualTimeSuggestion timeSignal) { + enforceManageTimeDetectorPermission(); + Objects.requireNonNull(timeSignal); + + // This calls suggestManualTime() as the logic is identical, it only differs in the + // permission required, which is handled on the line above. + int userId = mCallerIdentityInjector.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); + try { + return mTimeDetectorStrategy.suggestManualTime(userId, timeSignal); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) { enforceSuggestTelephonyTimePermission(); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java index d306d10f3c697..990c00feae16b 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java @@ -15,9 +15,12 @@ */ package com.android.server.timedetector; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME; @@ -30,6 +33,8 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIG import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeConfiguration; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.os.ShellCommand; @@ -69,6 +74,12 @@ class TimeDetectorShellCommand extends ShellCommand { return runSuggestGnssTime(); case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME: return runSuggestExternalTime(); + case SHELL_COMMAND_GET_TIME_STATE: + return runGetTimeState(); + case SHELL_COMMAND_SET_TIME_STATE: + return runSetTimeState(); + case SHELL_COMMAND_CONFIRM_TIME: + return runConfirmTime(); default: { return handleDefaultCommands(cmd); } @@ -140,6 +151,24 @@ class TimeDetectorShellCommand extends ShellCommand { } } + private int runGetTimeState() { + TimeState timeState = mInterface.getTimeState(); + getOutPrintWriter().println(timeState); + return 0; + } + + private int runSetTimeState() { + TimeState timeState = TimeState.parseCommandLineArgs(this); + mInterface.setTimeState(timeState); + return 0; + } + + private int runConfirmTime() { + UnixEpochTime unixEpochTime = UnixEpochTime.parseCommandLineArgs(this); + getOutPrintWriter().println(mInterface.confirmTime(unixEpochTime)); + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -161,6 +190,12 @@ class TimeDetectorShellCommand extends ShellCommand { pw.printf(" Suggests a time as if via the \"gnss\" origin.\n"); pw.printf(" %s \n", SHELL_COMMAND_SUGGEST_EXTERNAL_TIME); pw.printf(" Suggests a time as if via the \"external\" origin.\n"); + pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_STATE); + pw.printf(" Returns the current time setting state.\n"); + pw.printf(" %s