summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Neil Fuller <nfuller@google.com> 2022-10-05 15:45:42 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-10-05 15:45:42 +0000
commit5a86476d87be54a5a122cadad4bb0e02380a2694 (patch)
tree6e418bc93a482f78fb3ac336a3b06d4a952d0b6a
parentb0a9e7883d3260a3357e91a99f0d675c36bc03b1 (diff)
parent2537e5d5b6d1008c3971a9eaa89200b962f69e31 (diff)
Merge changes from topic "initialization_apis"
* changes: Implement proposed device initialization APIs Improve debug / bug report logging for time zones
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java11
-rw-r--r--core/java/android/app/time/TimeCapabilities.java37
-rw-r--r--core/java/android/app/time/TimeCapabilitiesAndConfig.java4
-rw-r--r--core/java/android/app/time/TimeConfiguration.java6
-rw-r--r--core/java/android/app/time/TimeManager.java151
-rw-r--r--core/java/android/app/time/TimeState.aidl19
-rw-r--r--core/java/android/app/time/TimeState.java162
-rw-r--r--core/java/android/app/time/TimeZoneCapabilities.java54
-rw-r--r--core/java/android/app/time/TimeZoneState.aidl19
-rw-r--r--core/java/android/app/time/TimeZoneState.java150
-rw-r--r--core/java/android/app/time/UnixEpochTime.aidl19
-rw-r--r--core/java/android/app/time/UnixEpochTime.java190
-rw-r--r--core/java/android/app/timedetector/ITimeDetectorService.aidl21
-rw-r--r--core/java/android/app/timedetector/TimeDetector.java18
-rw-r--r--core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl13
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java18
-rw-r--r--core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java34
-rw-r--r--core/tests/coretests/src/android/app/time/TimeStateTest.java115
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java26
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneStateTest.java106
-rw-r--r--core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java130
-rw-r--r--services/core/java/com/android/server/AlarmManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/SystemTimeZone.java70
-rw-r--r--services/core/java/com/android/server/timedetector/ConfigurationInternal.java34
-rw-r--r--services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java2
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorService.java54
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java39
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java22
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java76
-rw-r--r--services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java2
-rw-r--r--services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java17
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java52
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java57
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java19
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java112
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java122
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java149
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java123
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java159
46 files changed, 2286 insertions, 224 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 232d3c9aaa53..901796be9064 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2153,7 +2153,7 @@ public class AlarmManagerService extends SystemService {
}
}
- void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence) {
+ void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence, String logInfo) {
if (TextUtils.isEmpty(tzId)) {
return;
}
@@ -2166,7 +2166,7 @@ public class AlarmManagerService extends SystemService {
// TimeZone.getTimeZone() can return a time zone with a different ID (e.g. it can return
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
- timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence);
+ timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
if (KERNEL_TIME_ZONE_SYNC_ENABLED) {
// Update the kernel timezone information
@@ -2706,8 +2706,9 @@ public class AlarmManagerService extends SystemService {
}
@Override
- public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) {
- setTimeZoneImpl(tzId, confidence);
+ public void setTimeZone(String tzId, @TimeZoneConfidence int confidence,
+ String logInfo) {
+ setTimeZoneImpl(tzId, confidence, logInfo);
}
@Override
@@ -3010,7 +3011,7 @@ public class AlarmManagerService extends SystemService {
// of confidence, but since the time zone ID should come either from apps working on
// behalf of the user or a developer, confidence is assumed "high".
final int timeZoneConfidence = TIME_ZONE_CONFIDENCE_HIGH;
- setTimeZoneImpl(tz, timeZoneConfidence);
+ setTimeZoneImpl(tz, timeZoneConfidence, "AlarmManager.setTimeZone() called");
} finally {
Binder.restoreCallingIdentity(oldId);
}
diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java
index 44bc1788fa6a..76bad58e924b 100644
--- a/core/java/android/app/time/TimeCapabilities.java
+++ b/core/java/android/app/time/TimeCapabilities.java
@@ -57,21 +57,21 @@ public final class TimeCapabilities implements Parcelable {
@NonNull
private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
- private final @CapabilityState int mSuggestManualTimeCapability;
+ private final @CapabilityState int mSetManualTimeCapability;
private TimeCapabilities(@NonNull Builder builder) {
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
this.mConfigureAutoDetectionEnabledCapability =
builder.mConfigureAutoDetectionEnabledCapability;
- this.mSuggestManualTimeCapability = builder.mSuggestManualTimeCapability;
+ this.mSetManualTimeCapability = builder.mSetManualTimeCapability;
}
@NonNull
- private static TimeCapabilities createFromParcel(Parcel in) {
+ private static TimeCapabilities createFromParcel(@NonNull Parcel in) {
UserHandle userHandle = UserHandle.readFromParcel(in);
return new TimeCapabilities.Builder(userHandle)
.setConfigureAutoDetectionEnabledCapability(in.readInt())
- .setSuggestManualTimeCapability(in.readInt())
+ .setSetManualTimeCapability(in.readInt())
.build();
}
@@ -79,7 +79,7 @@ public final class TimeCapabilities implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
- dest.writeInt(mSuggestManualTimeCapability);
+ dest.writeInt(mSetManualTimeCapability);
}
/**
@@ -94,11 +94,12 @@ public final class TimeCapabilities implements Parcelable {
/**
* Returns the capability state associated with the user's ability to manually set time on a
- * device.
+ * device. The setting can be updated via {@link
+ * TimeManager#updateTimeConfiguration(TimeConfiguration)}.
*/
@CapabilityState
- public int getSuggestManualTimeCapability() {
- return mSuggestManualTimeCapability;
+ public int getSetManualTimeCapability() {
+ return mSetManualTimeCapability;
}
/**
@@ -136,14 +137,14 @@ public final class TimeCapabilities implements Parcelable {
TimeCapabilities that = (TimeCapabilities) o;
return mConfigureAutoDetectionEnabledCapability
== that.mConfigureAutoDetectionEnabledCapability
- && mSuggestManualTimeCapability == that.mSuggestManualTimeCapability
+ && mSetManualTimeCapability == that.mSetManualTimeCapability
&& mUserHandle.equals(that.mUserHandle);
}
@Override
public int hashCode() {
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
- mSuggestManualTimeCapability);
+ mSetManualTimeCapability);
}
@Override
@@ -152,7 +153,7 @@ public final class TimeCapabilities implements Parcelable {
+ "mUserHandle=" + mUserHandle
+ ", mConfigureAutoDetectionEnabledCapability="
+ mConfigureAutoDetectionEnabledCapability
- + ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability
+ + ", mSetManualTimeCapability=" + mSetManualTimeCapability
+ '}';
}
@@ -165,7 +166,7 @@ public final class TimeCapabilities implements Parcelable {
@NonNull private final UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
- private @CapabilityState int mSuggestManualTimeCapability;
+ private @CapabilityState int mSetManualTimeCapability;
public Builder(@NonNull UserHandle userHandle) {
this.mUserHandle = Objects.requireNonNull(userHandle);
@@ -176,18 +177,18 @@ public final class TimeCapabilities implements Parcelable {
this.mUserHandle = timeCapabilities.mUserHandle;
this.mConfigureAutoDetectionEnabledCapability =
timeCapabilities.mConfigureAutoDetectionEnabledCapability;
- this.mSuggestManualTimeCapability = timeCapabilities.mSuggestManualTimeCapability;
+ this.mSetManualTimeCapability = timeCapabilities.mSetManualTimeCapability;
}
- /** Sets the state for automatic time detection config. */
+ /** Sets the value for the "configure automatic time detection" capability. */
public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
- /** Sets the state for manual time change. */
- public Builder setSuggestManualTimeCapability(@CapabilityState int value) {
- this.mSuggestManualTimeCapability = value;
+ /** Sets the value for the "set manual time" capability. */
+ public Builder setSetManualTimeCapability(@CapabilityState int value) {
+ this.mSetManualTimeCapability = value;
return this;
}
@@ -195,7 +196,7 @@ public final class TimeCapabilities implements Parcelable {
public TimeCapabilities build() {
verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
"configureAutoDetectionEnabledCapability");
- verifyCapabilitySet(mSuggestManualTimeCapability, "mSuggestManualTimeCapability");
+ verifyCapabilitySet(mSetManualTimeCapability, "mSetManualTimeCapability");
return new TimeCapabilities(this);
}
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index be4d01048cee..b6a081825757 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -71,8 +71,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {
/**
* Returns the user's time behaviour capabilities.
- *
- * @hide
*/
@NonNull
public TimeCapabilities getCapabilities() {
@@ -81,8 +79,6 @@ public final class TimeCapabilitiesAndConfig implements Parcelable {
/**
* Returns the user's time behaviour configuration.
- *
- * @hide
*/
@NonNull
public TimeConfiguration getConfiguration() {
diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java
index 11f6ed2a9f88..7d986983160e 100644
--- a/core/java/android/app/time/TimeConfiguration.java
+++ b/core/java/android/app/time/TimeConfiguration.java
@@ -55,10 +55,16 @@ public final class TimeConfiguration implements Parcelable {
}
};
+ /**
+ * All configuration properties
+ *
+ * @hide
+ */
@StringDef(SETTING_AUTO_DETECTION_ENABLED)
@Retention(RetentionPolicy.SOURCE)
@interface Setting {}
+ /** See {@link TimeConfiguration#isAutoDetectionEnabled()} for details. */
@Setting
private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index d6acb8cd1076..6a833fd772af 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -21,11 +21,14 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.timedetector.ITimeDetectorService;
+import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.TimestampedValue;
import android.util.ArrayMap;
import android.util.Log;
@@ -274,4 +277,152 @@ public final class TimeManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns a snapshot of the device's current system clock time state. See also {@link
+ * #confirmTime(UnixEpochTime)} for how this information can be used.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ @NonNull
+ public TimeState getTimeState() {
+ if (DEBUG) {
+ Log.d(TAG, "getTimeState called");
+ }
+ try {
+ return mITimeDetectorService.getTimeState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Confirms the device's current time during device setup, raising the system's confidence in
+ * the time if needed. Unlike {@link #setManualTime(UnixEpochTime)}, which can only be used when
+ * automatic time detection is currently disabled, this method can be used regardless of the
+ * automatic time detection setting, but only to confirm the current time (which may have been
+ * set via automatic means). Use {@link #getTimeState()} to obtain the time state to confirm.
+ *
+ * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
+ * confirmed is no longer the time the device is currently set to. Confirming a time
+ * in which the system already has high confidence will return {@code true}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
+ if (DEBUG) {
+ Log.d(TAG, "confirmTime called: " + unixEpochTime);
+ }
+ try {
+ return mITimeDetectorService.confirmTime(unixEpochTime);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempts to set the device's time, expected to be determined from the user's manually entered
+ * information.
+ *
+ * <p>Returns {@code false} if the time is invalid, or the device configuration / user
+ * capabilities prevents the time being accepted, e.g. if the device is currently set to
+ * "automatic time detection". This method returns {@code true} if the time was accepted even
+ * if it is the same as the current device time.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
+ if (DEBUG) {
+ Log.d(TAG, "setTime called: " + unixEpochTime);
+ }
+ try {
+ TimestampedValue<Long> manualTime = new TimestampedValue<>(
+ unixEpochTime.getElapsedRealtimeMillis(),
+ unixEpochTime.getUnixEpochTimeMillis());
+ ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(manualTime);
+ manualTimeSuggestion.addDebugInfo("TimeManager.setTime()");
+ manualTimeSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
+ manualTimeSuggestion.addDebugInfo("UserHandle: " + android.os.Process.myUserHandle());
+ manualTimeSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
+ return mITimeDetectorService.setManualTime(manualTimeSuggestion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the device's current time zone state. See also {@link
+ * #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
+ * be used.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ @NonNull
+ public TimeZoneState getTimeZoneState() {
+ if (DEBUG) {
+ Log.d(TAG, "getTimeZoneState called");
+ }
+ try {
+ return mITimeZoneDetectorService.getTimeZoneState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Confirms the device's current time zone ID, raising the system's confidence in the time zone
+ * if needed. Unlike {@link #setManualTimeZone(String)}, which can only be used when automatic
+ * time zone detection is currently disabled, this method can be used regardless of the
+ * automatic time zone detection setting, but only to confirm the current value (which may have
+ * been set via automatic means).
+ *
+ * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
+ * confirmed is no longer the time zone ID the device is currently set to. Confirming a time
+ * zone ID in which the system already has high confidence returns {@code true}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean confirmTimeZone(@NonNull String timeZoneId) {
+ if (DEBUG) {
+ Log.d(TAG, "confirmTimeZone called: " + timeZoneId);
+ }
+ try {
+ return mITimeZoneDetectorService.confirmTimeZone(timeZoneId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempts to set the device's time zone, expected to be determined from a user's manually
+ * entered information.
+ *
+ * <p>Returns {@code false} if the time zone is invalid, or the device configuration / user
+ * capabilities prevents the time zone being accepted, e.g. if the device is currently set to
+ * "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
+ * time zone that is accepted and matches the current device time zone returns {@code true}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean setManualTimeZone(@NonNull String timeZoneId) {
+ if (DEBUG) {
+ Log.d(TAG, "setManualTimeZone called: " + timeZoneId);
+ }
+ try {
+ ManualTimeZoneSuggestion manualTimeZoneSuggestion =
+ new ManualTimeZoneSuggestion(timeZoneId);
+ manualTimeZoneSuggestion.addDebugInfo("TimeManager.setManualTimeZone()");
+ manualTimeZoneSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
+ manualTimeZoneSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
+ return mITimeZoneDetectorService.setManualTimeZone(manualTimeZoneSuggestion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/time/TimeState.aidl b/core/java/android/app/time/TimeState.aidl
new file mode 100644
index 000000000000..70c31d85bd55
--- /dev/null
+++ b/core/java/android/app/time/TimeState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable TimeState;
diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java
new file mode 100644
index 000000000000..15411e547814
--- /dev/null
+++ b/core/java/android/app/time/TimeState.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A snapshot of the system time state.
+ *
+ * <p>{@code mUnixEpochTime} contains a snapshot of the system clock time and elapsed realtime clock
+ * time.
+ *
+ * <p>{@code mUserShouldConfirmTime} is {@code true} if the system has low confidence in the system
+ * clock time.
+ *
+ * @hide
+ */
+public final class TimeState implements Parcelable {
+
+ public static final @NonNull Creator<TimeState> CREATOR = new Creator<>() {
+ public TimeState createFromParcel(Parcel in) {
+ return TimeState.createFromParcel(in);
+ }
+
+ public TimeState[] newArray(int size) {
+ return new TimeState[size];
+ }
+ };
+
+ @NonNull private final UnixEpochTime mUnixEpochTime;
+ private final boolean mUserShouldConfirmTime;
+
+ /** @hide */
+ public TimeState(@NonNull UnixEpochTime unixEpochTime, boolean userShouldConfirmTime) {
+ mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
+ mUserShouldConfirmTime = userShouldConfirmTime;
+ }
+
+ private static TimeState createFromParcel(Parcel in) {
+ UnixEpochTime unixEpochTime = in.readParcelable(null, UnixEpochTime.class);
+ boolean userShouldConfirmId = in.readBoolean();
+ return new TimeState(unixEpochTime, userShouldConfirmId);
+ }
+
+ /** @hide */
+ @Nullable
+ public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) {
+ Long elapsedRealtimeMillis = null;
+ Long unixEpochTimeMillis = null;
+ Boolean userShouldConfirmTime = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--elapsed_realtime": {
+ elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ case "--unix_epoch_time": {
+ unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ case "--user_should_confirm_time": {
+ userShouldConfirmTime = Boolean.parseBoolean(cmd.getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (elapsedRealtimeMillis == null) {
+ throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
+ }
+ if (unixEpochTimeMillis == null) {
+ throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+ }
+ if (userShouldConfirmTime == null) {
+ throw new IllegalArgumentException("No userShouldConfirmTime specified.");
+ }
+
+ UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+ return new TimeState(unixEpochTime, userShouldConfirmTime);
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(@NonNull PrintWriter pw) {
+ pw.println("TimeState options:");
+ pw.println(" --elapsed_realtime <elapsed realtime millis>");
+ pw.println(" --unix_epoch_time <Unix epoch time millis>");
+ pw.println(" --user_should_confirm_time {true|false}");
+ pw.println();
+ pw.println("See " + TimeState.class.getName() + " for more information");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mUnixEpochTime, 0);
+ dest.writeBoolean(mUserShouldConfirmTime);
+ }
+
+ @NonNull
+ public UnixEpochTime getUnixEpochTime() {
+ return mUnixEpochTime;
+ }
+
+ public boolean getUserShouldConfirmTime() {
+ return mUserShouldConfirmTime;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeState that = (TimeState) o;
+ return Objects.equals(mUnixEpochTime, that.mUnixEpochTime)
+ && mUserShouldConfirmTime == that.mUserShouldConfirmTime;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUnixEpochTime, mUserShouldConfirmTime);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeState{"
+ + "mUnixEpochTime=" + mUnixEpochTime
+ + ", mUserShouldConfirmTime=" + mUserShouldConfirmTime
+ + '}';
+ }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 895a8e491f8d..5d4629f81493 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -22,8 +22,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.time.Capabilities.CapabilityState;
-import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneDetector;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -61,7 +59,7 @@ public final class TimeZoneCapabilities implements Parcelable {
@NonNull private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
- private final @CapabilityState int mSuggestManualTimeZoneCapability;
+ private final @CapabilityState int mSetManualTimeZoneCapability;
private TimeZoneCapabilities(@NonNull Builder builder) {
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
@@ -69,16 +67,16 @@ public final class TimeZoneCapabilities implements Parcelable {
builder.mConfigureAutoDetectionEnabledCapability;
this.mConfigureGeoDetectionEnabledCapability =
builder.mConfigureGeoDetectionEnabledCapability;
- this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
+ this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
}
@NonNull
- private static TimeZoneCapabilities createFromParcel(Parcel in) {
+ private static TimeZoneCapabilities createFromParcel(@NonNull Parcel in) {
UserHandle userHandle = UserHandle.readFromParcel(in);
return new TimeZoneCapabilities.Builder(userHandle)
.setConfigureAutoDetectionEnabledCapability(in.readInt())
.setConfigureGeoDetectionEnabledCapability(in.readInt())
- .setSuggestManualTimeZoneCapability(in.readInt())
+ .setSetManualTimeZoneCapability(in.readInt())
.build();
}
@@ -87,7 +85,7 @@ public final class TimeZoneCapabilities implements Parcelable {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
dest.writeInt(mConfigureGeoDetectionEnabledCapability);
- dest.writeInt(mSuggestManualTimeZoneCapability);
+ dest.writeInt(mSetManualTimeZoneCapability);
}
/**
@@ -112,17 +110,17 @@ public final class TimeZoneCapabilities implements Parcelable {
/**
* Returns the capability state associated with the user's ability to manually set the time zone
- * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+ * on a device.
*
- * <p>The suggestion will be ignored in all cases unless the value is {@link
+ * <p>The time zone will be ignored in all cases unless the value is {@link
* Capabilities#CAPABILITY_POSSESSED}. See also
* {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
*
* @hide
*/
@CapabilityState
- public int getSuggestManualTimeZoneCapability() {
- return mSuggestManualTimeZoneCapability;
+ public int getSetManualTimeZoneCapability() {
+ return mSetManualTimeZoneCapability;
}
/**
@@ -174,13 +172,13 @@ public final class TimeZoneCapabilities implements Parcelable {
== that.mConfigureAutoDetectionEnabledCapability
&& mConfigureGeoDetectionEnabledCapability
== that.mConfigureGeoDetectionEnabledCapability
- && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
+ && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
}
@Override
public int hashCode() {
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
- mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
+ mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability);
}
@Override
@@ -191,17 +189,21 @@ public final class TimeZoneCapabilities implements Parcelable {
+ mConfigureAutoDetectionEnabledCapability
+ ", mConfigureGeoDetectionEnabledCapability="
+ mConfigureGeoDetectionEnabledCapability
- + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+ + ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
+ '}';
}
- /** @hide */
+ /**
+ * A builder of {@link TimeZoneCapabilities} objects.
+ *
+ * @hide
+ */
public static class Builder {
@NonNull private UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
- private @CapabilityState int mSuggestManualTimeZoneCapability;
+ private @CapabilityState int mSetManualTimeZoneCapability;
public Builder(@NonNull UserHandle userHandle) {
mUserHandle = Objects.requireNonNull(userHandle);
@@ -214,25 +216,27 @@ public final class TimeZoneCapabilities implements Parcelable {
capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
mConfigureGeoDetectionEnabledCapability =
capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
- mSuggestManualTimeZoneCapability =
- capabilitiesToCopy.mSuggestManualTimeZoneCapability;
+ mSetManualTimeZoneCapability =
+ capabilitiesToCopy.mSetManualTimeZoneCapability;
}
- /** Sets the state for the automatic time zone detection enabled config. */
+ /** Sets the value for the "configure automatic time zone detection enabled" capability. */
public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
- /** Sets the state for the geolocation time zone detection enabled config. */
+ /**
+ * Sets the value for the "configure geolocation time zone detection enabled" capability.
+ */
public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureGeoDetectionEnabledCapability = value;
return this;
}
- /** Sets the state for the suggestManualTimeZone action. */
- public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
- this.mSuggestManualTimeZoneCapability = value;
+ /** Sets the value for the "set manual time zone" capability. */
+ public Builder setSetManualTimeZoneCapability(@CapabilityState int value) {
+ this.mSetManualTimeZoneCapability = value;
return this;
}
@@ -243,8 +247,8 @@ public final class TimeZoneCapabilities implements Parcelable {
"configureAutoDetectionEnabledCapability");
verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
"configureGeoDetectionEnabledCapability");
- verifyCapabilitySet(mSuggestManualTimeZoneCapability,
- "suggestManualTimeZoneCapability");
+ verifyCapabilitySet(mSetManualTimeZoneCapability,
+ "mSetManualTimeZoneCapability");
return new TimeZoneCapabilities(this);
}
diff --git a/core/java/android/app/time/TimeZoneState.aidl b/core/java/android/app/time/TimeZoneState.aidl
new file mode 100644
index 000000000000..fc1962f3d5b9
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable TimeZoneState;
diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java
new file mode 100644
index 000000000000..efdff81b079a
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneState.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A snapshot of the system's time zone state.
+ *
+ * <p>{@code id} contains the system's time zone ID setting, e.g. "America/Los_Angeles". This
+ * will usually agree with {@code TimeZone.getDefault().getID()} but it can be empty in rare cases.
+ *
+ * <p>{@code userShouldConfirmId} is {@code true} if the system has low confidence in the current
+ * time zone.
+ *
+ * @hide
+ */
+public final class TimeZoneState implements Parcelable {
+
+ public static final @NonNull Creator<TimeZoneState> CREATOR = new Creator<>() {
+ public TimeZoneState createFromParcel(Parcel in) {
+ return TimeZoneState.createFromParcel(in);
+ }
+
+ public TimeZoneState[] newArray(int size) {
+ return new TimeZoneState[size];
+ }
+ };
+
+ @NonNull private final String mId;
+ private final boolean mUserShouldConfirmId;
+
+ /** @hide */
+ public TimeZoneState(@NonNull String id, boolean userShouldConfirmId) {
+ mId = Objects.requireNonNull(id);
+ mUserShouldConfirmId = userShouldConfirmId;
+ }
+
+ private static TimeZoneState createFromParcel(Parcel in) {
+ String zoneId = in.readString();
+ boolean userShouldConfirmId = in.readBoolean();
+ return new TimeZoneState(zoneId, userShouldConfirmId);
+ }
+
+ /** @hide */
+ @Nullable
+ public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) {
+ String zoneIdString = null;
+ Boolean userShouldConfirmId = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--zone_id": {
+ zoneIdString = cmd.getNextArgRequired();
+ break;
+ }
+ case "--user_should_confirm_id": {
+ userShouldConfirmId = Boolean.parseBoolean(cmd.getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+ if (zoneIdString == null) {
+ throw new IllegalArgumentException("No zoneId specified.");
+ }
+ if (userShouldConfirmId == null) {
+ throw new IllegalArgumentException("No userShouldConfirmId specified.");
+ }
+ return new TimeZoneState(zoneIdString, userShouldConfirmId);
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(@NonNull PrintWriter pw) {
+ pw.println("TimeZoneState options:");
+ pw.println(" --zone_id {<Olson ID>}");
+ pw.println(" --user_should_confirm_id {true|false}");
+ pw.println();
+ pw.println("See " + TimeZoneState.class.getName() + " for more information");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeBoolean(mUserShouldConfirmId);
+ }
+
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ public boolean getUserShouldConfirmId() {
+ return mUserShouldConfirmId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneState that = (TimeZoneState) o;
+ return Objects.equals(mId, that.mId)
+ && mUserShouldConfirmId == that.mUserShouldConfirmId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mUserShouldConfirmId);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneState{"
+ + "mZoneId=" + mId
+ + ", mUserShouldConfirmId=" + mUserShouldConfirmId
+ + '}';
+ }
+}
diff --git a/core/java/android/app/time/UnixEpochTime.aidl b/core/java/android/app/time/UnixEpochTime.aidl
new file mode 100644
index 000000000000..3392e22580f1
--- /dev/null
+++ b/core/java/android/app/time/UnixEpochTime.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable UnixEpochTime;
diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java
new file mode 100644
index 000000000000..2683547e9209
--- /dev/null
+++ b/core/java/android/app/time/UnixEpochTime.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.TimestampedValue;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A Unix epoch time value with an associated reading from the elapsed realtime clock.
+ * When representing a device's system clock time, the Unix epoch time can be obtained using {@link
+ * System#currentTimeMillis()}. The Unix epoch time might also come from an external source
+ * depending on usage.
+ *
+ * <p>The elapsed realtime clock can be obtained using methods like {@link
+ * SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}.
+ *
+ * @hide
+ */
+public final class UnixEpochTime implements Parcelable {
+ @ElapsedRealtimeLong private final long mElapsedRealtimeMillis;
+ private final long mUnixEpochTimeMillis;
+
+ public UnixEpochTime(@ElapsedRealtimeLong long elapsedRealtimeMillis,
+ long unixEpochTimeMillis) {
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ mUnixEpochTimeMillis = unixEpochTimeMillis;
+ }
+
+ /** @hide */
+ @NonNull
+ public static UnixEpochTime parseCommandLineArgs(ShellCommand cmd) {
+ Long elapsedRealtimeMillis = null;
+ Long unixEpochTimeMillis = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--elapsed_realtime": {
+ elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ case "--unix_epoch_time": {
+ unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (elapsedRealtimeMillis == null) {
+ throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
+ }
+ if (unixEpochTimeMillis == null) {
+ throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+ }
+ return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ pw.println("UnixEpochTime options:\n");
+ pw.println(" --elapsed_realtime <elapsed realtime millis>");
+ pw.println(" --unix_epoch_time <Unix epoch time millis>");
+ pw.println();
+ pw.println("See " + UnixEpochTime.class.getName() + " for more information");
+ }
+
+ /** Returns the elapsed realtime clock value. See {@link UnixEpochTime} for more information. */
+ public @ElapsedRealtimeLong long getElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ /** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */
+ @Nullable
+ public long getUnixEpochTimeMillis() {
+ return mUnixEpochTimeMillis;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ UnixEpochTime that = (UnixEpochTime) o;
+ return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
+ && Objects.equals(mUnixEpochTimeMillis, that.mUnixEpochTimeMillis);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mElapsedRealtimeMillis, mUnixEpochTimeMillis);
+ }
+
+ @Override
+ public String toString() {
+ return "UnixEpochTime{"
+ + "mElapsedRealtimeTimeMillis=" + mElapsedRealtimeMillis
+ + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+ + '}';
+ }
+
+ public static final @NonNull Creator<UnixEpochTime> CREATOR =
+ new ClassLoaderCreator<UnixEpochTime>() {
+
+ @Override
+ public UnixEpochTime createFromParcel(@NonNull Parcel source) {
+ return createFromParcel(source, null);
+ }
+
+ @Override
+ public UnixEpochTime createFromParcel(
+ @NonNull Parcel source, @Nullable ClassLoader classLoader) {
+ long elapsedRealtimeMillis = source.readLong();
+ long unixEpochTimeMillis = source.readLong();
+ return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+ }
+
+ @Override
+ public UnixEpochTime[] newArray(int size) {
+ return new UnixEpochTime[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mElapsedRealtimeMillis);
+ dest.writeLong(mUnixEpochTimeMillis);
+ }
+
+ /**
+ * Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
+ * Unix epoch time by the difference between the elapsed realtime value supplied and the one
+ * associated with this instance.
+ *
+ * @hide
+ */
+ public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) {
+ long adjustedUnixEpochTimeMillis =
+ (elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis;
+ return new UnixEpochTime(elapsedRealtimeTimeMillis, adjustedUnixEpochTimeMillis);
+ }
+
+ /**
+ * Returns the difference in milliseconds between two instance's elapsed realtimes.
+ *
+ * @hide
+ */
+ public static long elapsedRealtimeDifference(
+ @NonNull UnixEpochTime one, @NonNull UnixEpochTime two) {
+ return one.mElapsedRealtimeMillis - two.mElapsedRealtimeMillis;
+ }
+
+ // TODO(b/246256335) Switch to using UnixEpochTime where possible and remove this method.
+ /** @hide */
+ public TimestampedValue<Long> toTimestampedValue() {
+ return new TimestampedValue<>(mElapsedRealtimeMillis, mUnixEpochTimeMillis);
+ }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 0eb2b5470f58..a0c898ed2904 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -20,20 +20,23 @@ import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
/**
- * System private API to communicate with time detector service.
+ * Binder APIs to communicate with the time detector service.
*
- * <p>Used by parts of the Android system with signals associated with the device's time to provide
- * information to the Time Detector Service.
- *
- * <p>Use the {@link android.app.timedetector.TimeDetector} class rather than going through
- * this Binder interface directly. See {@link android.app.timedetector.TimeDetectorService} for
- * more complete documentation.
+ * <p>Used to provide information to the Time Detector Service from other parts of the Android
+ * system that have access to time-related signals, e.g. telephony. Over time, System APIs have
+ * been added to support unbundled parts of the platform, e.g. SetUp Wizard.
*
+ * <p>Use the {@link android.app.timedetector.TimeDetector} (internal API) and
+ * {@link android.app.time.TimeManager} (system API) classes rather than going through this Binder
+ * interface directly. See {@link android.app.timedetector.TimeDetectorService} for more complete
+ * documentation.
*
* {@hide}
*/
@@ -44,6 +47,10 @@ interface ITimeDetectorService {
boolean updateConfiguration(in TimeConfiguration timeConfiguration);
+ TimeState getTimeState();
+ boolean confirmTime(in UnixEpochTime time);
+ boolean setManualTime(in ManualTimeSuggestion timeZoneSuggestion);
+
void suggestExternalTime(in ExternalTimeSuggestion timeSuggestion);
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index db1614b32b59..9d996adc051a 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -85,6 +85,24 @@ public interface TimeDetector {
String SHELL_COMMAND_SUGGEST_EXTERNAL_TIME = "suggest_external_time";
/**
+ * A shell command that retrieves the current system clock time state.
+ * @hide
+ */
+ String SHELL_COMMAND_GET_TIME_STATE = "get_time_state";
+
+ /**
+ * A shell command that sets the current time state for testing.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_TIME_STATE = "set_time_state_for_tests";
+
+ /**
+ * A shell command that sets the confidence in the current time state for testing.
+ * @hide
+ */
+ String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
+
+ /**
* A shared utility method to create a {@link ManualTimeSuggestion}.
*
* @hide
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index af0389a14c4b..47d8e77ae764 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -19,16 +19,19 @@ package android.app.timezonedetector;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
/**
- * System private API to communicate with time zone detector service.
+ * Binder APIs to communicate with time zone detector service.
*
* <p>Used to provide information to the Time Zone Detector Service from other parts of the Android
- * system that have access to time zone-related signals, e.g. telephony.
+ * system that have access to time zone-related signals, e.g. telephony. Over time, System APIs have
+ * been added to support unbundled parts of the platform, e.g. SetUp Wizard.
*
- * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
+ * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} (internal API) and
+ * {@link android.app.time.TimeManager} (system API) classes rather than going through
* this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
* for more complete documentation.
*
@@ -41,6 +44,10 @@ interface ITimeZoneDetectorService {
boolean updateConfiguration(in TimeZoneConfiguration configuration);
+ TimeZoneState getTimeZoneState();
+ boolean confirmTimeZone(in String timeZoneId);
+ boolean setManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
+
boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index bae1c1c7312d..0e9e28be8818 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -108,6 +108,24 @@ public interface TimeZoneDetector {
String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
/**
+ * A shell command that retrieves the current time zone setting state.
+ * @hide
+ */
+ String SHELL_COMMAND_GET_TIME_ZONE_STATE = "get_time_zone_state";
+
+ /**
+ * A shell command that sets the current time zone state for testing.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_TIME_ZONE_STATE = "set_time_zone_state_for_tests";
+
+ /**
+ * A shell command that sets the confidence in the current time zone state for testing.
+ * @hide
+ */
+ String SHELL_COMMAND_CONFIRM_TIME_ZONE = "confirm_time_zone";
+
+ /**
* A shell command that dumps a {@link
* com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
* debugging.
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index 9d7dde2141bb..c9b96c6071ee 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -48,10 +48,10 @@ public class TimeCapabilitiesTest {
public void testEquals() {
TimeCapabilities.Builder builder1 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED);
TimeCapabilities.Builder builder2 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
@@ -72,14 +72,14 @@ public class TimeCapabilitiesTest {
assertEquals(one, two);
}
- builder2.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
+ builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
assertNotEquals(one, two);
}
- builder1.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
+ builder1.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
@@ -91,12 +91,12 @@ public class TimeCapabilitiesTest {
public void userHandle_notIgnoredInEquals() {
TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1))
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2))
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities);
@@ -106,12 +106,12 @@ public class TimeCapabilitiesTest {
public void testBuilder() {
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
+ .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
.build();
assertThat(capabilities.getConfigureAutoDetectionEnabledCapability())
.isEqualTo(CAPABILITY_NOT_APPLICABLE);
- assertThat(capabilities.getSuggestManualTimeCapability())
+ assertThat(capabilities.getSetManualTimeCapability())
.isEqualTo(CAPABILITY_NOT_SUPPORTED);
try {
@@ -133,7 +133,7 @@ public class TimeCapabilitiesTest {
try {
new TimeCapabilities.Builder(TEST_USER_HANDLE)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
+ .setSetManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
.build();
fail("Should throw IllegalStateException");
} catch (IllegalStateException ignored) {
@@ -145,11 +145,11 @@ public class TimeCapabilitiesTest {
public void testParcelable() {
TimeCapabilities.Builder builder = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
+ .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
assertRoundTripParcelable(builder.build());
- builder.setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+ builder.setSetManualTimeCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED);
@@ -164,7 +164,7 @@ public class TimeCapabilitiesTest {
.build();
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -185,7 +185,7 @@ public class TimeCapabilitiesTest {
.build();
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -199,7 +199,7 @@ public class TimeCapabilitiesTest {
public void copyBuilder_copiesAllFields() {
TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
{
@@ -210,7 +210,7 @@ public class TimeCapabilitiesTest {
TimeCapabilities expectedCapabilities =
new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -219,13 +219,13 @@ public class TimeCapabilitiesTest {
{
TimeCapabilities updatedCapabilities =
new TimeCapabilities.Builder(capabilities)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
TimeCapabilities expectedCapabilities =
new TimeCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeCapability(CAPABILITY_POSSESSED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
new file mode 100644
index 000000000000..a03229060dfb
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link TimeState}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeStateTest {
+
+ @Test
+ public void testEqualsAndHashcode() {
+ UnixEpochTime time1 = new UnixEpochTime(1, 1);
+ TimeState time1False_1 = new TimeState(time1, false);
+ assertEqualsAndHashCode(time1False_1, time1False_1);
+
+ TimeState time1False_2 = new TimeState(time1, false);
+ assertEqualsAndHashCode(time1False_1, time1False_2);
+
+ TimeState time1True = new TimeState(time1, true);
+ assertNotEquals(time1False_1, time1True);
+
+ UnixEpochTime time2 = new UnixEpochTime(2, 2);
+ TimeState time2False = new TimeState(time2, false);
+ assertNotEquals(time1False_1, time2False);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testParceling() {
+ UnixEpochTime time = new UnixEpochTime(1, 2);
+ TimeState value = new TimeState(time, true);
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(value, 0);
+
+ parcel.setDataPosition(0);
+
+ TimeState stringValueCopy =
+ parcel.readParcelable(null /* classLoader */, TimeState.class);
+ assertEquals(value, stringValueCopy);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noElapsedRealtime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345 --user_should_confirm_time true");
+ TimeState.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --user_should_confirm_time true");
+ TimeState.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUserShouldConfirmTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345 --elapsed_realtime 54321");
+ TimeState.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true");
+ TimeState expectedValue = new TimeState(new UnixEpochTime(54321L, 12345L), true);
+ TimeState actualValue = TimeState.parseCommandLineArgs(testShellCommand);
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true"
+ + " --bad_arg 0");
+ TimeState.parseCommandLineArgs(testShellCommand);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 008272830335..3f7da8a6fbd0 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -45,11 +45,11 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
@@ -84,14 +84,14 @@ public class TimeZoneCapabilitiesTest {
assertEquals(one, two);
}
- builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ builder2.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
assertNotEquals(one, two);
}
- builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ builder1.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
@@ -104,7 +104,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -113,7 +113,7 @@ public class TimeZoneCapabilitiesTest {
builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
- builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
}
@@ -127,7 +127,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -150,7 +150,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -165,7 +165,7 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
{
@@ -177,7 +177,7 @@ public class TimeZoneCapabilitiesTest {
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -193,7 +193,7 @@ public class TimeZoneCapabilitiesTest {
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -202,14 +202,14 @@ public class TimeZoneCapabilitiesTest {
{
TimeZoneCapabilities updatedCapabilities =
new TimeZoneCapabilities.Builder(capabilities)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
TimeZoneCapabilities expectedCapabilities =
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
new file mode 100644
index 000000000000..9786bb044cb2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link TimeZoneState}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeZoneStateTest {
+
+ @Test
+ public void testEqualsAndHashcode() {
+ String zone1 = "Europe/London";
+ TimeZoneState zone1False_1 = new TimeZoneState(zone1, false);
+ assertEqualsAndHashCode(zone1False_1, zone1False_1);
+
+ TimeZoneState zone1False_2 = new TimeZoneState(zone1, false);
+ assertEqualsAndHashCode(zone1False_1, zone1False_2);
+
+ TimeZoneState zone1True = new TimeZoneState(zone1, true);
+ assertNotEquals(zone1False_1, zone1True);
+
+ String zone2 = "Europe/Parise";
+ TimeZoneState zone2False = new TimeZoneState(zone2, false);
+ assertNotEquals(zone1False_1, zone2False);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testParceling() {
+ TimeZoneState value = new TimeZoneState("Europe/London", true);
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(value, 0);
+
+ parcel.setDataPosition(0);
+
+ TimeZoneState stringValueCopy =
+ parcel.readParcelable(null /* classLoader */, TimeZoneState.class);
+ assertEquals(value, stringValueCopy);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noZoneId() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--user_should_confirm_id true");
+ TimeZoneState.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUserShouldConfirmId() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--zone_id Europe/London");
+ TimeZoneState.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--zone_id Europe/London --user_should_confirm_id true");
+ TimeZoneState expectedValue = new TimeZoneState("Europe/London", true);
+ TimeZoneState actualValue = TimeZoneState.parseCommandLineArgs(testShellCommand);
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--zone_id Europe/London --user_should_confirm_id true --bad_arg 0");
+ TimeZoneState.parseCommandLineArgs(testShellCommand);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
new file mode 100644
index 000000000000..cd753489b50e
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link UnixEpochTime}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class UnixEpochTimeTest {
+
+ @Test
+ public void testEqualsAndHashcode() {
+ UnixEpochTime one1000one = new UnixEpochTime(1000, 1);
+ assertEqualsAndHashCode(one1000one, one1000one);
+
+ UnixEpochTime one1000two = new UnixEpochTime(1000, 1);
+ assertEqualsAndHashCode(one1000one, one1000two);
+
+ UnixEpochTime two1000 = new UnixEpochTime(1000, 2);
+ assertNotEquals(one1000one, two1000);
+
+ UnixEpochTime one2000 = new UnixEpochTime(2000, 1);
+ assertNotEquals(one1000one, one2000);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testParceling() {
+ UnixEpochTime value = new UnixEpochTime(1000, 1);
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(value, 0);
+
+ parcel.setDataPosition(0);
+
+ UnixEpochTime stringValueCopy =
+ parcel.readParcelable(null /* classLoader */, UnixEpochTime.class);
+ assertEquals(value, stringValueCopy);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noElapsedRealtime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345");
+ UnixEpochTime expectedValue = new UnixEpochTime(54321L, 12345L);
+ UnixEpochTime actualValue = UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
+ UnixEpochTime.parseCommandLineArgs(testShellCommand);
+ }
+
+ @Test
+ public void testAt() {
+ long timeMillis = 1000L;
+ int elapsedRealtimeMillis = 100;
+ UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis);
+ // Reference time is after the timestamp.
+ UnixEpochTime at125 = unixEpochTime.at(125);
+ assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis());
+ assertEquals(125, at125.getElapsedRealtimeMillis());
+
+ // Reference time is before the timestamp.
+ UnixEpochTime at75 = unixEpochTime.at(75);
+ assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis());
+ assertEquals(75, at75.getElapsedRealtimeMillis());
+ }
+
+ @Test
+ public void testElapsedRealtimeDifference() {
+ UnixEpochTime value1 = new UnixEpochTime(1000, 123L);
+ assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1));
+
+ UnixEpochTime value2 = new UnixEpochTime(1, 321L);
+ assertEquals(999, UnixEpochTime.elapsedRealtimeDifference(value1, value2));
+ assertEquals(-999, UnixEpochTime.elapsedRealtimeDifference(value2, value1));
+ }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index 67aa2b9a89bb..b6a8227c2461 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -59,8 +59,9 @@ public interface AlarmManagerInternal {
* @param tzId the time zone ID
* @param confidence the confidence that {@code tzId} is correct, see {@link TimeZoneConfidence}
* for details
+ * @param logInfo the reason the time zone is being changed, for bug report logging
*/
- void setTimeZone(String tzId, @TimeZoneConfidence int confidence);
+ void setTimeZone(String tzId, @TimeZoneConfidence int confidence, String logInfo);
/**
* Sets the device's current time and time confidence.
diff --git a/services/core/java/com/android/server/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java
index 7ce0831480dc..dd07081bda12 100644
--- a/services/core/java/com/android/server/SystemTimeZone.java
+++ b/services/core/java/com/android/server/SystemTimeZone.java
@@ -19,12 +19,15 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.LocalLog;
import android.util.Slog;
import com.android.i18n.timezone.ZoneInfoDb;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -70,6 +73,14 @@ public final class SystemTimeZone {
*/
public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_HIGH = 100;
+ /**
+ * An in-memory log that records the debug info related to the device's time zone setting.
+ * This is logged in bug reports to assist with debugging time zone detection issues.
+ */
+ @NonNull
+ private static final LocalLog sTimeZoneDebugLog =
+ new LocalLog(30, false /* useLocalTimestamps */);
+
private SystemTimeZone() {}
/**
@@ -78,27 +89,44 @@ public final class SystemTimeZone {
public static void initializeTimeZoneSettingsIfRequired() {
String timezoneProperty = SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY);
if (!isValidTimeZoneId(timezoneProperty)) {
- Slog.w(TAG, TIME_ZONE_SYSTEM_PROPERTY + " is not valid (" + timezoneProperty
- + "); setting to " + DEFAULT_TIME_ZONE_ID);
- setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW);
+ String logInfo = "initializeTimeZoneSettingsIfRequired():" + TIME_ZONE_SYSTEM_PROPERTY
+ + " is not valid (" + timezoneProperty + "); setting to "
+ + DEFAULT_TIME_ZONE_ID;
+ Slog.w(TAG, logInfo);
+ setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW, logInfo);
}
}
/**
- * Updates the device's time zone system property and associated metadata. Returns {@code true}
- * if the device's time zone changed, {@code false} if the ID is invalid or the device is
- * already set to the supplied ID.
+ * Adds an entry to the system time zone debug log that is included in bug reports. This method
+ * is intended to be used to record event that may lead to a time zone change, e.g. config or
+ * mode changes.
+ */
+ public static void addDebugLogEntry(@NonNull String logMsg) {
+ sTimeZoneDebugLog.log(logMsg);
+ }
+
+ /**
+ * Updates the device's time zone system property, associated metadata and adds an entry to the
+ * debug log. Returns {@code true} if the device's time zone changed, {@code false} if the ID is
+ * invalid or the device is already set to the supplied ID.
*
* <p>This method ensures the confidence metadata is set to the supplied value if the supplied
* time zone ID is considered valid.
*
* <p>This method is intended only for use by the AlarmManager. When changing the device's time
* zone other system service components must use {@link
- * com.android.server.AlarmManagerInternal#setTimeZone(String, int)} to ensure that important
+ * AlarmManagerInternal#setTimeZone(String, int, String)} to ensure that important
* system-wide side effects occur.
*/
- public static boolean setTimeZoneId(String timeZoneId, @TimeZoneConfidence int confidence) {
+ public static boolean setTimeZoneId(
+ @NonNull String timeZoneId, @TimeZoneConfidence int confidence,
+ @NonNull String logInfo) {
if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) {
+ addDebugLogEntry("setTimeZoneId: Invalid time zone ID."
+ + " timeZoneId=" + timeZoneId
+ + ", confidence=" + confidence
+ + ", logInfo=" + logInfo);
return false;
}
@@ -112,7 +140,14 @@ public final class SystemTimeZone {
}
timeZoneChanged = true;
}
- setTimeZoneConfidence(confidence);
+ boolean timeZoneConfidenceChanged = setTimeZoneConfidence(confidence);
+ if (timeZoneChanged || timeZoneConfidenceChanged) {
+ String logMsg = "Time zone or confidence set: "
+ + " (new) timeZoneId=" + timeZoneId
+ + ", (new) confidence=" + confidence
+ + ", logInfo=" + logInfo;
+ addDebugLogEntry(logMsg);
+ }
}
return timeZoneChanged;
@@ -121,16 +156,18 @@ public final class SystemTimeZone {
/**
* Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details.
*/
- private static void setTimeZoneConfidence(@TimeZoneConfidence int confidence) {
+ private static boolean setTimeZoneConfidence(@TimeZoneConfidence int newConfidence) {
int currentConfidence = getTimeZoneConfidence();
- if (currentConfidence != confidence) {
+ if (currentConfidence != newConfidence) {
SystemProperties.set(
- TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(confidence));
+ TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(newConfidence));
if (DEBUG) {
Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence
- + ", new=" + confidence);
+ + ", newConfidence=" + newConfidence);
}
+ return true;
}
+ return false;
}
/** Returns the time zone confidence value. See {@link TimeZoneConfidence} for details. */
@@ -148,6 +185,13 @@ public final class SystemTimeZone {
return SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY);
}
+ /**
+ * Dumps information about recent time zone decisions / changes to the supplied writer.
+ */
+ public static void dump(PrintWriter writer) {
+ sTimeZoneDebugLog.dump(writer);
+ }
+
private static boolean isValidTimeZoneConfidence(@TimeZoneConfidence int confidence) {
return confidence >= TIME_ZONE_CONFIDENCE_LOW && confidence <= TIME_ZONE_CONFIDENCE_HIGH;
}
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
index 46f335ef9fa2..d9a4266e2812 100644
--- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
@@ -46,7 +46,7 @@ public final class ConfigurationInternal {
private final boolean mAutoDetectionSupported;
private final int mSystemClockUpdateThresholdMillis;
- private final int mSystemClockConfidenceUpgradeThresholdMillis;
+ private final int mSystemClockConfidenceThresholdMillis;
private final Instant mAutoSuggestionLowerBound;
private final Instant mManualSuggestionLowerBound;
private final Instant mSuggestionUpperBound;
@@ -58,8 +58,8 @@ public final class ConfigurationInternal {
private ConfigurationInternal(Builder builder) {
mAutoDetectionSupported = builder.mAutoDetectionSupported;
mSystemClockUpdateThresholdMillis = builder.mSystemClockUpdateThresholdMillis;
- mSystemClockConfidenceUpgradeThresholdMillis =
- builder.mSystemClockConfidenceUpgradeThresholdMillis;
+ mSystemClockConfidenceThresholdMillis =
+ builder.mSystemClockConfidenceThresholdMillis;
mAutoSuggestionLowerBound = Objects.requireNonNull(builder.mAutoSuggestionLowerBound);
mManualSuggestionLowerBound = Objects.requireNonNull(builder.mManualSuggestionLowerBound);
mSuggestionUpperBound = Objects.requireNonNull(builder.mSuggestionUpperBound);
@@ -85,14 +85,14 @@ public final class ConfigurationInternal {
}
/**
- * Return the absolute threshold at/below which the system clock confidence can be upgraded.
- * i.e. if the detector receives a high-confidence time and the current system clock is +/- this
- * value from that time and the confidence in the time is low, then the device's confidence in
- * the current system clock time can be upgraded. This needs to be an amount users would
- * consider "close enough".
+ * Return the absolute threshold for Unix epoch time comparison at/below which the system clock
+ * confidence can be said to be "close enough", e.g. if the detector receives a high-confidence
+ * time and the current system clock is +/- this value from that time and the current confidence
+ * in the time is low, then the device's confidence in the current system clock time can be
+ * upgraded.
*/
- public int getSystemClockConfidenceUpgradeThresholdMillis() {
- return mSystemClockConfidenceUpgradeThresholdMillis;
+ public int getSystemClockConfidenceThresholdMillis() {
+ return mSystemClockConfidenceThresholdMillis;
}
/**
@@ -194,7 +194,7 @@ public final class ConfigurationInternal {
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability);
+ builder.setSetManualTimeCapability(suggestManualTimeZoneCapability);
return builder.build();
}
@@ -256,8 +256,8 @@ public final class ConfigurationInternal {
return "ConfigurationInternal{"
+ "mAutoDetectionSupported=" + mAutoDetectionSupported
+ ", mSystemClockUpdateThresholdMillis=" + mSystemClockUpdateThresholdMillis
- + ", mSystemClockConfidenceUpgradeThresholdMillis="
- + mSystemClockConfidenceUpgradeThresholdMillis
+ + ", mSystemClockConfidenceThresholdMillis="
+ + mSystemClockConfidenceThresholdMillis
+ ", mAutoSuggestionLowerBound=" + mAutoSuggestionLowerBound
+ "(" + mAutoSuggestionLowerBound.toEpochMilli() + ")"
+ ", mManualSuggestionLowerBound=" + mManualSuggestionLowerBound
@@ -274,7 +274,7 @@ public final class ConfigurationInternal {
static final class Builder {
private boolean mAutoDetectionSupported;
private int mSystemClockUpdateThresholdMillis;
- private int mSystemClockConfidenceUpgradeThresholdMillis;
+ private int mSystemClockConfidenceThresholdMillis;
@NonNull private Instant mAutoSuggestionLowerBound;
@NonNull private Instant mManualSuggestionLowerBound;
@NonNull private Instant mSuggestionUpperBound;
@@ -321,9 +321,9 @@ public final class ConfigurationInternal {
return this;
}
- /** See {@link ConfigurationInternal#getSystemClockConfidenceUpgradeThresholdMillis()}. */
- public Builder setSystemClockConfidenceUpgradeThresholdMillis(int thresholdMillis) {
- mSystemClockConfidenceUpgradeThresholdMillis = thresholdMillis;
+ /** See {@link ConfigurationInternal#getSystemClockConfidenceThresholdMillis()}. */
+ public Builder setSystemClockConfidenceThresholdMillis(int thresholdMillis) {
+ mSystemClockConfidenceThresholdMillis = thresholdMillis;
return this;
}
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 0ea5f7a105d1..84013a755035 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -245,7 +245,7 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
.setAutoDetectionSupported(isAutoDetectionSupported())
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setSystemClockUpdateThresholdMillis(getSystemClockUpdateThresholdMillis())
- .setSystemClockConfidenceUpgradeThresholdMillis(
+ .setSystemClockConfidenceThresholdMillis(
getSystemClockConfidenceUpgradeThresholdMillis())
.setAutoSuggestionLowerBound(getAutoSuggestionLowerBound())
.setManualSuggestionLowerBound(timeDetectorHelper.getManualSuggestionLowerBound())
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 5c47abfff09e..4186330fd23c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -24,6 +24,8 @@ import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -272,6 +274,58 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
}
@Override
+ public TimeState getTimeState() {
+ enforceManageTimeDetectorPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mTimeDetectorStrategy.getTimeState();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ void setTimeState(@NonNull TimeState timeState) {
+ enforceManageTimeDetectorPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mTimeDetectorStrategy.setTimeState(timeState);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean confirmTime(@NonNull UnixEpochTime time) {
+ enforceManageTimeDetectorPermission();
+ Objects.requireNonNull(time);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mTimeDetectorStrategy.confirmTime(time);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setManualTime(@NonNull ManualTimeSuggestion timeSignal) {
+ enforceManageTimeDetectorPermission();
+ Objects.requireNonNull(timeSignal);
+
+ // This calls suggestManualTime() as the logic is identical, it only differs in the
+ // permission required, which is handled on the line above.
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mTimeDetectorStrategy.suggestManualTime(userId, timeSignal);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) {
enforceSuggestTelephonyTimePermission();
Objects.requireNonNull(timeSignal);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index d306d10f3c69..990c00feae16 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -15,9 +15,12 @@
*/
package com.android.server.timedetector;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME;
@@ -30,6 +33,8 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIG
import android.app.time.ExternalTimeSuggestion;
import android.app.time.TimeConfiguration;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.ShellCommand;
@@ -69,6 +74,12 @@ class TimeDetectorShellCommand extends ShellCommand {
return runSuggestGnssTime();
case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
return runSuggestExternalTime();
+ case SHELL_COMMAND_GET_TIME_STATE:
+ return runGetTimeState();
+ case SHELL_COMMAND_SET_TIME_STATE:
+ return runSetTimeState();
+ case SHELL_COMMAND_CONFIRM_TIME:
+ return runConfirmTime();
default: {
return handleDefaultCommands(cmd);
}
@@ -140,6 +151,24 @@ class TimeDetectorShellCommand extends ShellCommand {
}
}
+ private int runGetTimeState() {
+ TimeState timeState = mInterface.getTimeState();
+ getOutPrintWriter().println(timeState);
+ return 0;
+ }
+
+ private int runSetTimeState() {
+ TimeState timeState = TimeState.parseCommandLineArgs(this);
+ mInterface.setTimeState(timeState);
+ return 0;
+ }
+
+ private int runConfirmTime() {
+ UnixEpochTime unixEpochTime = UnixEpochTime.parseCommandLineArgs(this);
+ getOutPrintWriter().println(mInterface.confirmTime(unixEpochTime));
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -161,6 +190,12 @@ class TimeDetectorShellCommand extends ShellCommand {
pw.printf(" Suggests a time as if via the \"gnss\" origin.\n");
pw.printf(" %s <external suggestion opts>\n", SHELL_COMMAND_SUGGEST_EXTERNAL_TIME);
pw.printf(" Suggests a time as if via the \"external\" origin.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_STATE);
+ pw.printf(" Returns the current time setting state.\n");
+ pw.printf(" %s <time state options>\n", SHELL_COMMAND_SET_TIME_STATE);
+ pw.printf(" Sets the current time state for tests.\n");
+ pw.printf(" %s <unix epoch time options>\n", SHELL_COMMAND_CONFIRM_TIME);
+ pw.printf(" Tries to confirms the time, raising the confidence.\n");
pw.println();
ManualTimeSuggestion.printCommandLineOpts(pw);
pw.println();
@@ -172,6 +207,10 @@ class TimeDetectorShellCommand extends ShellCommand {
pw.println();
ExternalTimeSuggestion.printCommandLineOpts(pw);
pw.println();
+ TimeState.printCommandLineOpts(pw);
+ pw.println();
+ UnixEpochTime.printCommandLineOpts(pw);
+ pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
pw.printf(" %s\n", KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 141cdcf0dbb9..ac2a391ca2c7 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -20,6 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
@@ -65,6 +67,25 @@ public interface TimeDetectorStrategy extends Dumpable {
/** Used when a time value originated from an externally specified signal. */
@Origin int ORIGIN_EXTERNAL = 5;
+ /** Returns a snapshot of the system clock's state. See {@link TimeState} for details. */
+ @NonNull
+ TimeState getTimeState();
+
+ /**
+ * Sets the system time state. See {@link TimeState} for details. Intended for use during
+ * testing to force the device's state, this bypasses the time detection logic.
+ */
+ void setTimeState(@NonNull TimeState timeState);
+
+ /**
+ * Signals that a user has confirmed the supplied time. If the {@code confirmationTime},
+ * adjusted for elapsed time since it was created (expected to be with {@link
+ * #getTimeState()}), is very close to the clock's current state, then this can be used to
+ * raise the system's confidence in that time. Returns {@code true} if confirmation was
+ * successful (i.e. the time matched), {@code false} otherwise.
+ */
+ boolean confirmTime(@NonNull UnixEpochTime confirmationTime);
+
/** Processes the suggested time from telephony sources. */
void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
@@ -100,6 +121,7 @@ public interface TimeDetectorStrategy extends Dumpable {
* Converts one of the {@code ORIGIN_} constants to a human readable string suitable for config
* and debug usage. Throws an {@link IllegalArgumentException} if the value is unrecognized.
*/
+ @NonNull
static String originToString(@Origin int origin) {
switch (origin) {
case ORIGIN_MANUAL:
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index fe2760e9ce10..3235bf077305 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -17,6 +17,7 @@
package com.android.server.timedetector;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
+import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
import static com.android.server.timedetector.TimeDetectorStrategy.originToString;
import android.annotation.CurrentTimeMillisLong;
@@ -25,6 +26,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
@@ -298,6 +301,75 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
}
@Override
+ public synchronized TimeState getTimeState() {
+ boolean userShouldConfirmTime = mEnvironment.systemClockConfidence() < TIME_CONFIDENCE_HIGH;
+ UnixEpochTime unixEpochTime = new UnixEpochTime(
+ mEnvironment.elapsedRealtimeMillis(), mEnvironment.systemClockMillis());
+ return new TimeState(unixEpochTime, userShouldConfirmTime);
+ }
+
+ @Override
+ public synchronized void setTimeState(@NonNull TimeState timeState) {
+ Objects.requireNonNull(timeState);
+
+ @TimeConfidence int confidence = timeState.getUserShouldConfirmTime()
+ ? TIME_CONFIDENCE_LOW : TIME_CONFIDENCE_HIGH;
+ mEnvironment.acquireWakeLock();
+ try {
+ // The origin is a lie but this method is only used for command line / manual testing
+ // to force the device into a specific state.
+ @Origin int origin = ORIGIN_MANUAL;
+ UnixEpochTime unixEpochTime = timeState.getUnixEpochTime();
+ setSystemClockAndConfidenceUnderWakeLock(
+ origin, unixEpochTime.toTimestampedValue(), confidence, "setTimeZoneState()");
+ } finally {
+ mEnvironment.releaseWakeLock();
+ }
+ }
+
+ @Override
+ public synchronized boolean confirmTime(@NonNull UnixEpochTime confirmationTime) {
+ Objects.requireNonNull(confirmationTime);
+
+ @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
+ @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
+ boolean timeNeedsConfirmation = currentTimeConfidence < newTimeConfidence;
+
+ // All system clock calculation take place under a wake lock.
+ mEnvironment.acquireWakeLock();
+ try {
+ // Check if the specified time matches the current system clock time (closely
+ // enough) to raise the confidence.
+ long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+ long actualSystemClockMillis = mEnvironment.systemClockMillis();
+ long adjustedAutoDetectedUnixEpochMillis =
+ confirmationTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis();
+ long absTimeDifferenceMillis =
+ Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
+ int confidenceUpgradeThresholdMillis =
+ mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
+ boolean timeConfirmed = absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
+ boolean updateConfidenceRequired = timeNeedsConfirmation && timeConfirmed;
+ if (updateConfidenceRequired) {
+ String logMsg = "Confirm system clock time."
+ + " confirmationTime=" + confirmationTime
+ + " newTimeConfidence=" + newTimeConfidence
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + " (old) actualSystemClockMillis=" + actualSystemClockMillis
+ + " currentTimeConfidence=" + currentTimeConfidence;
+ if (DBG) {
+ Slog.d(LOG_TAG, logMsg);
+ }
+
+ mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
+ }
+ return timeConfirmed;
+ } finally {
+ mEnvironment.releaseWakeLock();
+ }
+ }
+
+ @Override
public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
// Empty time suggestion means that telephony network connectivity has been lost.
// The passage of time is relentless, and we don't expect our users to use a time machine,
@@ -799,7 +871,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
long absTimeDifferenceMillis =
Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
int confidenceUpgradeThresholdMillis =
- mCurrentConfigurationInternal.getSystemClockConfidenceUpgradeThresholdMillis();
+ mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
boolean updateConfidenceRequired =
absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
if (updateConfidenceRequired) {
@@ -866,7 +938,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
boolean updateSystemClockRequired = absTimeDifference >= systemClockUpdateThreshold;
@TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
- boolean updateConfidenceRequired = newTimeConfidence > currentTimeConfidence;
+ boolean updateConfidenceRequired = newTimeConfidence != currentTimeConfidence;
if (updateSystemClockRequired) {
String logMsg = "Set system clock & confidence."
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index ef99d616cbec..d413febdcdb4 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -243,7 +243,7 @@ public final class ConfigurationInternal {
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
+ builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
return builder.build();
}
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index d973ca689600..4749f73d89e1 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -28,6 +28,7 @@ import com.android.server.LocalServices;
import com.android.server.SystemTimeZone;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import java.io.PrintWriter;
import java.util.Objects;
/**
@@ -57,6 +58,7 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment
}
@Override
+ @NonNull
public ConfigurationInternal getCurrentUserConfigurationInternal() {
return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
}
@@ -74,14 +76,25 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment
@Override
public void setDeviceTimeZoneAndConfidence(
- @NonNull String zoneId, @TimeZoneConfidence int confidence) {
+ @NonNull String zoneId, @TimeZoneConfidence int confidence,
+ @NonNull String logInfo) {
AlarmManagerInternal alarmManagerInternal =
LocalServices.getService(AlarmManagerInternal.class);
- alarmManagerInternal.setTimeZone(zoneId, confidence);
+ alarmManagerInternal.setTimeZone(zoneId, confidence, logInfo);
}
@Override
public @ElapsedRealtimeLong long elapsedRealtimeMillis() {
return SystemClock.elapsedRealtime();
}
+
+ @Override
+ public void addDebugLogEntry(@NonNull String logMsg) {
+ SystemTimeZone.addDebugLogEntry(logMsg);
+ }
+
+ @Override
+ public void dumpDebugLog(@NonNull PrintWriter printWriter) {
+ SystemTimeZone.dump(printWriter);
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 59db855dcf25..0e58d91a7f65 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -23,6 +23,7 @@ import android.app.ActivityManager;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -313,6 +314,57 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
@Override
+ @NonNull
+ public TimeZoneState getTimeZoneState() {
+ enforceManageTimeZoneDetectorPermission();
+
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ return mTimeZoneDetectorStrategy.getTimeZoneState();
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
+ }
+
+ void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
+ enforceManageTimeZoneDetectorPermission();
+
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ mTimeZoneDetectorStrategy.setTimeZoneState(timeZoneState);
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean confirmTimeZone(@NonNull String timeZoneId) {
+ enforceManageTimeZoneDetectorPermission();
+
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ return mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId);
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
+ enforceManageTimeZoneDetectorPermission();
+
+ // This calls suggestManualTimeZone() as the logic is identical, it only differs in the
+ // permission required, which is handled on the line above.
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
enforceSuggestManualTimeZonePermission();
Objects.requireNonNull(timeZoneSuggestion);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 4d808ff231f2..1b9f8e6cd66f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,8 +15,10 @@
*/
package com.android.server.timezonedetector;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIRM_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -24,6 +26,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_TEL
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
@@ -38,6 +41,7 @@ import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR
import android.app.time.LocationTimeZoneManager;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
@@ -83,6 +87,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return runSuggestTelephonyTimeZone();
case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
return runEnableTelephonyFallback();
+ case SHELL_COMMAND_GET_TIME_ZONE_STATE:
+ return runGetTimeZoneState();
+ case SHELL_COMMAND_SET_TIME_ZONE_STATE:
+ return runSetTimeZoneState();
+ case SHELL_COMMAND_CONFIRM_TIME_ZONE:
+ return runConfirmTimeZone();
case SHELL_COMMAND_DUMP_METRICS:
return runDumpMetrics();
default: {
@@ -183,6 +193,45 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return 0;
}
+ private int runGetTimeZoneState() {
+ TimeZoneState timeZoneState = mInterface.getTimeZoneState();
+ getOutPrintWriter().println(timeZoneState);
+ return 0;
+ }
+
+ private int runSetTimeZoneState() {
+ TimeZoneState timeZoneState = TimeZoneState.parseCommandLineArgs(this);
+ mInterface.setTimeZoneState(timeZoneState);
+ return 0;
+ }
+
+ private int runConfirmTimeZone() {
+ String timeZoneId = parseTimeZoneIdArg(this);
+ getOutPrintWriter().println(mInterface.confirmTimeZone(timeZoneId));
+ return 0;
+ }
+
+ private static String parseTimeZoneIdArg(ShellCommand cmd) {
+ String zoneId = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--zone_id": {
+ zoneId = cmd.getNextArgRequired();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (zoneId == null) {
+ throw new IllegalArgumentException("No zoneId specified.");
+ }
+ return zoneId;
+ }
+
private int runDumpMetrics() {
final PrintWriter pw = getOutPrintWriter();
MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
@@ -226,6 +275,12 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n");
pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
+ pw.printf(" Returns the current time zone setting state.\n");
+ pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
+ pw.printf(" Sets the current time zone state for tests.\n");
+ pw.printf(" %s <--zone_id Olson ID>\n", SHELL_COMMAND_CONFIRM_TIME_ZONE);
+ pw.printf(" Tries to confirms the time zone, raising the confidence.\n");
pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
@@ -235,6 +290,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.println();
TelephonyTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
+ TimeZoneState.printCommandLineOpts(pw);
+ pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 95ebd6803cd0..e4b2df1460ae 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,7 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
@@ -93,6 +94,24 @@ import android.util.IndentingPrintWriter;
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
+ /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
+ @NonNull
+ TimeZoneState getTimeZoneState();
+
+ /**
+ * Sets the system time zone state. See {@link TimeZoneState} for details. Intended for use
+ * during testing to force the device's state, this bypasses the time zone detection logic.
+ */
+ void setTimeZoneState(@NonNull TimeZoneState timeZoneState);
+
+ /**
+ * Signals that a user has confirmed the time zone. If the {@code timeZoneId} is the same as
+ * the current time zone then this can be used to raise the system's confidence in that time
+ * zone. Returns {@code true} if confirmation was successful (i.e. the ID matched),
+ * {@code false} otherwise.
+ */
+ boolean confirmTimeZone(@NonNull String timeZoneId);
+
/**
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
* {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 9b35779f6820..85986ddaa564 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -23,6 +23,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
@@ -30,19 +31,20 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
import android.os.Handler;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
-import android.util.LocalLog;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import java.io.PrintWriter;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
@@ -76,7 +78,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
/**
- * Returns the device's currently configured time zone.
+ * Returns the device's currently configured time zone. May return an empty string.
*/
@NonNull String getDeviceTimeZone();
@@ -86,10 +88,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@TimeZoneConfidence int getDeviceTimeZoneConfidence();
/**
- * Sets the device's time zone and associated confidence.
+ * Sets the device's time zone, associated confidence, and records a debug log entry.
*/
void setDeviceTimeZoneAndConfidence(
- @NonNull String zoneId, @TimeZoneConfidence int confidence);
+ @NonNull String zoneId, @TimeZoneConfidence int confidence,
+ @NonNull String logInfo);
/**
* Returns the time according to the elapsed realtime clock, the same as {@link
@@ -97,6 +100,16 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
*/
@ElapsedRealtimeLong
long elapsedRealtimeMillis();
+
+ /**
+ * Adds a standalone entry to the time zone debug log.
+ */
+ void addDebugLogEntry(@NonNull String logMsg);
+
+ /**
+ * Dumps the time zone debug log to the supplied {@link PrintWriter}.
+ */
+ void dumpDebugLog(PrintWriter printWriter);
}
private static final String LOG_TAG = TimeZoneDetectorService.TAG;
@@ -169,13 +182,6 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private final Environment mEnvironment;
/**
- * A log that records the decisions / decision metadata that affected the device's time zone.
- * This is logged in bug reports to assist with debugging issues with detection.
- */
- @NonNull
- private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
- /**
* A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two
* mappings: devices will have a small number of telephony devices and slotIndexes are assumed
* to be stable.
@@ -246,6 +252,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
@Override
+ public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
+ Objects.requireNonNull(timeZoneId);
+
+ String currentTimeZoneId = mEnvironment.getDeviceTimeZone();
+ if (!currentTimeZoneId.equals(timeZoneId)) {
+ return false;
+ }
+
+ if (mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH) {
+ mEnvironment.setDeviceTimeZoneAndConfidence(currentTimeZoneId,
+ TIME_ZONE_CONFIDENCE_HIGH, "confirmTimeZone: timeZoneId=" + timeZoneId);
+ }
+ return true;
+ }
+
+ @Override
+ public synchronized TimeZoneState getTimeZoneState() {
+ boolean userShouldConfirmId =
+ mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH;
+ return new TimeZoneState(mEnvironment.getDeviceTimeZone(), userShouldConfirmId);
+ }
+
+ @Override
+ public void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
+ Objects.requireNonNull(timeZoneState);
+
+ @TimeZoneConfidence int confidence = timeZoneState.getUserShouldConfirmId()
+ ? TIME_ZONE_CONFIDENCE_LOW : TIME_ZONE_CONFIDENCE_HIGH;
+ mEnvironment.setDeviceTimeZoneAndConfidence(
+ timeZoneState.getId(), confidence, "setTimeZoneState()");
+ }
+
+ @Override
public synchronized void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion suggestion) {
@@ -297,9 +336,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
currentUserConfig.createCapabilitiesAndConfig();
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
- if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
+ if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
- + ", capabilities=" + capabilities
+ + ": capabilities=" + capabilities
+ ", timeZoneId=" + timeZoneId
+ ", cause=" + cause);
return false;
@@ -349,11 +388,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
- String logMsg = "enableTelephonyTimeZoneFallbackMode"
- + ": currentUserConfig=" + currentUserConfig
+ String logMsg = "enableTelephonyTimeZoneFallbackMode: "
+ + " currentUserConfig=" + currentUserConfig
+ ", mTelephonyTimeZoneFallbackEnabled="
+ mTelephonyTimeZoneFallbackEnabled;
- logTimeZoneDetectorChange(logMsg);
+ logTimeZoneDebugInfo(logMsg);
// mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
// If there is currently a certain geolocation suggestion, then the telephony fallback
@@ -558,19 +597,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
- String logMsg = "disableTelephonyFallbackIfNeeded"
- + ": mTelephonyTimeZoneFallbackEnabled="
- + mTelephonyTimeZoneFallbackEnabled;
- logTimeZoneDetectorChange(logMsg);
+ String logMsg = "disableTelephonyFallbackIfNeeded:"
+ + " mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled;
+ logTimeZoneDebugInfo(logMsg);
}
}
}
- private void logTimeZoneDetectorChange(@NonNull String logMsg) {
+ private void logTimeZoneDebugInfo(@NonNull String logMsg) {
if (DBG) {
Slog.d(LOG_TAG, logMsg);
}
- mTimeZoneChangesLog.log(logMsg);
+ mEnvironment.addDebugLogEntry(logMsg);
}
/**
@@ -598,7 +636,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD;
if (!suggestionGoodEnough) {
if (DBG) {
- Slog.d(LOG_TAG, "Best suggestion not good enough."
+ Slog.d(LOG_TAG, "Best suggestion not good enough:"
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
+ ", detectionReason=" + detectionReason);
}
@@ -611,12 +649,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
if (zoneId == null) {
Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
- + " detectionReason=" + detectionReason);
+ + ", detectionReason=" + detectionReason);
return;
}
- String cause = "Found good suggestion."
- + ", bestTelephonySuggestion=" + bestTelephonySuggestion
+ String cause = "Found good suggestion:"
+ + " bestTelephonySuggestion=" + bestTelephonySuggestion
+ ", detectionReason=" + detectionReason;
setDeviceTimeZoneIfRequired(zoneId, cause);
}
@@ -634,9 +672,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
if (newZoneId.equals(currentZoneId) && newConfidence <= currentConfidence) {
// No need to modify the device time zone settings.
if (DBG) {
- Slog.d(LOG_TAG, "No need to change the time zone;"
- + " device is already set to newZoneId."
- + ", newZoneId=" + newZoneId
+ Slog.d(LOG_TAG, "No need to change the time zone device is already set to newZoneId"
+ + ": newZoneId=" + newZoneId
+ ", cause=" + cause
+ ", currentScore=" + currentConfidence
+ ", newConfidence=" + newConfidence);
@@ -644,13 +681,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return;
}
- mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence);
- String logMsg = "Set device time zone or higher confidence."
- + ", currentZoneId=" + currentZoneId
- + ", newZoneId=" + newZoneId
+ String logInfo = "Set device time zone or higher confidence:"
+ + " newZoneId=" + newZoneId
+ ", cause=" + cause
+ ", newConfidence=" + newConfidence;
- logTimeZoneDetectorChange(logMsg);
+ if (DBG) {
+ Slog.d(LOG_TAG, logInfo);
+ }
+ mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence, logInfo);
}
@GuardedBy("this")
@@ -702,7 +740,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
String logMsg = "handleConfigurationInternalChanged:"
+ " oldConfiguration=" + mCurrentConfigurationInternal
+ ", newConfiguration=" + currentUserConfig;
- logTimeZoneDetectorChange(logMsg);
+ logTimeZoneDebugInfo(logMsg);
mCurrentConfigurationInternal = currentUserConfig;
// The configuration change may have changed available suggestions or the way suggestions
@@ -731,9 +769,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
+ formatDebugString(mTelephonyTimeZoneFallbackEnabled));
ipw.decreaseIndent(); // level 2
- ipw.println("Time zone change log:");
+ ipw.println("Time zone debug log:");
ipw.increaseIndent(); // level 2
- mTimeZoneChangesLog.dump(ipw);
+ mEnvironment.dumpDebugLog(ipw);
ipw.decreaseIndent(); // level 2
ipw.println("Manual suggestion history:");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c43e298b3f28..a5613078bdcd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12553,9 +12553,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
return false;
}
+ String logInfo = "DevicePolicyManager.setTimeZone()";
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.getAlarmManagerInternal()
- .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH));
+ .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH, logInfo));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_TIME_ZONE)
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 830e2ac029c2..e2b25ef8da2a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3438,7 +3438,7 @@ public class AlarmManagerServiceTest {
public void setTimeZoneImpl() {
final long durationMs = 20000L;
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
- mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH);
+ mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH, "AlarmManagerServiceTest");
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0bdcac655a14..ddb3049b5cd1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4503,7 +4503,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
setupDeviceOwner();
dpm.setTimeZone(admin1, "Asia/Shanghai");
verify(getServices().alarmManagerInternal)
- .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
+ .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString());
}
@Test
@@ -4519,7 +4519,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
dpm.setTimeZone(admin1, "Asia/Shanghai");
verify(getServices().alarmManagerInternal)
- .setTimeZone("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
+ .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
index 208f99ad63a9..a24afe6bc25e 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
@@ -79,7 +79,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeCapability());
+ assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -98,7 +98,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
- capabilities.getSuggestManualTimeCapability());
+ capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
@@ -131,7 +131,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -149,7 +149,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
@@ -181,7 +181,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
@@ -199,7 +199,7 @@ public class ConfigurationInternalTest {
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
index 1c6add2ffd61..7b38fa014512 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.util.IndentingPrintWriter;
@@ -30,6 +32,8 @@ import android.util.IndentingPrintWriter;
* in tests.
*/
class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
+ // State
+ private TimeState mTimeState;
// Call tracking.
private TelephonyTimeSuggestion mLastTelephonySuggestion;
@@ -38,9 +42,26 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
private NetworkTimeSuggestion mLastNetworkSuggestion;
private GnssTimeSuggestion mLastGnssSuggestion;
private ExternalTimeSuggestion mLastExternalSuggestion;
+ private UnixEpochTime mLastConfirmedTime;
private boolean mDumpCalled;
@Override
+ public TimeState getTimeState() {
+ return mTimeState;
+ }
+
+ @Override
+ public void setTimeState(TimeState timeState) {
+ mTimeState = timeState;
+ }
+
+ @Override
+ public boolean confirmTime(UnixEpochTime confirmationTime) {
+ mLastConfirmedTime = confirmationTime;
+ return false;
+ }
+
+ @Override
public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) {
mLastTelephonySuggestion = timeSuggestion;
}
@@ -79,6 +100,7 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
mLastNetworkSuggestion = null;
mLastGnssSuggestion = null;
mLastExternalSuggestion = null;
+ mLastConfirmedTime = null;
mDumpCalled = false;
}
@@ -104,6 +126,10 @@ class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
assertEquals(expectedSuggestion, mLastExternalSuggestion);
}
+ void verifyConfirmTimeCalled(UnixEpochTime expectedConfirmationTime) {
+ assertEquals(mLastConfirmedTime, expectedConfirmationTime);
+ }
+
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 67c8c4fc102f..12184894a943 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -19,6 +19,7 @@ package com.android.server.timedetector;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -38,6 +39,8 @@ import static org.mockito.Mockito.when;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeConfiguration;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
@@ -424,6 +427,125 @@ public class TimeDetectorServiceTest {
}
@Test
+ public void testGetTimeState() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+ TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+ mFakeTimeDetectorStrategy.setTimeState(fakeState);
+
+ TimeState actualState = mTimeDetectorService.getTimeState();
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ assertEquals(actualState, fakeState);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testGetTimeState_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ try {
+ mTimeDetectorService.getTimeState();
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testSetTimeState() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+ mTimeDetectorService.setTimeState(state);
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ assertEquals(mFakeTimeDetectorStrategy.getTimeState(), state);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetTimeState_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+ try {
+ mTimeDetectorService.setTimeState(state);
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testConfirmTime() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ UnixEpochTime confirmationTime = new UnixEpochTime(12345L, 98765L);
+ // The fake strategy always returns false.
+ assertFalse(mTimeDetectorService.confirmTime(confirmationTime));
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ mFakeTimeDetectorStrategy.verifyConfirmTimeCalled(confirmationTime);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testConfirmTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ try {
+ mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L));
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testSetManualTime() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
+
+ boolean expectedResult = true; // The test strategy always returns true.
+ assertEquals(expectedResult,
+ mTimeDetectorService.setManualTime(timeSuggestion));
+
+ // The service calls "suggestManualTime()" because the logic is the same.
+ mFakeTimeDetectorStrategy.verifySuggestManualTimeCalled(
+ mTestCallerIdentityInjector.getCallingUserId(), timeSuggestion);
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetManualTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+ ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
+
+ try {
+ mTimeDetectorService.setManualTime(timeSuggestion);
+ fail("Expected SecurityException");
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
.thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 060c31f2985e..9ec70dc5ec3b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,6 +18,7 @@ package com.android.server.timedetector;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
@@ -31,6 +32,8 @@ import static org.junit.Assert.fail;
import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
@@ -300,7 +303,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
- .setSystemClockConfidenceUpgradeThresholdMillis(
+ .setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script()
@@ -338,7 +341,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
- .setSystemClockConfidenceUpgradeThresholdMillis(
+ .setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_LOW)
@@ -375,7 +378,7 @@ public class TimeDetectorStrategyImplTest {
final int confidenceUpgradeThresholdMillis = 1000;
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
- .setSystemClockConfidenceUpgradeThresholdMillis(
+ .setSystemClockConfidenceThresholdMillis(
confidenceUpgradeThresholdMillis)
.build();
Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_HIGH)
@@ -1137,6 +1140,126 @@ public class TimeDetectorStrategyImplTest {
}
@Test
+ public void testGetTimeState() {
+ TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+ Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+ .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+ UnixEpochTime systemClockTime = new UnixEpochTime(deviceTime.getReferenceTimeMillis(),
+ deviceTime.getValue().toEpochMilli());
+
+ // When confidence is low, the user should confirm.
+ script.assertGetTimeStateReturns(new TimeState(systemClockTime, true));
+
+ // When confidence is high, no need for the user to confirm.
+ script.pokeFakeClocks(deviceTime, TIME_ZONE_CONFIDENCE_HIGH)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH);
+
+ script.assertGetTimeStateReturns(new TimeState(systemClockTime, false));
+ }
+
+ @Test
+ public void testSetTimeState() {
+ TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+ Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+ .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+
+ UnixEpochTime systemClockTime = new UnixEpochTime(11111L, 222222L);
+ boolean userShouldConfirmTime = false;
+ TimeState state = new TimeState(systemClockTime, userShouldConfirmTime);
+ script.simulateSetTimeState(state);
+
+ UnixEpochTime expectedTime = systemClockTime.at(script.peekElapsedRealtimeMillis());
+ long expectedTimeMillis = expectedTime.getUnixEpochTimeMillis();
+ // userShouldConfirmTime == high confidence
+ script.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+ .verifySystemClockWasSetAndResetCallTracking(expectedTimeMillis);
+
+ TimeState expectedTimeState = new TimeState(expectedTime, userShouldConfirmTime);
+ script.assertGetTimeStateReturns(expectedTimeState);
+ }
+
+ @Test
+ public void testConfirmTime_autoDisabled() {
+ testConfirmTime(CONFIG_AUTO_ENABLED);
+ }
+
+ @Test
+ public void testConfirmTime_autoEnabled() {
+ testConfirmTime(CONFIG_AUTO_ENABLED);
+ }
+
+ private void testConfirmTime(ConfigurationInternal config) {
+ TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+ Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+ .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+ long maxConfidenceThreshold = config.getSystemClockConfidenceThresholdMillis();
+ UnixEpochTime incorrectTime1 =
+ new UnixEpochTime(
+ deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold + 1,
+ deviceTime.getValue().toEpochMilli());
+ script.simulateConfirmTime(incorrectTime1, false)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ UnixEpochTime incorrectTime2 =
+ new UnixEpochTime(
+ deviceTime.getReferenceTimeMillis(),
+ deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold + 1);
+ script.simulateConfirmTime(incorrectTime2, false)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm using a time that is at the threshold.
+ UnixEpochTime correctTime1 =
+ new UnixEpochTime(
+ deviceTime.getReferenceTimeMillis(),
+ deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold);
+ script.simulateConfirmTime(correctTime1, true)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Reset back to low confidence.
+ script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm using a time that is at the threshold.
+ UnixEpochTime correctTime2 =
+ new UnixEpochTime(
+ deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold,
+ deviceTime.getValue().toEpochMilli());
+ script.simulateConfirmTime(correctTime2, true)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Reset back to low confidence.
+ script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm using a time that exactly matches.
+ UnixEpochTime correctTime3 =
+ new UnixEpochTime(
+ deviceTime.getReferenceTimeMillis(),
+ deviceTime.getValue().toEpochMilli());
+ script.simulateConfirmTime(correctTime3, true)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Now try to confirm using another incorrect time: Confidence should remain high as the
+ // confirmation is ignored / returns false.
+ script.simulateConfirmTime(incorrectTime1, false)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() {
ConfigurationInternal configInternal =
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
@@ -1771,7 +1894,7 @@ public class TimeDetectorStrategyImplTest {
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- public void verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
+ public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -1879,6 +2002,12 @@ public class TimeDetectorStrategyImplTest {
return simulateTimePassing(999);
}
+ /** Calls {@link TimeDetectorStrategy#setTimeState(TimeState)}. */
+ Script simulateSetTimeState(TimeState timeState) {
+ mTimeDetectorStrategy.setTimeState(timeState);
+ return this;
+ }
+
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeEnvironment.verifySystemClockNotSet();
mFakeEnvironment.resetCallTracking();
@@ -1892,7 +2021,7 @@ public class TimeDetectorStrategyImplTest {
}
Script verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
- mFakeEnvironment.verifySystemClockConfidence(expectedConfidence);
+ mFakeEnvironment.verifySystemClockConfidenceLatest(expectedConfidence);
return this;
}
@@ -1931,6 +2060,11 @@ public class TimeDetectorStrategyImplTest {
return this;
}
+ Script assertGetTimeStateReturns(TimeState expected) {
+ assertEquals(expected, mTimeDetectorStrategy.getTimeState());
+ return this;
+ }
+
/**
* White box test info: Returns the telephony suggestion that would be used, if any, given
* the current elapsed real time clock and regardless of origin prioritization.
@@ -2037,6 +2171,11 @@ public class TimeDetectorStrategyImplTest {
long calculateTimeInMillisForNow(TimestampedValue<Long> unixEpochTime) {
return TimeDetectorStrategy.getTimeAt(unixEpochTime, peekElapsedRealtimeMillis());
}
+
+ Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) {
+ assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime));
+ return this;
+ }
}
private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 767c466b74f0..810bd82f4431 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -76,7 +76,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -102,7 +102,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_POSSESSED,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -143,7 +143,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
// This has user privacy implications so it is not restricted in the same way as others.
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -170,7 +170,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_ALLOWED,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
// This has user privacy implications so it is not restricted in the same way as others.
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -211,7 +211,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -235,7 +235,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -279,7 +279,7 @@ public class ConfigurationInternalTest {
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
assertEquals(CAPABILITY_NOT_APPLICABLE,
- capabilities.getSuggestManualTimeZoneCapability());
+ capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -303,7 +303,7 @@ public class ConfigurationInternalTest {
TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureAutoDetectionEnabledCapability());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index c9fc033e9086..339e5b23cd9a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -20,19 +20,40 @@ import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+ private TimeZoneState mTimeZoneState;
+
// Call tracking.
private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
private ManualTimeZoneSuggestion mLastManualSuggestion;
+ private Integer mLastManualSuggestionUserId;
private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
+ private String mLastConfirmedTimeZone;
private boolean mDumpCalled;
@Override
+ public boolean confirmTimeZone(String timeZoneId) {
+ mLastConfirmedTimeZone = timeZoneId;
+ return false;
+ }
+
+ @Override
+ public TimeZoneState getTimeZoneState() {
+ return mTimeZoneState;
+ }
+
+ @Override
+ public void setTimeZoneState(TimeZoneState timeZoneState) {
+ mTimeZoneState = timeZoneState;
+ }
+
+ @Override
public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
mLastGeolocationSuggestion = timeZoneSuggestion;
}
@@ -40,6 +61,7 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@Override
public boolean suggestManualTimeZone(
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
+ mLastManualSuggestionUserId = userId;
mLastManualSuggestion = timeZoneSuggestion;
return true;
}
@@ -78,8 +100,10 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
void resetCallTracking() {
mLastGeolocationSuggestion = null;
mLastManualSuggestion = null;
+ mLastManualSuggestionUserId = null;
mLastTelephonySuggestion = null;
mDumpCalled = false;
+ mLastConfirmedTimeZone = null;
}
void verifySuggestGeolocationTimeZoneCalled(
@@ -87,7 +111,9 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
assertEquals(expectedSuggestion, mLastGeolocationSuggestion);
}
- void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
+ void verifySuggestManualTimeZoneCalled(
+ @UserIdInt int expectedUserId, ManualTimeZoneSuggestion expectedSuggestion) {
+ assertEquals((Integer) expectedUserId, mLastManualSuggestionUserId);
assertEquals(expectedSuggestion, mLastManualSuggestion);
}
@@ -98,4 +124,8 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
+
+ void verifyConfirmTimeZoneCalled(String expectedTimeZoneId) {
+ assertEquals(expectedTimeZoneId, mLastConfirmedTimeZone);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 6365b98ce24f..b8c3ea7db91f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server.timezonedetector;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +34,7 @@ import static org.mockito.Mockito.when;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
@@ -294,7 +296,8 @@ public class TimeZoneDetectorServiceTest {
assertEquals(expectedResult,
mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion));
- mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
+ mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
+ mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
verify(mMockContext).enforceCallingOrSelfPermission(
eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
@@ -350,6 +353,124 @@ public class TimeZoneDetectorServiceTest {
}
@Test
+ public void testGetTimeZoneState() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+ TimeZoneState fakeState = new TimeZoneState("Europe/Narnia", true);
+ mFakeTimeZoneDetectorStrategy.setTimeZoneState(fakeState);
+
+ TimeZoneState actualState = mTimeZoneDetectorService.getTimeZoneState();
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ assertEquals(actualState, fakeState);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testGetTimeZoneState_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ try {
+ mTimeZoneDetectorService.getTimeZoneState();
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testSetTimeZoneState() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
+ mTimeZoneDetectorService.setTimeZoneState(state);
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ assertEquals(mFakeTimeZoneDetectorStrategy.getTimeZoneState(), state);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetTimeZoneState_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
+ try {
+ mTimeZoneDetectorService.setTimeZoneState(state);
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testConfirmTimeZone() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ // The fake strategy always returns false.
+ assertFalse(mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ mFakeTimeZoneDetectorStrategy.verifyConfirmTimeZoneCalled("Europe/Narnia");
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testConfirmTimeZone_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ try {
+ mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia");
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
+ public void testSetManualTimeZone() {
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+ ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+ boolean expectedResult = true; // The test strategy always returns true.
+ assertEquals(expectedResult,
+ mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion));
+
+ // The service calls "suggestManualTimeZone()" because the logic is the same.
+ mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
+ mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
+
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSetManualTimeZone_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+ ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+ try {
+ mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion);
+ fail("Expected SecurityException");
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
+ anyString());
+ }
+ }
+
+ @Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
.thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index ff838fdf9d73..77d8b8bf9687 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -25,6 +25,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
@@ -33,6 +34,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -40,6 +42,7 @@ import static org.junit.Assert.assertTrue;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
@@ -51,6 +54,7 @@ import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.Qualifie
import org.junit.Before;
import org.junit.Test;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -182,7 +186,7 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
createEmptySlotIndex2Suggestion();
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -295,7 +299,7 @@ public class TimeZoneDetectorStrategyImplTest {
for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
// Start with the device in a known state.
- script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -347,7 +351,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testTelephonySuggestionsSingleSlotId() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -413,7 +417,7 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_NONE);
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
.resetConfigurationTracking()
@@ -552,7 +556,7 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(geoTzEnabledConfig)
.resetConfigurationTracking();
@@ -567,7 +571,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_ENABLED)
.resetConfigurationTracking();
@@ -582,7 +586,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -598,7 +602,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_DISABLED)
.resetConfigurationTracking();
@@ -614,7 +618,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testManualSuggestion_autoDetectNotSupported() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DETECT_NOT_SUPPORTED)
.resetConfigurationTracking();
@@ -630,7 +634,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_uncertain() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -647,7 +651,7 @@ public class TimeZoneDetectorStrategyImplTest {
@Test
public void testGeoSuggestion_noZones() {
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -666,7 +670,7 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/London");
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -692,7 +696,7 @@ public class TimeZoneDetectorStrategyImplTest {
createCertainGeolocationSuggestion("Europe/Paris");
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
@@ -733,7 +737,7 @@ public class TimeZoneDetectorStrategyImplTest {
"Europe/Paris");
Script script = new Script()
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
.resetConfigurationTracking();
@@ -779,7 +783,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(config)
.resetConfigurationTracking();
@@ -913,7 +917,7 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
- .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(config)
.resetConfigurationTracking();
@@ -969,6 +973,78 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Test
+ public void testGetTimeZoneState() {
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+ .resetConfigurationTracking();
+
+ String timeZoneId = "Europe/London";
+
+ // When confidence is low, the user should confirm.
+ script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW);
+ assertEquals(new TimeZoneState(timeZoneId, true),
+ mTimeZoneDetectorStrategy.getTimeZoneState());
+
+ // When confidence is high, no need for the user to confirm.
+ script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+
+ assertEquals(new TimeZoneState(timeZoneId, false),
+ mTimeZoneDetectorStrategy.getTimeZoneState());
+ }
+
+ @Test
+ public void testSetTimeZoneState() {
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+ .resetConfigurationTracking();
+
+ String timeZoneId = "Europe/London";
+ boolean userShouldConfirmId = false;
+ TimeZoneState state = new TimeZoneState(timeZoneId, userShouldConfirmId);
+ mTimeZoneDetectorStrategy.setTimeZoneState(state);
+
+ script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+ assertEquals(state, mTimeZoneDetectorStrategy.getTimeZoneState());
+ }
+
+ @Test
+ public void testConfirmTimeZone_autoDisabled() {
+ testConfirmTimeZone(CONFIG_AUTO_DISABLED_GEO_DISABLED);
+ }
+
+ @Test
+ public void testConfirmTimeZone_autoEnabled() {
+ testConfirmTimeZone(CONFIG_AUTO_ENABLED_GEO_DISABLED);
+ }
+
+ private void testConfirmTimeZone(ConfigurationInternal config) {
+ String timeZoneId = "Europe/London";
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(config)
+ .resetConfigurationTracking();
+
+ String incorrectTimeZoneId = "Europe/Paris";
+ assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
+ script.verifyTimeZoneNotChanged();
+
+ assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
+ script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+
+ assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
+ // The strategy checks the current confidence and if it is already high it takes no action.
+ script.verifyTimeZoneNotChanged();
+
+ assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
+ script.verifyTimeZoneNotChanged();
+ }
+
+ @Test
public void testGenerateMetricsState_enhancedMetricsCollection() {
testGenerateMetricsState(true);
}
@@ -986,7 +1062,7 @@ public class TimeZoneDetectorStrategyImplTest {
String expectedDeviceTimeZoneId = "InitialZoneId";
Script script = new Script()
- .initializeTimeZoneSetting(expectedDeviceTimeZoneId)
+ .initializeTimeZoneSetting(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(expectedInternalConfig)
.resetConfigurationTracking();
@@ -1112,11 +1188,16 @@ public class TimeZoneDetectorStrategyImplTest {
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
private final TestState<String> mTimeZoneId = new TestState<>();
- private int mTimeZoneConfidence;
+ private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
+ FakeEnvironment() {
+ // Ensure the fake environment starts with the defaults a fresh device would.
+ initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
+ }
+
void initializeConfig(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
@@ -1125,8 +1206,9 @@ public class TimeZoneDetectorStrategyImplTest {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
- void initializeTimeZoneSetting(String zoneId) {
+ void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
mTimeZoneId.init(zoneId);
+ mTimeZoneConfidence.init(timeZoneConfidence);
}
void incrementClock() {
@@ -1150,14 +1232,14 @@ public class TimeZoneDetectorStrategyImplTest {
@Override
public int getDeviceTimeZoneConfidence() {
- return mTimeZoneConfidence;
+ return mTimeZoneConfidence.getLatest();
}
@Override
public void setDeviceTimeZoneAndConfidence(
- String zoneId, @TimeZoneConfidence int confidence) {
+ String zoneId, @TimeZoneConfidence int confidence, String logInfo) {
mTimeZoneId.set(zoneId);
- mTimeZoneConfidence = confidence;
+ mTimeZoneConfidence.set(confidence);
}
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1167,17 +1249,22 @@ public class TimeZoneDetectorStrategyImplTest {
void assertTimeZoneNotChanged() {
mTimeZoneId.assertHasNotBeenSet();
+ mTimeZoneConfidence.assertHasNotBeenSet();
}
void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
mTimeZoneId.assertHasBeenSet();
mTimeZoneId.assertChangeCount(1);
mTimeZoneId.assertLatestEquals(timeZoneId);
- assertEquals(confidence, mTimeZoneConfidence);
+
+ mTimeZoneConfidence.assertHasBeenSet();
+ mTimeZoneConfidence.assertChangeCount(1);
+ mTimeZoneConfidence.assertLatestEquals(confidence);
}
void commitAllChanges() {
mTimeZoneId.commitLatest();
+ mTimeZoneConfidence.commitLatest();
}
@Override
@@ -1185,6 +1272,16 @@ public class TimeZoneDetectorStrategyImplTest {
public long elapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
+
+ @Override
+ public void addDebugLogEntry(String logMsg) {
+ // No-op for tests
+ }
+
+ @Override
+ public void dumpDebugLog(PrintWriter printWriter) {
+ // No-op for tests
+ }
}
/**
@@ -1193,8 +1290,9 @@ public class TimeZoneDetectorStrategyImplTest {
*/
private class Script {
- Script initializeTimeZoneSetting(String zoneId) {
- mFakeEnvironment.initializeTimeZoneSetting(zoneId);
+ Script initializeTimeZoneSetting(
+ String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
+ mFakeEnvironment.initializeTimeZoneSetting(zoneId, timeZoneConfidence);
return this;
}
@@ -1293,25 +1391,20 @@ public class TimeZoneDetectorStrategyImplTest {
}
Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
- mFakeEnvironment.assertTimeZoneChangedTo(
- suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
- mFakeEnvironment.commitAllChanges();
+ verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
return this;
}
Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
- mFakeEnvironment.assertTimeZoneChangedTo(
- suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
- mFakeEnvironment.commitAllChanges();
+ verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
return this;
}
Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
- mFakeEnvironment.assertTimeZoneChangedTo(
+ verifyTimeZoneChangedAndReset(
suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH);
- mFakeEnvironment.commitAllChanges();
return this;
}