Implement proposed device initialization APIs
APIs to improve time / time zone initialization flow.
Firstly, this change provides a way for SetUp Wizard and similar apps to
ask how confident the device is about time and time zone. This will
remove the need for SUW to watch for changes to the time / time zone
made by the system and infer confidence from that. This SUW "watching"
behavior means that the system cannot set low confidence time / time
zones while SUW is running, as the SUW will interpret them as meaning
the user doesn't need to confirm them.
The remaining methods are to remove the need for SUW and similar apps to
use low-level APIs to force the device's time / time zone into the state
the user wants:
Auto time / time zone detection is usually on by default when SUW runs.
Using low-level APIs to set the time and time zone bypass the system
server time and time zone services' automatic detection behavior. This
leads to either:
(a) the user not getting what they want, i.e. if SUW sets a value, the
detection systems may override them immediately because those auto
detection systems are still active.
(b) the auto detection systems leaving the device set to the values the
user provided incorrectly because they are not aware the low-level APIs
were used. Typically, the detection systems will kick in at some point
later, leading to an unexpected "random" change if the SUW has forced an
"incorrect" setting.
Instead, what SUW should do is either:
1) Confirm the existing settings.
2) If the user wishes to diverge from the current time or time zone
settings, there are two cases:
2a) The device is currently set incorrectly because time / time zone
detection has not yet determined values yet or has determined them
incorrectly.
2b) The user wishes to deliberately set the device to incorrect
values.
For (1) there are new, dedicated "confirm" APIs. These explicitly handle
if the time / time zone changes while the user is looking at the UI; the
user would presumably be asked to reflect on their choice if they try to
confirm and it now differs from what they are confirming, i.e. because
it has become case (2).
For (2), the current value is considered wrong by a user. New "get
state" APIs can be used by SUW to understand device confidence. For (2b)
the user is overriding a "confident" value and so SUW may want to inform
the user / ask if they are sure and and leave auto detection turned off
after setting the incorrect value. For a user to set a new value, the
high level APIs only allows this if auto detection is off. Auto
detection state can be determined by APIs exposed here. Auto detection
can be turned off via the APIs too and "manual" values can be set via
other APIs. Auto detection can then be turned back on (if required).[*]
[*] At any point after auto detection goes back on the device can choose
a different time / time zone from the one the user chose. The SUW could
wait for a short period to "settle" and re-check or just ignore this
possibility and proceed to the next step immediately.
Test: atest services/tests/servicestests/src/com/android/server/timedetector
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector
Test: atest core/tests/coretests/src/android/app/time
Bug: 236612872
Change-Id: I1903569907e7e268155814193379b7eef2e75a8d
diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java
index 44bc178..76bad58 100644
--- a/core/java/android/app/time/TimeCapabilities.java
+++ b/core/java/android/app/time/TimeCapabilities.java
@@ -57,21 +57,21 @@
@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 void writeToParcel(@NonNull Parcel dest, int flags) {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
- dest.writeInt(mSuggestManualTimeCapability);
+ dest.writeInt(mSetManualTimeCapability);
}
/**
@@ -94,11 +94,12 @@
/**
* 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 @@
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 @@
+ "mUserHandle=" + mUserHandle
+ ", mConfigureAutoDetectionEnabledCapability="
+ mConfigureAutoDetectionEnabledCapability
- + ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability
+ + ", mSetManualTimeCapability=" + mSetManualTimeCapability
+ '}';
}
@@ -165,7 +166,7 @@
@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 @@
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 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 be4d010..b6a0818 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -71,8 +71,6 @@
/**
* Returns the user's time behaviour capabilities.
- *
- * @hide
*/
@NonNull
public TimeCapabilities getCapabilities() {
@@ -81,8 +79,6 @@
/**
* 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 11f6ed2..7d98698 100644
--- a/core/java/android/app/time/TimeConfiguration.java
+++ b/core/java/android/app/time/TimeConfiguration.java
@@ -55,10 +55,16 @@
}
};
+ /**
+ * 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 d6acb8c..6a833fd 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -21,11 +21,14 @@
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 @@
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 0000000..70c31d8
--- /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 0000000..15411e5
--- /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 895a8e4..5d4629f 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -22,8 +22,6 @@
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 @@
@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 @@
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 @@
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
dest.writeInt(mConfigureGeoDetectionEnabledCapability);
- dest.writeInt(mSuggestManualTimeZoneCapability);
+ dest.writeInt(mSetManualTimeZoneCapability);
}
/**
@@ -112,17 +110,17 @@
/**
* 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 @@
== 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 @@
+ 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 @@
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 @@
"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 0000000..fc1962f
--- /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 0000000..efdff81
--- /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 0000000..3392e22
--- /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 0000000..2683547
--- /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 0eb2b54..a0c898e 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.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>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} class rather than going through
- * this Binder interface directly. See {@link android.app.timedetector.TimeDetectorService} for
- * more complete documentation.
- *
+ * <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 @@
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 db1614b..9d996ad 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -85,6 +85,24 @@
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 af0389a..47d8e77 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -19,16 +19,19 @@
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 @@
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 bae1c1c..0e9e28b 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -108,6 +108,24 @@
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 9d7dde2..c9b96c6 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 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 @@
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 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 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 @@
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 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 @@
.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 @@
.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 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 @@
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 @@
{
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 0000000..a032290
--- /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 0082728..3f7da8a 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
{
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 0000000..9786bb0
--- /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 0000000..cd75348
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link UnixEpochTime}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class UnixEpochTimeTest {
+
+ @Test
+ public void testEqualsAndHashcode() {
+ UnixEpochTime one1000one = new UnixEpochTime(1000, 1);
+ assertEqualsAndHashCode(one1000one, one1000one);
+
+ UnixEpochTime one1000two = new UnixEpochTime(1000, 1);
+ assertEqualsAndHashCode(one1000one, one1000two);
+
+ UnixEpochTime two1000 = new UnixEpochTime(1000, 2);
+ assertNotEquals(one1000one, two1000);
+
+ UnixEpochTime one2000 = new UnixEpochTime(2000, 1);
+ assertNotEquals(one1000one, one2000);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testParceling() {
+ UnixEpochTime value = new UnixEpochTime(1000, 1);
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(value, 0);
+
+ parcel.setDataPosition(0);
+
+ UnixEpochTime stringValueCopy =
+ parcel.readParcelable(null /* classLoader */, UnixEpochTime.class);
+ assertEquals(value, stringValueCopy);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noElapsedRealtime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345");
+ UnixEpochTime expectedValue = new UnixEpochTime(54321L, 12345L);
+ UnixEpochTime actualValue = UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testAt() {
+ long timeMillis = 1000L;
+ int elapsedRealtimeMillis = 100;
+ UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis);
+ // Reference time is after the timestamp.
+ UnixEpochTime at125 = unixEpochTime.at(125);
+ assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis());
+ assertEquals(125, at125.getElapsedRealtimeMillis());
+
+ // Reference time is before the timestamp.
+ UnixEpochTime at75 = unixEpochTime.at(75);
+ assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis());
+ assertEquals(75, at75.getElapsedRealtimeMillis());
+ }
+
+ @Test
+ public void testElapsedRealtimeDifference() {
+ UnixEpochTime value1 = new UnixEpochTime(1000, 123L);
+ assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1));
+
+ UnixEpochTime value2 = new UnixEpochTime(1, 321L);
+ assertEquals(999, UnixEpochTime.elapsedRealtimeDifference(value1, value2));
+ assertEquals(-999, UnixEpochTime.elapsedRealtimeDifference(value2, value1));
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
index 46f335e..d9a4266 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 @@
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 @@
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 @@
}
/**
- * 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 @@
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability);
+ builder.setSetManualTimeCapability(suggestManualTimeZoneCapability);
return builder.build();
}
@@ -256,8 +256,8 @@
return "ConfigurationInternal{"
+ "mAutoDetectionSupported=" + mAutoDetectionSupported
+ ", mSystemClockUpdateThresholdMillis=" + mSystemClockUpdateThresholdMillis
- + ", mSystemClockConfidenceUpgradeThresholdMillis="
- + mSystemClockConfidenceUpgradeThresholdMillis
+ + ", mSystemClockConfidenceThresholdMillis="
+ + mSystemClockConfidenceThresholdMillis
+ ", mAutoSuggestionLowerBound=" + mAutoSuggestionLowerBound
+ "(" + mAutoSuggestionLowerBound.toEpochMilli() + ")"
+ ", mManualSuggestionLowerBound=" + mManualSuggestionLowerBound
@@ -274,7 +274,7 @@
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 @@
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 0ea5f7a..84013a7 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 @@
.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 5c47abf..4186330 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.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 @@
}
@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 d306d10..990c00f 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 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 @@
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 @@
}
}
+ 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 @@
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 @@
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 141cdcf..ac2a391 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.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 @@
/** 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 @@
* 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 fe2760e9..3235bf0 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.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 @@
}
@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 @@
long absTimeDifferenceMillis =
Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
int confidenceUpgradeThresholdMillis =
- mCurrentConfigurationInternal.getSystemClockConfidenceUpgradeThresholdMillis();
+ mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
boolean updateConfidenceRequired =
absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
if (updateConfidenceRequired) {
@@ -866,7 +938,7 @@
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 ef99d61..d413feb 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 @@
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
+ builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
return builder.build();
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 59db855..0e58d91 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.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 @@
}
@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 4d808ff..1b9f8e6 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_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 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 @@
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 @@
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 @@
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 @@
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 95ebd68..e4b2df1 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 @@
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 @@
*/
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 6f227ee..85986dd 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_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,6 +31,7 @@
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;
@@ -76,7 +78,7 @@
@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();
@@ -250,6 +252,39 @@
}
@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) {
@@ -301,7 +336,7 @@
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
+ ", timeZoneId=" + timeZoneId
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 208f99a..a24afe6 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 @@
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 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
- capabilities.getSuggestManualTimeCapability());
+ capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
@@ -131,7 +131,7 @@
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 @@
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 @@
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 @@
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 1c6add2..7b38fa0 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 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 @@
* in tests.
*/
class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
+ // State
+ private TimeState mTimeState;
// Call tracking.
private TelephonyTimeSuggestion mLastTelephonySuggestion;
@@ -38,9 +42,26 @@
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 @@
mLastNetworkSuggestion = null;
mLastGnssSuggestion = null;
mLastExternalSuggestion = null;
+ mLastConfirmedTime = null;
mDumpCalled = false;
}
@@ -104,6 +126,10 @@
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 67c8c4f..1218489 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 @@
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 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 @@
}
@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 060c31f..9ec70dc 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 @@
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 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 @@
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
- .setSystemClockConfidenceUpgradeThresholdMillis(
+ .setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script()
@@ -338,7 +341,7 @@
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 @@
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 @@
}
@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 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- public void verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
+ public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -1879,6 +2002,12 @@
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 @@
}
Script verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
- mFakeEnvironment.verifySystemClockConfidence(expectedConfidence);
+ mFakeEnvironment.verifySystemClockConfidenceLatest(expectedConfidence);
return this;
}
@@ -1931,6 +2060,11 @@
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 @@
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 767c466..810bd82 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 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -102,7 +102,7 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -143,7 +143,7 @@
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 @@
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 @@
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 @@
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 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -303,7 +303,7 @@
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 c9fc033..339e5b2 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 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 @@
@Override
public boolean suggestManualTimeZone(
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
+ mLastManualSuggestionUserId = userId;
mLastManualSuggestion = timeZoneSuggestion;
return true;
}
@@ -78,8 +100,10 @@
void resetCallTracking() {
mLastGeolocationSuggestion = null;
mLastManualSuggestion = null;
+ mLastManualSuggestionUserId = null;
mLastTelephonySuggestion = null;
mDumpCalled = false;
+ mLastConfirmedTimeZone = null;
}
void verifySuggestGeolocationTimeZoneCalled(
@@ -87,7 +111,9 @@
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 @@
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 6365b98..b8c3ea7 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 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 @@
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 @@
}
@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 81d3f30..77d8b8b 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_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.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 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;
@@ -183,7 +186,7 @@
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();
@@ -296,7 +299,7 @@
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();
@@ -348,7 +351,7 @@
@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();
@@ -414,7 +417,7 @@
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()
@@ -553,7 +556,7 @@
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(geoTzEnabledConfig)
.resetConfigurationTracking();
@@ -568,7 +571,7 @@
@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();
@@ -583,7 +586,7 @@
@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();
@@ -599,7 +602,7 @@
@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();
@@ -615,7 +618,7 @@
@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();
@@ -631,7 +634,7 @@
@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();
@@ -648,7 +651,7 @@
@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();
@@ -667,7 +670,7 @@
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();
@@ -693,7 +696,7 @@
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();
@@ -734,7 +737,7 @@
"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();
@@ -780,7 +783,7 @@
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();
@@ -914,7 +917,7 @@
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();
@@ -970,6 +973,78 @@
}
@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);
}
@@ -987,7 +1062,7 @@
String expectedDeviceTimeZoneId = "InitialZoneId";
Script script = new Script()
- .initializeTimeZoneSetting(expectedDeviceTimeZoneId)
+ .initializeTimeZoneSetting(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(expectedInternalConfig)
.resetConfigurationTracking();
@@ -1113,11 +1188,16 @@
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;
}
@@ -1126,8 +1206,9 @@
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
- void initializeTimeZoneSetting(String zoneId) {
+ void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
mTimeZoneId.init(zoneId);
+ mTimeZoneConfidence.init(timeZoneConfidence);
}
void incrementClock() {
@@ -1151,14 +1232,14 @@
@Override
public int getDeviceTimeZoneConfidence() {
- return mTimeZoneConfidence;
+ return mTimeZoneConfidence.getLatest();
}
@Override
public void setDeviceTimeZoneAndConfidence(
String zoneId, @TimeZoneConfidence int confidence, String logInfo) {
mTimeZoneId.set(zoneId);
- mTimeZoneConfidence = confidence;
+ mTimeZoneConfidence.set(confidence);
}
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1168,17 +1249,22 @@
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
@@ -1204,8 +1290,9 @@
*/
private class Script {
- Script initializeTimeZoneSetting(String zoneId) {
- mFakeEnvironment.initializeTimeZoneSetting(zoneId);
+ Script initializeTimeZoneSetting(
+ String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
+ mFakeEnvironment.initializeTimeZoneSetting(zoneId, timeZoneConfidence);
return this;
}
@@ -1304,25 +1391,20 @@
}
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;
}