diff options
| author | 2022-10-05 15:45:42 +0000 | |
|---|---|---|
| committer | 2022-10-05 15:45:42 +0000 | |
| commit | 5a86476d87be54a5a122cadad4bb0e02380a2694 (patch) | |
| tree | 6e418bc93a482f78fb3ac336a3b06d4a952d0b6a | |
| parent | b0a9e7883d3260a3357e91a99f0d675c36bc03b1 (diff) | |
| parent | 2537e5d5b6d1008c3971a9eaa89200b962f69e31 (diff) | |
Merge changes from topic "initialization_apis"
* changes:
Implement proposed device initialization APIs
Improve debug / bug report logging for time zones
46 files changed, 2286 insertions, 224 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 232d3c9aaa53..901796be9064 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2153,7 +2153,7 @@ public class AlarmManagerService extends SystemService { } } - void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence) { + void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence, String logInfo) { if (TextUtils.isEmpty(tzId)) { return; } @@ -2166,7 +2166,7 @@ public class AlarmManagerService extends SystemService { // TimeZone.getTimeZone() can return a time zone with a different ID (e.g. it can return // "GMT" if the ID is unrecognized). The parameter ID is used here rather than // newZone.getId(). It will be rejected if it is invalid. - timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence); + timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo); if (KERNEL_TIME_ZONE_SYNC_ENABLED) { // Update the kernel timezone information @@ -2706,8 +2706,9 @@ public class AlarmManagerService extends SystemService { } @Override - public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) { - setTimeZoneImpl(tzId, confidence); + public void setTimeZone(String tzId, @TimeZoneConfidence int confidence, + String logInfo) { + setTimeZoneImpl(tzId, confidence, logInfo); } @Override @@ -3010,7 +3011,7 @@ public class AlarmManagerService extends SystemService { // of confidence, but since the time zone ID should come either from apps working on // behalf of the user or a developer, confidence is assumed "high". final int timeZoneConfidence = TIME_ZONE_CONFIDENCE_HIGH; - setTimeZoneImpl(tz, timeZoneConfidence); + setTimeZoneImpl(tz, timeZoneConfidence, "AlarmManager.setTimeZone() called"); } finally { Binder.restoreCallingIdentity(oldId); } diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java index 44bc1788fa6a..76bad58e924b 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 be4d01048cee..b6a081825757 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 11f6ed2a9f88..7d986983160e 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 d6acb8cd1076..6a833fd772af 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. + * + * <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(); + } + } } diff --git a/core/java/android/app/time/TimeState.aidl b/core/java/android/app/time/TimeState.aidl new file mode 100644 index 000000000000..70c31d85bd55 --- /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 000000000000..15411e547814 --- /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. + * + * <p>{@code mUnixEpochTime} contains a snapshot of the system clock time and elapsed realtime clock + * time. + * + * <p>{@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<TimeState> 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 <elapsed realtime millis>"); + pw.println(" --unix_epoch_time <Unix epoch time millis>"); + 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 895a8e491f8d..5d4629f81493 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. * - * <p>The suggestion will be ignored in all cases unless the value is {@link + * <p>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 000000000000..fc1962f3d5b9 --- /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 000000000000..efdff81b079a --- /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. + * + * <p>{@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. + * + * <p>{@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<TimeZoneState> 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 {<Olson 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 000000000000..3392e22580f1 --- /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 000000000000..2683547e9209 --- /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. + * + * <p>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 <elapsed realtime millis>"); + pw.println(" --unix_epoch_time <Unix epoch time millis>"); + 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<UnixEpochTime> CREATOR = + new ClassLoaderCreator<UnixEpochTime>() { + + @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<Long> 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 0eb2b5470f58..a0c898ed2904 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. * - * <p>Used by parts of the Android system with signals associated with the device's time to provide - * information to the Time Detector Service. - * - * <p>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. + * <p>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. * + * <p>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 db1614b32b59..9d996adc051a 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -85,6 +85,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}. * * @hide diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl index af0389a14c4b..47d8e77ae764 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. * * <p>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. * - * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through + * <p>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 bae1c1c7312d..0e9e28be8818 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -108,6 +108,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 * debugging. diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java index 9d7dde2141bb..c9b96c6071ee 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 000000000000..a03229060dfb --- /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 008272830335..3f7da8a6fbd0 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 000000000000..9786bb044cb2 --- /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 000000000000..cd753489b50e --- /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/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index 67aa2b9a89bb..b6a8227c2461 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -59,8 +59,9 @@ public interface AlarmManagerInternal { * @param tzId the time zone ID * @param confidence the confidence that {@code tzId} is correct, see {@link TimeZoneConfidence} * for details + * @param logInfo the reason the time zone is being changed, for bug report logging */ - void setTimeZone(String tzId, @TimeZoneConfidence int confidence); + void setTimeZone(String tzId, @TimeZoneConfidence int confidence, String logInfo); /** * Sets the device's current time and time confidence. diff --git a/services/core/java/com/android/server/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java index 7ce0831480dc..dd07081bda12 100644 --- a/services/core/java/com/android/server/SystemTimeZone.java +++ b/services/core/java/com/android/server/SystemTimeZone.java @@ -19,12 +19,15 @@ import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.LocalLog; import android.util.Slog; import com.android.i18n.timezone.ZoneInfoDb; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -70,6 +73,14 @@ public final class SystemTimeZone { */ public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_HIGH = 100; + /** + * An in-memory log that records the debug info related to the device's time zone setting. + * This is logged in bug reports to assist with debugging time zone detection issues. + */ + @NonNull + private static final LocalLog sTimeZoneDebugLog = + new LocalLog(30, false /* useLocalTimestamps */); + private SystemTimeZone() {} /** @@ -78,27 +89,44 @@ public final class SystemTimeZone { public static void initializeTimeZoneSettingsIfRequired() { String timezoneProperty = SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); if (!isValidTimeZoneId(timezoneProperty)) { - Slog.w(TAG, TIME_ZONE_SYSTEM_PROPERTY + " is not valid (" + timezoneProperty - + "); setting to " + DEFAULT_TIME_ZONE_ID); - setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW); + String logInfo = "initializeTimeZoneSettingsIfRequired():" + TIME_ZONE_SYSTEM_PROPERTY + + " is not valid (" + timezoneProperty + "); setting to " + + DEFAULT_TIME_ZONE_ID; + Slog.w(TAG, logInfo); + setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW, logInfo); } } /** - * Updates the device's time zone system property and associated metadata. Returns {@code true} - * if the device's time zone changed, {@code false} if the ID is invalid or the device is - * already set to the supplied ID. + * Adds an entry to the system time zone debug log that is included in bug reports. This method + * is intended to be used to record event that may lead to a time zone change, e.g. config or + * mode changes. + */ + public static void addDebugLogEntry(@NonNull String logMsg) { + sTimeZoneDebugLog.log(logMsg); + } + + /** + * Updates the device's time zone system property, associated metadata and adds an entry to the + * debug log. Returns {@code true} if the device's time zone changed, {@code false} if the ID is + * invalid or the device is already set to the supplied ID. * * <p>This method ensures the confidence metadata is set to the supplied value if the supplied * time zone ID is considered valid. * * <p>This method is intended only for use by the AlarmManager. When changing the device's time * zone other system service components must use {@link - * com.android.server.AlarmManagerInternal#setTimeZone(String, int)} to ensure that important + * AlarmManagerInternal#setTimeZone(String, int, String)} to ensure that important * system-wide side effects occur. */ - public static boolean setTimeZoneId(String timeZoneId, @TimeZoneConfidence int confidence) { + public static boolean setTimeZoneId( + @NonNull String timeZoneId, @TimeZoneConfidence int confidence, + @NonNull String logInfo) { if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) { + addDebugLogEntry("setTimeZoneId: Invalid time zone ID." + + " timeZoneId=" + timeZoneId + + ", confidence=" + confidence + + ", logInfo=" + logInfo); return false; } @@ -112,7 +140,14 @@ public final class SystemTimeZone { } timeZoneChanged = true; } - setTimeZoneConfidence(confidence); + boolean timeZoneConfidenceChanged = setTimeZoneConfidence(confidence); + if (timeZoneChanged || timeZoneConfidenceChanged) { + String logMsg = "Time zone or confidence set: " + + " (new) timeZoneId=" + timeZoneId + + ", (new) confidence=" + confidence + + ", logInfo=" + logInfo; + addDebugLogEntry(logMsg); + } } return timeZoneChanged; @@ -121,16 +156,18 @@ public final class SystemTimeZone { /** * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details. */ - private static void setTimeZoneConfidence(@TimeZoneConfidence int confidence) { + private static boolean setTimeZoneConfidence(@TimeZoneConfidence int newConfidence) { int currentConfidence = getTimeZoneConfidence(); - if (currentConfidence != confidence) { + if (currentConfidence != newConfidence) { SystemProperties.set( - TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(confidence)); + TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(newConfidence)); if (DEBUG) { Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence - + ", new=" + confidence); + + ", newConfidence=" + newConfidence); } + return true; } + return false; } /** Returns the time zone confidence value. See {@link TimeZoneConfidence} for details. */ @@ -148,6 +185,13 @@ public final class SystemTimeZone { return SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY); } + /** + * Dumps information about recent time zone decisions / changes to the supplied writer. + */ + public static void dump(PrintWriter writer) { + sTimeZoneDebugLog.dump(writer); + } + private static boolean isValidTimeZoneConfidence(@TimeZoneConfidence int confidence) { return confidence >= TIME_ZONE_CONFIDENCE_LOW && confidence <= TIME_ZONE_CONFIDENCE_HIGH; } diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java index 46f335ef9fa2..d9a4266e2812 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 0ea5f7a105d1..84013a755035 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 5c47abfff09e..4186330fd23c 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; @@ -272,6 +274,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(); Objects.requireNonNull(timeSignal); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java index d306d10f3c69..990c00feae16 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 <external suggestion opts>\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 <time state options>\n", SHELL_COMMAND_SET_TIME_STATE); + pw.printf(" Sets the current time state for tests.\n"); + pw.printf(" %s <unix epoch time options>\n", SHELL_COMMAND_CONFIRM_TIME); + pw.printf(" Tries to confirms the time, raising the confidence.\n"); pw.println(); ManualTimeSuggestion.printCommandLineOpts(pw); pw.println(); @@ -172,6 +207,10 @@ class TimeDetectorShellCommand extends ShellCommand { pw.println(); ExternalTimeSuggestion.printCommandLineOpts(pw); pw.println(); + TimeState.printCommandLineOpts(pw); + pw.println(); + UnixEpochTime.printCommandLineOpts(pw); + pw.println(); pw.printf("This service is also affected by the following device_config flags in the" + " %s namespace:\n", NAMESPACE_SYSTEM_TIME); pw.printf(" %s\n", KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 141cdcf0dbb9..ac2a391ca2c7 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; @@ -65,6 +67,25 @@ public interface TimeDetectorStrategy extends Dumpable { /** Used when a time value originated from an externally specified signal. */ @Origin int ORIGIN_EXTERNAL = 5; + /** Returns a snapshot of the system clock's state. See {@link TimeState} for details. */ + @NonNull + TimeState getTimeState(); + + /** + * Sets the system time state. See {@link TimeState} for details. Intended for use during + * testing to force the device's state, this bypasses the time detection logic. + */ + void setTimeState(@NonNull TimeState timeState); + + /** + * Signals that a user has confirmed the supplied time. If the {@code confirmationTime}, + * adjusted for elapsed time since it was created (expected to be with {@link + * #getTimeState()}), is very close to the clock's current state, then this can be used to + * raise the system's confidence in that time. Returns {@code true} if confirmation was + * successful (i.e. the time matched), {@code false} otherwise. + */ + boolean confirmTime(@NonNull UnixEpochTime confirmationTime); + /** Processes the suggested time from telephony sources. */ void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion); @@ -100,6 +121,7 @@ public interface TimeDetectorStrategy extends Dumpable { * Converts one of the {@code ORIGIN_} constants to a human readable string suitable for config * and debug usage. Throws an {@link IllegalArgumentException} if the value is unrecognized. */ + @NonNull static String originToString(@Origin int origin) { switch (origin) { case ORIGIN_MANUAL: diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index fe2760e9ce10..3235bf077305 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -17,6 +17,7 @@ package com.android.server.timedetector; import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH; +import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW; import static com.android.server.timedetector.TimeDetectorStrategy.originToString; import android.annotation.CurrentTimeMillisLong; @@ -25,6 +26,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; @@ -298,6 +301,75 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override + public synchronized TimeState getTimeState() { + boolean userShouldConfirmTime = mEnvironment.systemClockConfidence() < TIME_CONFIDENCE_HIGH; + UnixEpochTime unixEpochTime = new UnixEpochTime( + mEnvironment.elapsedRealtimeMillis(), mEnvironment.systemClockMillis()); + return new TimeState(unixEpochTime, userShouldConfirmTime); + } + + @Override + public synchronized void setTimeState(@NonNull TimeState timeState) { + Objects.requireNonNull(timeState); + + @TimeConfidence int confidence = timeState.getUserShouldConfirmTime() + ? TIME_CONFIDENCE_LOW : TIME_CONFIDENCE_HIGH; + mEnvironment.acquireWakeLock(); + try { + // The origin is a lie but this method is only used for command line / manual testing + // to force the device into a specific state. + @Origin int origin = ORIGIN_MANUAL; + UnixEpochTime unixEpochTime = timeState.getUnixEpochTime(); + setSystemClockAndConfidenceUnderWakeLock( + origin, unixEpochTime.toTimestampedValue(), confidence, "setTimeZoneState()"); + } finally { + mEnvironment.releaseWakeLock(); + } + } + + @Override + public synchronized boolean confirmTime(@NonNull UnixEpochTime confirmationTime) { + Objects.requireNonNull(confirmationTime); + + @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH; + @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence(); + boolean timeNeedsConfirmation = currentTimeConfidence < newTimeConfidence; + + // All system clock calculation take place under a wake lock. + mEnvironment.acquireWakeLock(); + try { + // Check if the specified time matches the current system clock time (closely + // enough) to raise the confidence. + long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); + long actualSystemClockMillis = mEnvironment.systemClockMillis(); + long adjustedAutoDetectedUnixEpochMillis = + confirmationTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis(); + long absTimeDifferenceMillis = + Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis); + int confidenceUpgradeThresholdMillis = + mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis(); + boolean timeConfirmed = absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis; + boolean updateConfidenceRequired = timeNeedsConfirmation && timeConfirmed; + if (updateConfidenceRequired) { + String logMsg = "Confirm system clock time." + + " confirmationTime=" + confirmationTime + + " newTimeConfidence=" + newTimeConfidence + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + " (old) actualSystemClockMillis=" + actualSystemClockMillis + + " currentTimeConfidence=" + currentTimeConfidence; + if (DBG) { + Slog.d(LOG_TAG, logMsg); + } + + mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg); + } + return timeConfirmed; + } finally { + mEnvironment.releaseWakeLock(); + } + } + + @Override public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) { // Empty time suggestion means that telephony network connectivity has been lost. // The passage of time is relentless, and we don't expect our users to use a time machine, @@ -799,7 +871,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { long absTimeDifferenceMillis = Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis); int confidenceUpgradeThresholdMillis = - mCurrentConfigurationInternal.getSystemClockConfidenceUpgradeThresholdMillis(); + mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis(); boolean updateConfidenceRequired = absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis; if (updateConfidenceRequired) { @@ -866,7 +938,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { boolean updateSystemClockRequired = absTimeDifference >= systemClockUpdateThreshold; @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence(); - boolean updateConfidenceRequired = newTimeConfidence > currentTimeConfidence; + boolean updateConfidenceRequired = newTimeConfidence != currentTimeConfidence; if (updateSystemClockRequired) { String logMsg = "Set system clock & confidence." diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index ef99d616cbec..d413febdcdb4 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -243,7 +243,7 @@ public final class ConfigurationInternal { } else { suggestManualTimeZoneCapability = CAPABILITY_POSSESSED; } - builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability); + builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability); return builder.build(); } diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index d973ca689600..4749f73d89e1 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -28,6 +28,7 @@ import com.android.server.LocalServices; import com.android.server.SystemTimeZone; import com.android.server.SystemTimeZone.TimeZoneConfidence; +import java.io.PrintWriter; import java.util.Objects; /** @@ -57,6 +58,7 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment } @Override + @NonNull public ConfigurationInternal getCurrentUserConfigurationInternal() { return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @@ -74,14 +76,25 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment @Override public void setDeviceTimeZoneAndConfidence( - @NonNull String zoneId, @TimeZoneConfidence int confidence) { + @NonNull String zoneId, @TimeZoneConfidence int confidence, + @NonNull String logInfo) { AlarmManagerInternal alarmManagerInternal = LocalServices.getService(AlarmManagerInternal.class); - alarmManagerInternal.setTimeZone(zoneId, confidence); + alarmManagerInternal.setTimeZone(zoneId, confidence, logInfo); } @Override public @ElapsedRealtimeLong long elapsedRealtimeMillis() { return SystemClock.elapsedRealtime(); } + + @Override + public void addDebugLogEntry(@NonNull String logMsg) { + SystemTimeZone.addDebugLogEntry(logMsg); + } + + @Override + public void dumpDebugLog(@NonNull PrintWriter printWriter) { + SystemTimeZone.dump(printWriter); + } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 59db855dcf25..0e58d91a7f65 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.time.ITimeZoneDetectorListener; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; @@ -313,6 +314,57 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } @Override + @NonNull + public TimeZoneState getTimeZoneState() { + enforceManageTimeZoneDetectorPermission(); + + final long token = mCallerIdentityInjector.clearCallingIdentity(); + try { + return mTimeZoneDetectorStrategy.getTimeZoneState(); + } finally { + mCallerIdentityInjector.restoreCallingIdentity(token); + } + } + + void setTimeZoneState(@NonNull TimeZoneState timeZoneState) { + enforceManageTimeZoneDetectorPermission(); + + final long token = mCallerIdentityInjector.clearCallingIdentity(); + try { + mTimeZoneDetectorStrategy.setTimeZoneState(timeZoneState); + } finally { + mCallerIdentityInjector.restoreCallingIdentity(token); + } + } + + @Override + public boolean confirmTimeZone(@NonNull String timeZoneId) { + enforceManageTimeZoneDetectorPermission(); + + final long token = mCallerIdentityInjector.clearCallingIdentity(); + try { + return mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId); + } finally { + mCallerIdentityInjector.restoreCallingIdentity(token); + } + } + + @Override + public boolean setManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { + enforceManageTimeZoneDetectorPermission(); + + // This calls suggestManualTimeZone() 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 = mCallerIdentityInjector.clearCallingIdentity(); + try { + return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion); + } finally { + mCallerIdentityInjector.restoreCallingIdentity(token); + } + } + + @Override public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { enforceSuggestManualTimeZonePermission(); Objects.requireNonNull(timeZoneSuggestion); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 4d808ff231f2..1b9f8e6cd66f 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -15,8 +15,10 @@ */ package com.android.server.timezonedetector; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIRM_TIME_ZONE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED; @@ -24,6 +26,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_TEL import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE; @@ -38,6 +41,7 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR import android.app.time.LocationTimeZoneManager; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.os.ShellCommand; @@ -83,6 +87,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return runSuggestTelephonyTimeZone(); case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK: return runEnableTelephonyFallback(); + case SHELL_COMMAND_GET_TIME_ZONE_STATE: + return runGetTimeZoneState(); + case SHELL_COMMAND_SET_TIME_ZONE_STATE: + return runSetTimeZoneState(); + case SHELL_COMMAND_CONFIRM_TIME_ZONE: + return runConfirmTimeZone(); case SHELL_COMMAND_DUMP_METRICS: return runDumpMetrics(); default: { @@ -183,6 +193,45 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return 0; } + private int runGetTimeZoneState() { + TimeZoneState timeZoneState = mInterface.getTimeZoneState(); + getOutPrintWriter().println(timeZoneState); + return 0; + } + + private int runSetTimeZoneState() { + TimeZoneState timeZoneState = TimeZoneState.parseCommandLineArgs(this); + mInterface.setTimeZoneState(timeZoneState); + return 0; + } + + private int runConfirmTimeZone() { + String timeZoneId = parseTimeZoneIdArg(this); + getOutPrintWriter().println(mInterface.confirmTimeZone(timeZoneId)); + return 0; + } + + private static String parseTimeZoneIdArg(ShellCommand cmd) { + String zoneId = null; + String opt; + while ((opt = cmd.getNextArg()) != null) { + switch (opt) { + case "--zone_id": { + zoneId = cmd.getNextArgRequired(); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + } + + if (zoneId == null) { + throw new IllegalArgumentException("No zoneId specified."); + } + return zoneId; + } + private int runDumpMetrics() { final PrintWriter pw = getOutPrintWriter(); MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState(); @@ -226,6 +275,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n"); pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE); pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n"); + pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE); + pw.printf(" Returns the current time zone setting state.\n"); + pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE); + pw.printf(" Sets the current time zone state for tests.\n"); + pw.printf(" %s <--zone_id Olson ID>\n", SHELL_COMMAND_CONFIRM_TIME_ZONE); + pw.printf(" Tries to confirms the time zone, raising the confidence.\n"); pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS); pw.printf(" Dumps the service metrics to stdout for inspection.\n"); pw.println(); @@ -235,6 +290,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.println(); TelephonyTimeZoneSuggestion.printCommandLineOpts(pw); pw.println(); + TimeZoneState.printCommandLineOpts(pw); + pw.println(); pw.printf("This service is also affected by the following device_config flags in the" + " %s namespace:\n", NAMESPACE_SYSTEM_TIME); pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 95ebd6803cd0..e4b2df1460ae 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -17,6 +17,7 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.util.IndentingPrintWriter; @@ -93,6 +94,24 @@ import android.util.IndentingPrintWriter; */ public interface TimeZoneDetectorStrategy extends Dumpable { + /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */ + @NonNull + TimeZoneState getTimeZoneState(); + + /** + * Sets the system time zone state. See {@link TimeZoneState} for details. Intended for use + * during testing to force the device's state, this bypasses the time zone detection logic. + */ + void setTimeZoneState(@NonNull TimeZoneState timeZoneState); + + /** + * Signals that a user has confirmed the time zone. If the {@code timeZoneId} is the same as + * the current time zone then this can be used to raise the system's confidence in that time + * zone. Returns {@code true} if confirmation was successful (i.e. the ID matched), + * {@code false} otherwise. + */ + boolean confirmTimeZone(@NonNull String timeZoneId); + /** * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}. diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 9b35779f6820..85986ddaa564 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -23,6 +23,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; +import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; @@ -30,19 +31,20 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.TimeZoneCapabilities; import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; import android.os.Handler; import android.os.TimestampedValue; import android.util.IndentingPrintWriter; -import android.util.LocalLog; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemTimeZone.TimeZoneConfidence; +import java.io.PrintWriter; import java.time.Duration; import java.util.List; import java.util.Objects; @@ -76,7 +78,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** - * Returns the device's currently configured time zone. + * Returns the device's currently configured time zone. May return an empty string. */ @NonNull String getDeviceTimeZone(); @@ -86,10 +88,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @TimeZoneConfidence int getDeviceTimeZoneConfidence(); /** - * Sets the device's time zone and associated confidence. + * Sets the device's time zone, associated confidence, and records a debug log entry. */ void setDeviceTimeZoneAndConfidence( - @NonNull String zoneId, @TimeZoneConfidence int confidence); + @NonNull String zoneId, @TimeZoneConfidence int confidence, + @NonNull String logInfo); /** * Returns the time according to the elapsed realtime clock, the same as {@link @@ -97,6 +100,16 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ @ElapsedRealtimeLong long elapsedRealtimeMillis(); + + /** + * Adds a standalone entry to the time zone debug log. + */ + void addDebugLogEntry(@NonNull String logMsg); + + /** + * Dumps the time zone debug log to the supplied {@link PrintWriter}. + */ + void dumpDebugLog(PrintWriter printWriter); } private static final String LOG_TAG = TimeZoneDetectorService.TAG; @@ -169,13 +182,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private final Environment mEnvironment; /** - * A log that records the decisions / decision metadata that affected the device's time zone. - * This is logged in bug reports to assist with debugging issues with detection. - */ - @NonNull - private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); - - /** * A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two * mappings: devices will have a small number of telephony devices and slotIndexes are assumed * to be stable. @@ -246,6 +252,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override + public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) { + Objects.requireNonNull(timeZoneId); + + String currentTimeZoneId = mEnvironment.getDeviceTimeZone(); + if (!currentTimeZoneId.equals(timeZoneId)) { + return false; + } + + if (mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH) { + mEnvironment.setDeviceTimeZoneAndConfidence(currentTimeZoneId, + TIME_ZONE_CONFIDENCE_HIGH, "confirmTimeZone: timeZoneId=" + timeZoneId); + } + return true; + } + + @Override + public synchronized TimeZoneState getTimeZoneState() { + boolean userShouldConfirmId = + mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH; + return new TimeZoneState(mEnvironment.getDeviceTimeZone(), userShouldConfirmId); + } + + @Override + public void setTimeZoneState(@NonNull TimeZoneState timeZoneState) { + Objects.requireNonNull(timeZoneState); + + @TimeZoneConfidence int confidence = timeZoneState.getUserShouldConfirmId() + ? TIME_ZONE_CONFIDENCE_LOW : TIME_ZONE_CONFIDENCE_HIGH; + mEnvironment.setDeviceTimeZoneAndConfidence( + timeZoneState.getId(), confidence, "setTimeZoneState()"); + } + + @Override public synchronized void suggestGeolocationTimeZone( @NonNull GeolocationTimeZoneSuggestion suggestion) { @@ -297,9 +336,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = currentUserConfig.createCapabilitiesAndConfig(); TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); - if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) { + if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) { Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually" - + ", capabilities=" + capabilities + + ": capabilities=" + capabilities + ", timeZoneId=" + timeZoneId + ", cause=" + cause); return false; @@ -349,11 +388,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); - String logMsg = "enableTelephonyTimeZoneFallbackMode" - + ": currentUserConfig=" + currentUserConfig + String logMsg = "enableTelephonyTimeZoneFallbackMode: " + + " currentUserConfig=" + currentUserConfig + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled; - logTimeZoneDetectorChange(logMsg); + logTimeZoneDebugInfo(logMsg); // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact. // If there is currently a certain geolocation suggestion, then the telephony fallback @@ -558,19 +597,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); - String logMsg = "disableTelephonyFallbackIfNeeded" - + ": mTelephonyTimeZoneFallbackEnabled=" - + mTelephonyTimeZoneFallbackEnabled; - logTimeZoneDetectorChange(logMsg); + String logMsg = "disableTelephonyFallbackIfNeeded:" + + " mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled; + logTimeZoneDebugInfo(logMsg); } } } - private void logTimeZoneDetectorChange(@NonNull String logMsg) { + private void logTimeZoneDebugInfo(@NonNull String logMsg) { if (DBG) { Slog.d(LOG_TAG, logMsg); } - mTimeZoneChangesLog.log(logMsg); + mEnvironment.addDebugLogEntry(logMsg); } /** @@ -598,7 +636,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD; if (!suggestionGoodEnough) { if (DBG) { - Slog.d(LOG_TAG, "Best suggestion not good enough." + Slog.d(LOG_TAG, "Best suggestion not good enough:" + " bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason); } @@ -611,12 +649,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat if (zoneId == null) { Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" + " bestTelephonySuggestion=" + bestTelephonySuggestion - + " detectionReason=" + detectionReason); + + ", detectionReason=" + detectionReason); return; } - String cause = "Found good suggestion." - + ", bestTelephonySuggestion=" + bestTelephonySuggestion + String cause = "Found good suggestion:" + + " bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason; setDeviceTimeZoneIfRequired(zoneId, cause); } @@ -634,9 +672,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat if (newZoneId.equals(currentZoneId) && newConfidence <= currentConfidence) { // No need to modify the device time zone settings. if (DBG) { - Slog.d(LOG_TAG, "No need to change the time zone;" - + " device is already set to newZoneId." - + ", newZoneId=" + newZoneId + Slog.d(LOG_TAG, "No need to change the time zone device is already set to newZoneId" + + ": newZoneId=" + newZoneId + ", cause=" + cause + ", currentScore=" + currentConfidence + ", newConfidence=" + newConfidence); @@ -644,13 +681,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return; } - mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence); - String logMsg = "Set device time zone or higher confidence." - + ", currentZoneId=" + currentZoneId - + ", newZoneId=" + newZoneId + String logInfo = "Set device time zone or higher confidence:" + + " newZoneId=" + newZoneId + ", cause=" + cause + ", newConfidence=" + newConfidence; - logTimeZoneDetectorChange(logMsg); + if (DBG) { + Slog.d(LOG_TAG, logInfo); + } + mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence, logInfo); } @GuardedBy("this") @@ -702,7 +740,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat String logMsg = "handleConfigurationInternalChanged:" + " oldConfiguration=" + mCurrentConfigurationInternal + ", newConfiguration=" + currentUserConfig; - logTimeZoneDetectorChange(logMsg); + logTimeZoneDebugInfo(logMsg); mCurrentConfigurationInternal = currentUserConfig; // The configuration change may have changed available suggestions or the way suggestions @@ -731,9 +769,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat + formatDebugString(mTelephonyTimeZoneFallbackEnabled)); ipw.decreaseIndent(); // level 2 - ipw.println("Time zone change log:"); + ipw.println("Time zone debug log:"); ipw.increaseIndent(); // level 2 - mTimeZoneChangesLog.dump(ipw); + mEnvironment.dumpDebugLog(ipw); ipw.decreaseIndent(); // level 2 ipw.println("Manual suggestion history:"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c43e298b3f28..a5613078bdcd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -12553,9 +12553,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { return false; } + String logInfo = "DevicePolicyManager.setTimeZone()"; mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManagerInternal() - .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH)); + .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH, logInfo)); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_TIME_ZONE) diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 830e2ac029c2..e2b25ef8da2a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -3438,7 +3438,7 @@ public class AlarmManagerServiceTest { public void setTimeZoneImpl() { final long durationMs = 20000L; when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); - mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH); + mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH, "AlarmManagerServiceTest"); final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL), diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 0bdcac655a14..ddb3049b5cd1 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4503,7 +4503,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { setupDeviceOwner(); dpm.setTimeZone(admin1, "Asia/Shanghai"); verify(getServices().alarmManagerInternal) - .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH); + .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString()); } @Test @@ -4519,7 +4519,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); dpm.setTimeZone(admin1, "Asia/Shanghai"); verify(getServices().alarmManagerInternal) - .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH); + .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java index 208f99ad63a9..a24afe6bc25e 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java @@ -79,7 +79,7 @@ public class ConfigurationInternalTest { TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeCapability()); + assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertTrue(configuration.isAutoDetectionEnabled()); @@ -98,7 +98,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_POSSESSED, - capabilities.getSuggestManualTimeCapability()); + capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertFalse(configuration.isAutoDetectionEnabled()); @@ -131,7 +131,7 @@ public class ConfigurationInternalTest { TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertTrue(configuration.isAutoDetectionEnabled()); @@ -149,7 +149,7 @@ public class ConfigurationInternalTest { TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertFalse(configuration.isAutoDetectionEnabled()); @@ -181,7 +181,7 @@ public class ConfigurationInternalTest { TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertTrue(configuration.isAutoDetectionEnabled()); @@ -199,7 +199,7 @@ public class ConfigurationInternalTest { TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability()); TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration(); assertFalse(configuration.isAutoDetectionEnabled()); diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java index 1c6add2ffd61..7b38fa014512 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java @@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.util.IndentingPrintWriter; @@ -30,6 +32,8 @@ import android.util.IndentingPrintWriter; * in tests. */ class FakeTimeDetectorStrategy implements TimeDetectorStrategy { + // State + private TimeState mTimeState; // Call tracking. private TelephonyTimeSuggestion mLastTelephonySuggestion; @@ -38,9 +42,26 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy { private NetworkTimeSuggestion mLastNetworkSuggestion; private GnssTimeSuggestion mLastGnssSuggestion; private ExternalTimeSuggestion mLastExternalSuggestion; + private UnixEpochTime mLastConfirmedTime; private boolean mDumpCalled; @Override + public TimeState getTimeState() { + return mTimeState; + } + + @Override + public void setTimeState(TimeState timeState) { + mTimeState = timeState; + } + + @Override + public boolean confirmTime(UnixEpochTime confirmationTime) { + mLastConfirmedTime = confirmationTime; + return false; + } + + @Override public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) { mLastTelephonySuggestion = timeSuggestion; } @@ -79,6 +100,7 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy { mLastNetworkSuggestion = null; mLastGnssSuggestion = null; mLastExternalSuggestion = null; + mLastConfirmedTime = null; mDumpCalled = false; } @@ -104,6 +126,10 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy { assertEquals(expectedSuggestion, mLastExternalSuggestion); } + void verifyConfirmTimeCalled(UnixEpochTime expectedConfirmationTime) { + assertEquals(mLastConfirmedTime, expectedConfirmationTime); + } + void verifyDumpCalled() { assertTrue(mDumpCalled); } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 67c8c4fc102f..12184894a943 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.timedetector; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -38,6 +39,8 @@ import static org.mockito.Mockito.when; import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; 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; @@ -424,6 +427,125 @@ public class TimeDetectorServiceTest { } @Test + public void testGetTimeState() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true); + mFakeTimeDetectorStrategy.setTimeState(fakeState); + + TimeState actualState = mTimeDetectorService.getTimeState(); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + assertEquals(actualState, fakeState); + } + + @Test(expected = SecurityException.class) + public void testGetTimeState_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + try { + mTimeDetectorService.getTimeState(); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testSetTimeState() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true); + mTimeDetectorService.setTimeState(state); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + assertEquals(mFakeTimeDetectorStrategy.getTimeState(), state); + } + + @Test(expected = SecurityException.class) + public void testSetTimeState_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true); + try { + mTimeDetectorService.setTimeState(state); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testConfirmTime() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + UnixEpochTime confirmationTime = new UnixEpochTime(12345L, 98765L); + // The fake strategy always returns false. + assertFalse(mTimeDetectorService.confirmTime(confirmationTime)); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + mFakeTimeDetectorStrategy.verifyConfirmTimeCalled(confirmationTime); + } + + @Test(expected = SecurityException.class) + public void testConfirmTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + try { + mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L)); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testSetManualTime() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion(); + + boolean expectedResult = true; // The test strategy always returns true. + assertEquals(expectedResult, + mTimeDetectorService.setManualTime(timeSuggestion)); + + // The service calls "suggestManualTime()" because the logic is the same. + mFakeTimeDetectorStrategy.verifySuggestManualTimeCalled( + mTestCallerIdentityInjector.getCallingUserId(), timeSuggestion); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + + @Test(expected = SecurityException.class) + public void testSetManualTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion(); + + try { + mTimeDetectorService.setManualTime(timeSuggestion); + fail("Expected SecurityException"); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test public void testDump() { when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) .thenReturn(PackageManager.PERMISSION_GRANTED); diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 060c31f2985e..9ec70dc5ec3b 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -18,6 +18,7 @@ package com.android.server.timedetector; import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH; import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW; +import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS; import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK; @@ -31,6 +32,8 @@ import static org.junit.Assert.fail; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; +import android.app.time.TimeState; +import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; @@ -300,7 +303,7 @@ public class TimeDetectorStrategyImplTest { final int confidenceUpgradeThresholdMillis = 1000; ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED) - .setSystemClockConfidenceUpgradeThresholdMillis( + .setSystemClockConfidenceThresholdMillis( confidenceUpgradeThresholdMillis) .build(); Script script = new Script() @@ -338,7 +341,7 @@ public class TimeDetectorStrategyImplTest { final int confidenceUpgradeThresholdMillis = 1000; ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED) - .setSystemClockConfidenceUpgradeThresholdMillis( + .setSystemClockConfidenceThresholdMillis( confidenceUpgradeThresholdMillis) .build(); Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_LOW) @@ -375,7 +378,7 @@ public class TimeDetectorStrategyImplTest { final int confidenceUpgradeThresholdMillis = 1000; ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED) - .setSystemClockConfidenceUpgradeThresholdMillis( + .setSystemClockConfidenceThresholdMillis( confidenceUpgradeThresholdMillis) .build(); Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_HIGH) @@ -1137,6 +1140,126 @@ public class TimeDetectorStrategyImplTest { } @Test + public void testGetTimeState() { + TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO; + Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) + .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW); + + UnixEpochTime systemClockTime = new UnixEpochTime(deviceTime.getReferenceTimeMillis(), + deviceTime.getValue().toEpochMilli()); + + // When confidence is low, the user should confirm. + script.assertGetTimeStateReturns(new TimeState(systemClockTime, true)); + + // When confidence is high, no need for the user to confirm. + script.pokeFakeClocks(deviceTime, TIME_ZONE_CONFIDENCE_HIGH) + .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH); + + script.assertGetTimeStateReturns(new TimeState(systemClockTime, false)); + } + + @Test + public void testSetTimeState() { + TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO; + Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) + .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW); + + + UnixEpochTime systemClockTime = new UnixEpochTime(11111L, 222222L); + boolean userShouldConfirmTime = false; + TimeState state = new TimeState(systemClockTime, userShouldConfirmTime); + script.simulateSetTimeState(state); + + UnixEpochTime expectedTime = systemClockTime.at(script.peekElapsedRealtimeMillis()); + long expectedTimeMillis = expectedTime.getUnixEpochTimeMillis(); + // userShouldConfirmTime == high confidence + script.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) + .verifySystemClockWasSetAndResetCallTracking(expectedTimeMillis); + + TimeState expectedTimeState = new TimeState(expectedTime, userShouldConfirmTime); + script.assertGetTimeStateReturns(expectedTimeState); + } + + @Test + public void testConfirmTime_autoDisabled() { + testConfirmTime(CONFIG_AUTO_ENABLED); + } + + @Test + public void testConfirmTime_autoEnabled() { + testConfirmTime(CONFIG_AUTO_ENABLED); + } + + private void testConfirmTime(ConfigurationInternal config) { + TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO; + Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED) + .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW); + + long maxConfidenceThreshold = config.getSystemClockConfidenceThresholdMillis(); + UnixEpochTime incorrectTime1 = + new UnixEpochTime( + deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold + 1, + deviceTime.getValue().toEpochMilli()); + script.simulateConfirmTime(incorrectTime1, false) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) + .verifySystemClockWasNotSetAndResetCallTracking(); + + UnixEpochTime incorrectTime2 = + new UnixEpochTime( + deviceTime.getReferenceTimeMillis(), + deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold + 1); + script.simulateConfirmTime(incorrectTime2, false) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Confirm using a time that is at the threshold. + UnixEpochTime correctTime1 = + new UnixEpochTime( + deviceTime.getReferenceTimeMillis(), + deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold); + script.simulateConfirmTime(correctTime1, true) + .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Reset back to low confidence. + script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Confirm using a time that is at the threshold. + UnixEpochTime correctTime2 = + new UnixEpochTime( + deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold, + deviceTime.getValue().toEpochMilli()); + script.simulateConfirmTime(correctTime2, true) + .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Reset back to low confidence. + script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW) + .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Confirm using a time that exactly matches. + UnixEpochTime correctTime3 = + new UnixEpochTime( + deviceTime.getReferenceTimeMillis(), + deviceTime.getValue().toEpochMilli()); + script.simulateConfirmTime(correctTime3, true) + .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Now try to confirm using another incorrect time: Confidence should remain high as the + // confirmation is ignored / returns false. + script.simulateConfirmTime(incorrectTime1, false) + .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) + .verifySystemClockWasNotSetAndResetCallTracking(); + } + + @Test public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() { ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED) @@ -1771,7 +1894,7 @@ public class TimeDetectorStrategyImplTest { assertEquals(expectedSystemClockMillis, mSystemClockMillis); } - public void verifySystemClockConfidence(@TimeConfidence int expectedConfidence) { + public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) { assertEquals(expectedConfidence, mSystemClockConfidence); } @@ -1879,6 +2002,12 @@ public class TimeDetectorStrategyImplTest { return simulateTimePassing(999); } + /** Calls {@link TimeDetectorStrategy#setTimeState(TimeState)}. */ + Script simulateSetTimeState(TimeState timeState) { + mTimeDetectorStrategy.setTimeState(timeState); + return this; + } + Script verifySystemClockWasNotSetAndResetCallTracking() { mFakeEnvironment.verifySystemClockNotSet(); mFakeEnvironment.resetCallTracking(); @@ -1892,7 +2021,7 @@ public class TimeDetectorStrategyImplTest { } Script verifySystemClockConfidence(@TimeConfidence int expectedConfidence) { - mFakeEnvironment.verifySystemClockConfidence(expectedConfidence); + mFakeEnvironment.verifySystemClockConfidenceLatest(expectedConfidence); return this; } @@ -1931,6 +2060,11 @@ public class TimeDetectorStrategyImplTest { return this; } + Script assertGetTimeStateReturns(TimeState expected) { + assertEquals(expected, mTimeDetectorStrategy.getTimeState()); + return this; + } + /** * White box test info: Returns the telephony suggestion that would be used, if any, given * the current elapsed real time clock and regardless of origin prioritization. @@ -2037,6 +2171,11 @@ public class TimeDetectorStrategyImplTest { long calculateTimeInMillisForNow(TimestampedValue<Long> unixEpochTime) { return TimeDetectorStrategy.getTimeAt(unixEpochTime, peekElapsedRealtimeMillis()); } + + Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) { + assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime)); + return this; + } } private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex, diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 767c466b74f0..810bd82f4431 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -76,7 +76,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_NOT_APPLICABLE, - capabilities.getSuggestManualTimeZoneCapability()); + capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -102,7 +102,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_POSSESSED, - capabilities.getSuggestManualTimeZoneCapability()); + capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -143,7 +143,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_NOT_ALLOWED, - capabilities.getSuggestManualTimeZoneCapability()); + capabilities.getSetManualTimeZoneCapability()); // This has user privacy implications so it is not restricted in the same way as others. assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -170,7 +170,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_NOT_ALLOWED, - capabilities.getSuggestManualTimeZoneCapability()); + capabilities.getSetManualTimeZoneCapability()); // This has user privacy implications so it is not restricted in the same way as others. assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -211,7 +211,7 @@ public class ConfigurationInternalTest { TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -235,7 +235,7 @@ public class ConfigurationInternalTest { TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -279,7 +279,7 @@ public class ConfigurationInternalTest { assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); assertEquals(CAPABILITY_NOT_APPLICABLE, - capabilities.getSuggestManualTimeZoneCapability()); + capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabledCapability()); @@ -303,7 +303,7 @@ public class ConfigurationInternalTest { TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabledCapability()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability()); assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabledCapability()); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index c9fc033e9086..339e5b23cd9a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -20,19 +20,40 @@ import static org.junit.Assert.assertTrue; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.util.IndentingPrintWriter; class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { + private TimeZoneState mTimeZoneState; + // Call tracking. private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion; private ManualTimeZoneSuggestion mLastManualSuggestion; + private Integer mLastManualSuggestionUserId; private TelephonyTimeZoneSuggestion mLastTelephonySuggestion; + private String mLastConfirmedTimeZone; private boolean mDumpCalled; @Override + public boolean confirmTimeZone(String timeZoneId) { + mLastConfirmedTimeZone = timeZoneId; + return false; + } + + @Override + public TimeZoneState getTimeZoneState() { + return mTimeZoneState; + } + + @Override + public void setTimeZoneState(TimeZoneState timeZoneState) { + mTimeZoneState = timeZoneState; + } + + @Override public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) { mLastGeolocationSuggestion = timeZoneSuggestion; } @@ -40,6 +61,7 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { @Override public boolean suggestManualTimeZone( @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { + mLastManualSuggestionUserId = userId; mLastManualSuggestion = timeZoneSuggestion; return true; } @@ -78,8 +100,10 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { void resetCallTracking() { mLastGeolocationSuggestion = null; mLastManualSuggestion = null; + mLastManualSuggestionUserId = null; mLastTelephonySuggestion = null; mDumpCalled = false; + mLastConfirmedTimeZone = null; } void verifySuggestGeolocationTimeZoneCalled( @@ -87,7 +111,9 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { assertEquals(expectedSuggestion, mLastGeolocationSuggestion); } - void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) { + void verifySuggestManualTimeZoneCalled( + @UserIdInt int expectedUserId, ManualTimeZoneSuggestion expectedSuggestion) { + assertEquals((Integer) expectedUserId, mLastManualSuggestionUserId); assertEquals(expectedSuggestion, mLastManualSuggestion); } @@ -98,4 +124,8 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { void verifyDumpCalled() { assertTrue(mDumpCalled); } + + void verifyConfirmTimeZoneCalled(String expectedTimeZoneId) { + assertEquals(expectedTimeZoneId, mLastConfirmedTimeZone); + } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index 6365b98ce24f..b8c3ea7db91f 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -17,6 +17,7 @@ package com.android.server.timezonedetector; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +34,7 @@ import static org.mockito.Mockito.when; import android.app.time.ITimeZoneDetectorListener; import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; @@ -294,7 +296,8 @@ public class TimeZoneDetectorServiceTest { assertEquals(expectedResult, mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion)); - mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion); + mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled( + mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion); verify(mMockContext).enforceCallingOrSelfPermission( eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), @@ -350,6 +353,124 @@ public class TimeZoneDetectorServiceTest { } @Test + public void testGetTimeZoneState() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + TimeZoneState fakeState = new TimeZoneState("Europe/Narnia", true); + mFakeTimeZoneDetectorStrategy.setTimeZoneState(fakeState); + + TimeZoneState actualState = mTimeZoneDetectorService.getTimeZoneState(); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + assertEquals(actualState, fakeState); + } + + @Test(expected = SecurityException.class) + public void testGetTimeZoneState_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + try { + mTimeZoneDetectorService.getTimeZoneState(); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testSetTimeZoneState() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + TimeZoneState state = new TimeZoneState("Europe/Narnia", true); + mTimeZoneDetectorService.setTimeZoneState(state); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + assertEquals(mFakeTimeZoneDetectorStrategy.getTimeZoneState(), state); + } + + @Test(expected = SecurityException.class) + public void testSetTimeZoneState_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + TimeZoneState state = new TimeZoneState("Europe/Narnia", true); + try { + mTimeZoneDetectorService.setTimeZoneState(state); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testConfirmTimeZone() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + // The fake strategy always returns false. + assertFalse(mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia")); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + mFakeTimeZoneDetectorStrategy.verifyConfirmTimeZoneCalled("Europe/Narnia"); + } + + @Test(expected = SecurityException.class) + public void testConfirmTimeZone_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + try { + mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test + public void testSetManualTimeZone() { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + + boolean expectedResult = true; // The test strategy always returns true. + assertEquals(expectedResult, + mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion)); + + // The service calls "suggestManualTimeZone()" because the logic is the same. + mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled( + mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + + @Test(expected = SecurityException.class) + public void testSetManualTimeZone_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + + try { + mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion); + fail("Expected SecurityException"); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), + anyString()); + } + } + + @Test public void testDump() { when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) .thenReturn(PackageManager.PERMISSION_GRANTED); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index ff838fdf9d73..77d8b8bf9687 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -25,6 +25,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; +import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH; import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST; import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW; @@ -33,6 +34,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -40,6 +42,7 @@ import static org.junit.Assert.assertTrue; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.TimeZoneState; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; @@ -51,6 +54,7 @@ import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.Qualifie import org.junit.Before; import org.junit.Test; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -182,7 +186,7 @@ public class TimeZoneDetectorStrategyImplTest { TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion = createEmptySlotIndex2Suggestion(); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -295,7 +299,7 @@ public class TimeZoneDetectorStrategyImplTest { for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) { // Start with the device in a known state. - script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -347,7 +351,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testTelephonySuggestionsSingleSlotId() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -413,7 +417,7 @@ public class TimeZoneDetectorStrategyImplTest { TELEPHONY_SCORE_NONE); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED) .resetConfigurationTracking() @@ -552,7 +556,7 @@ public class TimeZoneDetectorStrategyImplTest { .setGeoDetectionEnabledSetting(geoDetectionEnabled) .build(); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(geoTzEnabledConfig) .resetConfigurationTracking(); @@ -567,7 +571,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_ENABLED) .resetConfigurationTracking(); @@ -582,7 +586,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -598,7 +602,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_DISABLED) .resetConfigurationTracking(); @@ -614,7 +618,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testManualSuggestion_autoDetectNotSupported() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DETECT_NOT_SUPPORTED) .resetConfigurationTracking(); @@ -630,7 +634,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testGeoSuggestion_uncertain() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) .resetConfigurationTracking(); @@ -647,7 +651,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testGeoSuggestion_noZones() { Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) .resetConfigurationTracking(); @@ -666,7 +670,7 @@ public class TimeZoneDetectorStrategyImplTest { createCertainGeolocationSuggestion("Europe/London"); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) .resetConfigurationTracking(); @@ -692,7 +696,7 @@ public class TimeZoneDetectorStrategyImplTest { createCertainGeolocationSuggestion("Europe/Paris"); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED) .resetConfigurationTracking(); @@ -733,7 +737,7 @@ public class TimeZoneDetectorStrategyImplTest { "Europe/Paris"); Script script = new Script() - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -779,7 +783,7 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script() .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -913,7 +917,7 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script() .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) - .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -969,6 +973,78 @@ public class TimeZoneDetectorStrategyImplTest { } @Test + public void testGetTimeZoneState() { + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) + .resetConfigurationTracking(); + + String timeZoneId = "Europe/London"; + + // When confidence is low, the user should confirm. + script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW); + assertEquals(new TimeZoneState(timeZoneId, true), + mTimeZoneDetectorStrategy.getTimeZoneState()); + + // When confidence is high, no need for the user to confirm. + script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH); + + assertEquals(new TimeZoneState(timeZoneId, false), + mTimeZoneDetectorStrategy.getTimeZoneState()); + } + + @Test + public void testSetTimeZoneState() { + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) + .resetConfigurationTracking(); + + String timeZoneId = "Europe/London"; + boolean userShouldConfirmId = false; + TimeZoneState state = new TimeZoneState(timeZoneId, userShouldConfirmId); + mTimeZoneDetectorStrategy.setTimeZoneState(state); + + script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH); + assertEquals(state, mTimeZoneDetectorStrategy.getTimeZoneState()); + } + + @Test + public void testConfirmTimeZone_autoDisabled() { + testConfirmTimeZone(CONFIG_AUTO_DISABLED_GEO_DISABLED); + } + + @Test + public void testConfirmTimeZone_autoEnabled() { + testConfirmTimeZone(CONFIG_AUTO_ENABLED_GEO_DISABLED); + } + + private void testConfirmTimeZone(ConfigurationInternal config) { + String timeZoneId = "Europe/London"; + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(config) + .resetConfigurationTracking(); + + String incorrectTimeZoneId = "Europe/Paris"; + assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId)); + script.verifyTimeZoneNotChanged(); + + assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId)); + script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH); + + assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId)); + // The strategy checks the current confidence and if it is already high it takes no action. + script.verifyTimeZoneNotChanged(); + + assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId)); + script.verifyTimeZoneNotChanged(); + } + + @Test public void testGenerateMetricsState_enhancedMetricsCollection() { testGenerateMetricsState(true); } @@ -986,7 +1062,7 @@ public class TimeZoneDetectorStrategyImplTest { String expectedDeviceTimeZoneId = "InitialZoneId"; Script script = new Script() - .initializeTimeZoneSetting(expectedDeviceTimeZoneId) + .initializeTimeZoneSetting(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(expectedInternalConfig) .resetConfigurationTracking(); @@ -1112,11 +1188,16 @@ public class TimeZoneDetectorStrategyImplTest { static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment { private final TestState<String> mTimeZoneId = new TestState<>(); - private int mTimeZoneConfidence; + private final TestState<Integer> mTimeZoneConfidence = new TestState<>(); private ConfigurationInternal mConfigurationInternal; private @ElapsedRealtimeLong long mElapsedRealtimeMillis; private ConfigurationChangeListener mConfigurationInternalChangeListener; + FakeEnvironment() { + // Ensure the fake environment starts with the defaults a fresh device would. + initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW); + } + void initializeConfig(ConfigurationInternal configurationInternal) { mConfigurationInternal = configurationInternal; } @@ -1125,8 +1206,9 @@ public class TimeZoneDetectorStrategyImplTest { mElapsedRealtimeMillis = elapsedRealtimeMillis; } - void initializeTimeZoneSetting(String zoneId) { + void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) { mTimeZoneId.init(zoneId); + mTimeZoneConfidence.init(timeZoneConfidence); } void incrementClock() { @@ -1150,14 +1232,14 @@ public class TimeZoneDetectorStrategyImplTest { @Override public int getDeviceTimeZoneConfidence() { - return mTimeZoneConfidence; + return mTimeZoneConfidence.getLatest(); } @Override public void setDeviceTimeZoneAndConfidence( - String zoneId, @TimeZoneConfidence int confidence) { + String zoneId, @TimeZoneConfidence int confidence, String logInfo) { mTimeZoneId.set(zoneId); - mTimeZoneConfidence = confidence; + mTimeZoneConfidence.set(confidence); } void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) { @@ -1167,17 +1249,22 @@ public class TimeZoneDetectorStrategyImplTest { void assertTimeZoneNotChanged() { mTimeZoneId.assertHasNotBeenSet(); + mTimeZoneConfidence.assertHasNotBeenSet(); } void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) { mTimeZoneId.assertHasBeenSet(); mTimeZoneId.assertChangeCount(1); mTimeZoneId.assertLatestEquals(timeZoneId); - assertEquals(confidence, mTimeZoneConfidence); + + mTimeZoneConfidence.assertHasBeenSet(); + mTimeZoneConfidence.assertChangeCount(1); + mTimeZoneConfidence.assertLatestEquals(confidence); } void commitAllChanges() { mTimeZoneId.commitLatest(); + mTimeZoneConfidence.commitLatest(); } @Override @@ -1185,6 +1272,16 @@ public class TimeZoneDetectorStrategyImplTest { public long elapsedRealtimeMillis() { return mElapsedRealtimeMillis; } + + @Override + public void addDebugLogEntry(String logMsg) { + // No-op for tests + } + + @Override + public void dumpDebugLog(PrintWriter printWriter) { + // No-op for tests + } } /** @@ -1193,8 +1290,9 @@ public class TimeZoneDetectorStrategyImplTest { */ private class Script { - Script initializeTimeZoneSetting(String zoneId) { - mFakeEnvironment.initializeTimeZoneSetting(zoneId); + Script initializeTimeZoneSetting( + String zoneId, @TimeZoneConfidence int timeZoneConfidence) { + mFakeEnvironment.initializeTimeZoneSetting(zoneId, timeZoneConfidence); return this; } @@ -1293,25 +1391,20 @@ public class TimeZoneDetectorStrategyImplTest { } Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) { - mFakeEnvironment.assertTimeZoneChangedTo( - suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH); - mFakeEnvironment.commitAllChanges(); + verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH); return this; } Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) { - mFakeEnvironment.assertTimeZoneChangedTo( - suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH); - mFakeEnvironment.commitAllChanges(); + verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH); return this; } Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) { assertEquals("Only use this method with unambiguous geo suggestions", 1, suggestion.getZoneIds().size()); - mFakeEnvironment.assertTimeZoneChangedTo( + verifyTimeZoneChangedAndReset( suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH); - mFakeEnvironment.commitAllChanges(); return this; } |