summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Fuller <nfuller@google.com>2022-05-10 11:44:09 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2022-05-10 11:44:09 +0000
commit808b9f1b730ed7d046c26d0c11181632379ce570 (patch)
treed95b771870b196fb6637d10f49b6b26d652b89f6
parentfa8aac4629b21feafe195a88006fe17cbcc7bb08 (diff)
parent2ff5ec1aadb1d2ee8fc6e616bc2e6ba6189dcf39 (diff)
downloadbase-808b9f1b730ed7d046c26d0c11181632379ce570.tar.gz
Merge "Add a Y2038 check into the time_detector"
-rw-r--r--core/java/android/app/time/ExternalTimeSuggestion.java64
-rw-r--r--core/java/android/app/timedetector/GnssTimeSuggestion.java71
-rw-r--r--core/java/android/app/timedetector/ManualTimeSuggestion.java69
-rw-r--r--core/java/android/app/timedetector/NetworkTimeSuggestion.java78
-rw-r--r--core/java/android/app/timedetector/TelephonyTimeSuggestion.java57
-rw-r--r--core/java/android/app/timedetector/TimeDetector.java30
-rw-r--r--core/java/android/app/timedetector/TimeSuggestionHelper.java209
-rw-r--r--core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java63
-rw-r--r--core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java34
-rw-r--r--core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java34
-rw-r--r--core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java34
-rw-r--r--core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java43
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/ShellCommandTestSupport.java6
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--services/core/java/com/android/server/timedetector/EnvironmentImpl.java6
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java85
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java58
18 files changed, 805 insertions, 162 deletions
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index a7c0e5c79607..a7828ab4c9dc 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -19,15 +19,14 @@ package android.app.time;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.timedetector.TimeSuggestionHelper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
@@ -75,7 +74,9 @@ public final class ExternalTimeSuggestion implements Parcelable {
public static final @NonNull Creator<ExternalTimeSuggestion> CREATOR =
new Creator<ExternalTimeSuggestion>() {
public ExternalTimeSuggestion createFromParcel(Parcel in) {
- return ExternalTimeSuggestion.createFromParcel(in);
+ TimeSuggestionHelper helper = TimeSuggestionHelper.handleCreateFromParcel(
+ ExternalTimeSuggestion.class, in);
+ return new ExternalTimeSuggestion(helper);
}
public ExternalTimeSuggestion[] newArray(int size) {
@@ -83,10 +84,7 @@ public final class ExternalTimeSuggestion implements Parcelable {
}
};
- @NonNull
- private final TimestampedValue<Long> mUnixEpochTime;
- @Nullable
- private ArrayList<String> mDebugInfo;
+ @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
/**
* Creates a time suggestion cross-referenced to the elapsed realtime clock. See {@link
@@ -98,17 +96,12 @@ public final class ExternalTimeSuggestion implements Parcelable {
*/
public ExternalTimeSuggestion(@ElapsedRealtimeLong long elapsedRealtimeMillis,
@CurrentTimeMillisLong long suggestionMillis) {
- mUnixEpochTime = new TimestampedValue(elapsedRealtimeMillis, suggestionMillis);
+ mTimeSuggestionHelper = new TimeSuggestionHelper(ExternalTimeSuggestion.class,
+ new TimestampedValue<>(elapsedRealtimeMillis, suggestionMillis));
}
- private static ExternalTimeSuggestion createFromParcel(Parcel in) {
- TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
- ExternalTimeSuggestion suggestion =
- new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue());
- @SuppressWarnings("unchecked")
- ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
- suggestion.mDebugInfo = debugInfo;
- return suggestion;
+ private ExternalTimeSuggestion(@NonNull TimeSuggestionHelper helper) {
+ mTimeSuggestionHelper = Objects.requireNonNull(helper);
}
@Override
@@ -118,8 +111,7 @@ public final class ExternalTimeSuggestion implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mUnixEpochTime, 0);
- dest.writeList(mDebugInfo);
+ mTimeSuggestionHelper.handleWriteToParcel(dest, flags);
}
/**
@@ -127,7 +119,7 @@ public final class ExternalTimeSuggestion implements Parcelable {
*/
@NonNull
public TimestampedValue<Long> getUnixEpochTime() {
- return mUnixEpochTime;
+ return mTimeSuggestionHelper.getUnixEpochTime();
}
/**
@@ -135,9 +127,7 @@ public final class ExternalTimeSuggestion implements Parcelable {
*/
@NonNull
public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList()
- : Collections.unmodifiableList(mDebugInfo);
+ return mTimeSuggestionHelper.getDebugInfo();
}
/**
@@ -146,10 +136,7 @@ public final class ExternalTimeSuggestion implements Parcelable {
* #equals(Object)} and {@link #hashCode()}.
*/
public void addDebugInfo(@NonNull String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
+ mTimeSuggestionHelper.addDebugInfo(debugInfos);
}
@Override
@@ -161,18 +148,29 @@ public final class ExternalTimeSuggestion implements Parcelable {
return false;
}
ExternalTimeSuggestion that = (ExternalTimeSuggestion) o;
- return Objects.equals(mUnixEpochTime, that.mUnixEpochTime);
+ return mTimeSuggestionHelper.handleEquals(that.mTimeSuggestionHelper);
}
@Override
public int hashCode() {
- return Objects.hash(mUnixEpochTime);
+ return mTimeSuggestionHelper.hashCode();
}
@Override
public String toString() {
- return "ExternalTimeSuggestion{" + "mUnixEpochTime=" + mUnixEpochTime
- + ", mDebugInfo=" + mDebugInfo
- + '}';
+ return mTimeSuggestionHelper.handleToString();
+ }
+
+ /** @hide */
+ public static ExternalTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ return new ExternalTimeSuggestion(
+ TimeSuggestionHelper.handleParseCommandLineArg(ExternalTimeSuggestion.class, cmd));
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ TimeSuggestionHelper.handlePrintCommandLineOpts(
+ pw, "External", ExternalTimeSuggestion.class);
}
}
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java
index 34f4565fb410..3531b19d0f65 100644
--- a/core/java/android/app/timedetector/GnssTimeSuggestion.java
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java
@@ -17,30 +17,19 @@
package android.app.timedetector;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
/**
* A time signal from a GNSS source.
*
- * <p>{@code unixEpochTime} is the suggested time. The {@code unixEpochTime.value} is the number of
- * milliseconds elapsed since 1/1/1970 00:00:00 UTC according to the Unix time system. The {@code
- * unixEpochTime.referenceTimeMillis} is the value of the elapsed realtime clock when the {@code
- * unixEpochTime.value} was established. Note that the elapsed realtime clock is considered accurate
- * but it is volatile, so time suggestions cannot be persisted across device resets.
- *
- * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to
- * record why the suggestion exists and how it was entered. This information exists only to aid in
- * debugging and therefore is used by {@link #toString()}, but it is not for use in detection
- * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
+ * <p>See {@link TimeSuggestionHelper} for property information.
*
* @hide
*/
@@ -49,7 +38,9 @@ public final class GnssTimeSuggestion implements Parcelable {
public static final @NonNull Creator<GnssTimeSuggestion> CREATOR =
new Creator<GnssTimeSuggestion>() {
public GnssTimeSuggestion createFromParcel(Parcel in) {
- return GnssTimeSuggestion.createFromParcel(in);
+ TimeSuggestionHelper helper = TimeSuggestionHelper.handleCreateFromParcel(
+ GnssTimeSuggestion.class, in);
+ return new GnssTimeSuggestion(helper);
}
public GnssTimeSuggestion[] newArray(int size) {
@@ -57,21 +48,14 @@ public final class GnssTimeSuggestion implements Parcelable {
}
};
- @NonNull private final TimestampedValue<Long> mUnixEpochTime;
- @Nullable private ArrayList<String> mDebugInfo;
+ @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
public GnssTimeSuggestion(@NonNull TimestampedValue<Long> unixEpochTime) {
- mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
- Objects.requireNonNull(unixEpochTime.getValue());
+ mTimeSuggestionHelper = new TimeSuggestionHelper(GnssTimeSuggestion.class, unixEpochTime);
}
- private static GnssTimeSuggestion createFromParcel(Parcel in) {
- TimestampedValue<Long> unixEpochTime = in.readParcelable(null /* classLoader */);
- GnssTimeSuggestion suggestion = new GnssTimeSuggestion(unixEpochTime);
- @SuppressWarnings("unchecked")
- ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
- suggestion.mDebugInfo = debugInfo;
- return suggestion;
+ private GnssTimeSuggestion(@NonNull TimeSuggestionHelper helper) {
+ mTimeSuggestionHelper = Objects.requireNonNull(helper);
}
@Override
@@ -81,19 +65,17 @@ public final class GnssTimeSuggestion implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mUnixEpochTime, 0);
- dest.writeList(mDebugInfo);
+ mTimeSuggestionHelper.handleWriteToParcel(dest, flags);
}
@NonNull
public TimestampedValue<Long> getUnixEpochTime() {
- return mUnixEpochTime;
+ return mTimeSuggestionHelper.getUnixEpochTime();
}
@NonNull
public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ return mTimeSuggestionHelper.getDebugInfo();
}
/**
@@ -102,10 +84,7 @@ public final class GnssTimeSuggestion implements Parcelable {
* {@link #equals(Object)} and {@link #hashCode()}.
*/
public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
+ mTimeSuggestionHelper.addDebugInfo(debugInfos);
}
@Override
@@ -117,19 +96,29 @@ public final class GnssTimeSuggestion implements Parcelable {
return false;
}
GnssTimeSuggestion that = (GnssTimeSuggestion) o;
- return Objects.equals(mUnixEpochTime, that.mUnixEpochTime);
+ return mTimeSuggestionHelper.handleEquals(that.mTimeSuggestionHelper);
}
@Override
public int hashCode() {
- return Objects.hash(mUnixEpochTime);
+ return mTimeSuggestionHelper.hashCode();
}
@Override
public String toString() {
- return "GnssTimeSuggestion{"
- + "mUnixEpochTime=" + mUnixEpochTime
- + ", mDebugInfo=" + mDebugInfo
- + '}';
+ return mTimeSuggestionHelper.handleToString();
+ }
+
+ /** Parses command line args to create a {@link GnssTimeSuggestion}. */
+ public static GnssTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ TimeSuggestionHelper suggestionHelper =
+ TimeSuggestionHelper.handleParseCommandLineArg(GnssTimeSuggestion.class, cmd);
+ return new GnssTimeSuggestion(suggestionHelper);
+ }
+
+ /** Prints the command line args needed to create a {@link GnssTimeSuggestion}. */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ TimeSuggestionHelper.handlePrintCommandLineOpts(pw, "GNSS", GnssTimeSuggestion.class);
}
}
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 76db33b1c32b..b447799eb84c 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -20,27 +20,17 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
/**
* A time signal from a manual (user provided) source.
*
- * <p>{@code unixEpochTime} is the suggested time. The {@code unixEpochTime.value} is the number of
- * milliseconds elapsed since 1/1/1970 00:00:00 UTC. The {@code unixEpochTime.referenceTimeMillis}
- * is the value of the elapsed realtime clock when the {@code unixEpochTime.value} was established.
- * Note that the elapsed realtime clock is considered accurate but it is volatile, so time
- * suggestions cannot be persisted across device resets.
- *
- * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to
- * record why the suggestion exists and how it was entered. This information exists only to aid in
- * debugging and therefore is used by {@link #toString()}, but it is not for use in detection
- * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
+ * <p>See {@link TimeSuggestionHelper} for property information.
*
* @hide
*/
@@ -49,7 +39,9 @@ public final class ManualTimeSuggestion implements Parcelable {
public static final @NonNull Creator<ManualTimeSuggestion> CREATOR =
new Creator<ManualTimeSuggestion>() {
public ManualTimeSuggestion createFromParcel(Parcel in) {
- return ManualTimeSuggestion.createFromParcel(in);
+ TimeSuggestionHelper helper = TimeSuggestionHelper.handleCreateFromParcel(
+ ManualTimeSuggestion.class, in);
+ return new ManualTimeSuggestion(helper);
}
public ManualTimeSuggestion[] newArray(int size) {
@@ -57,21 +49,14 @@ public final class ManualTimeSuggestion implements Parcelable {
}
};
- @NonNull private final TimestampedValue<Long> mUnixEpochTime;
- @Nullable private ArrayList<String> mDebugInfo;
+ @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
public ManualTimeSuggestion(@NonNull TimestampedValue<Long> unixEpochTime) {
- mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
- Objects.requireNonNull(unixEpochTime.getValue());
+ mTimeSuggestionHelper = new TimeSuggestionHelper(ManualTimeSuggestion.class, unixEpochTime);
}
- private static ManualTimeSuggestion createFromParcel(Parcel in) {
- TimestampedValue<Long> unixEpochTime = in.readParcelable(null /* classLoader */);
- ManualTimeSuggestion suggestion = new ManualTimeSuggestion(unixEpochTime);
- @SuppressWarnings("unchecked")
- ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
- suggestion.mDebugInfo = debugInfo;
- return suggestion;
+ private ManualTimeSuggestion(@NonNull TimeSuggestionHelper helper) {
+ mTimeSuggestionHelper = Objects.requireNonNull(helper);
}
@Override
@@ -81,19 +66,17 @@ public final class ManualTimeSuggestion implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mUnixEpochTime, 0);
- dest.writeList(mDebugInfo);
+ mTimeSuggestionHelper.handleWriteToParcel(dest, flags);
}
@NonNull
public TimestampedValue<Long> getUnixEpochTime() {
- return mUnixEpochTime;
+ return mTimeSuggestionHelper.getUnixEpochTime();
}
@NonNull
public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ return mTimeSuggestionHelper.getDebugInfo();
}
/**
@@ -102,10 +85,7 @@ public final class ManualTimeSuggestion implements Parcelable {
* {@link #equals(Object)} and {@link #hashCode()}.
*/
public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
+ mTimeSuggestionHelper.addDebugInfo(debugInfos);
}
@Override
@@ -117,19 +97,28 @@ public final class ManualTimeSuggestion implements Parcelable {
return false;
}
ManualTimeSuggestion that = (ManualTimeSuggestion) o;
- return Objects.equals(mUnixEpochTime, that.mUnixEpochTime);
+ return mTimeSuggestionHelper.handleEquals(that.mTimeSuggestionHelper);
}
@Override
public int hashCode() {
- return Objects.hash(mUnixEpochTime);
+ return mTimeSuggestionHelper.hashCode();
}
@Override
public String toString() {
- return "ManualTimeSuggestion{"
- + "mUnixEpochTime=" + mUnixEpochTime
- + ", mDebugInfo=" + mDebugInfo
- + '}';
+ return mTimeSuggestionHelper.handleToString();
+ }
+
+ /** @hide */
+ public static ManualTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ return new ManualTimeSuggestion(
+ TimeSuggestionHelper.handleParseCommandLineArg(ManualTimeSuggestion.class, cmd));
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ TimeSuggestionHelper.handlePrintCommandLineOpts(pw, "Manual", ManualTimeSuggestion.class);
}
}
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index e22f1d6ea8be..e93c75cb0a0c 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -17,31 +17,19 @@
package android.app.timedetector;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
/**
* A time signal from a network time source like NTP.
*
- * <p>{@code unixEpochTime} contains the suggested time. The {@code unixEpochTime.value} is the
- * number of milliseconds elapsed since 1/1/1970 00:00:00 UTC according to the Unix time system.
- * The {@code unixEpochTime.referenceTimeMillis} is the value of the elapsed realtime clock when
- * the {@code unixEpochTime.value} was established. Note that the elapsed realtime clock is
- * considered accurate but it is volatile, so time suggestions cannot be persisted across device
- * resets.
- *
- * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to
- * record why the suggestion exists and how it was determined. This information exists only to aid
- * in debugging and therefore is used by {@link #toString()}, but it is not for use in detection
- * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
+ * <p>See {@link TimeSuggestionHelper} for property information.
*
* @hide
*/
@@ -50,7 +38,9 @@ public final class NetworkTimeSuggestion implements Parcelable {
public static final @NonNull Creator<NetworkTimeSuggestion> CREATOR =
new Creator<NetworkTimeSuggestion>() {
public NetworkTimeSuggestion createFromParcel(Parcel in) {
- return NetworkTimeSuggestion.createFromParcel(in);
+ TimeSuggestionHelper helper = TimeSuggestionHelper.handleCreateFromParcel(
+ NetworkTimeSuggestion.class, in);
+ return new NetworkTimeSuggestion(helper);
}
public NetworkTimeSuggestion[] newArray(int size) {
@@ -58,21 +48,15 @@ public final class NetworkTimeSuggestion implements Parcelable {
}
};
- @NonNull private final TimestampedValue<Long> mUnixEpochTime;
- @Nullable private ArrayList<String> mDebugInfo;
+ @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
public NetworkTimeSuggestion(@NonNull TimestampedValue<Long> unixEpochTime) {
- mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
- Objects.requireNonNull(unixEpochTime.getValue());
+ mTimeSuggestionHelper = new TimeSuggestionHelper(
+ NetworkTimeSuggestion.class, unixEpochTime);
}
- private static NetworkTimeSuggestion createFromParcel(Parcel in) {
- TimestampedValue<Long> unixEpochTime = in.readParcelable(null /* classLoader */);
- NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(unixEpochTime);
- @SuppressWarnings("unchecked")
- ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
- suggestion.mDebugInfo = debugInfo;
- return suggestion;
+ private NetworkTimeSuggestion(@NonNull TimeSuggestionHelper helper) {
+ mTimeSuggestionHelper = Objects.requireNonNull(helper);
}
@Override
@@ -82,35 +66,30 @@ public final class NetworkTimeSuggestion implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mUnixEpochTime, 0);
- dest.writeList(mDebugInfo);
+ mTimeSuggestionHelper.handleWriteToParcel(dest, flags);
}
@NonNull
public TimestampedValue<Long> getUnixEpochTime() {
- return mUnixEpochTime;
+ return mTimeSuggestionHelper.getUnixEpochTime();
}
@NonNull
public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ return mTimeSuggestionHelper.getDebugInfo();
}
/**
* Associates information with the instance that can be useful for debugging / logging. The
- * information is present in {@link #toString()} but is not considered for
- * {@link #equals(Object)} and {@link #hashCode()}.
+ * information is present in {@link #toString()} but is not considered for {@link
+ * #equals(Object)} and {@link #hashCode()}.
*/
public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
+ mTimeSuggestionHelper.addDebugInfo(debugInfos);
}
@Override
- public boolean equals(@Nullable Object o) {
+ public boolean equals(Object o) {
if (this == o) {
return true;
}
@@ -118,19 +97,28 @@ public final class NetworkTimeSuggestion implements Parcelable {
return false;
}
NetworkTimeSuggestion that = (NetworkTimeSuggestion) o;
- return Objects.equals(mUnixEpochTime, that.mUnixEpochTime);
+ return mTimeSuggestionHelper.handleEquals(that.mTimeSuggestionHelper);
}
@Override
public int hashCode() {
- return Objects.hash(mUnixEpochTime);
+ return mTimeSuggestionHelper.hashCode();
}
@Override
public String toString() {
- return "NetworkTimeSuggestion{"
- + "mUnixEpochTime=" + mUnixEpochTime
- + ", mDebugInfo=" + mDebugInfo
- + '}';
+ return mTimeSuggestionHelper.handleToString();
+ }
+
+ /** @hide */
+ public static NetworkTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ return new NetworkTimeSuggestion(
+ TimeSuggestionHelper.handleParseCommandLineArg(NetworkTimeSuggestion.class, cmd));
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ TimeSuggestionHelper.handlePrintCommandLineOpts(pw, "Network", NetworkTimeSuggestion.class);
}
}
diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 4ff75174ab57..6f204d661004 100644
--- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -20,8 +20,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -88,6 +90,61 @@ public final class TelephonyTimeSuggestion implements Parcelable {
return suggestion;
}
+ /** @hide */
+ public static TelephonyTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ Integer slotIndex = null;
+ Long referenceTimeMillis = null;
+ Long unixEpochTimeMillis = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--slot_index": {
+ slotIndex = Integer.parseInt(cmd.getNextArgRequired());
+ break;
+ }
+ case "--reference_time": {
+ referenceTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ case "--unix_epoch_time": {
+ unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (slotIndex == null) {
+ throw new IllegalArgumentException("No slotIndex specified.");
+ }
+ if (referenceTimeMillis == null) {
+ throw new IllegalArgumentException("No referenceTimeMillis specified.");
+ }
+ if (unixEpochTimeMillis == null) {
+ throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+ }
+
+ TimestampedValue<Long> timeSignal =
+ new TimestampedValue<>(referenceTimeMillis, unixEpochTimeMillis);
+ Builder builder = new Builder(slotIndex)
+ .setUnixEpochTime(timeSignal)
+ .addDebugInfo("Command line injection");
+ return builder.build();
+ }
+
+ /** @hide */
+ public static void printCommandLineOpts(PrintWriter pw) {
+ pw.println("Telephony suggestion options:");
+ pw.println(" --slot_index <number>");
+ pw.println(" --reference_time <elapsed realtime millis>");
+ pw.println(" --unix_epoch_time <Unix epoch time millis>");
+ pw.println();
+ pw.println("See " + TelephonyTimeSuggestion.class.getName() + " for more information");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index a3562301cde7..f0d777611620 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -44,6 +44,36 @@ public interface TimeDetector {
String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
/**
+ * A shell command that injects a manual time suggestion.
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_MANUAL_TIME = "suggest_manual_time";
+
+ /**
+ * A shell command that injects a telephony time suggestion.
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME = "suggest_telephony_time";
+
+ /**
+ * A shell command that injects a network time suggestion.
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_NETWORK_TIME = "suggest_network_time";
+
+ /**
+ * A shell command that injects a GNSS time suggestion.
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_GNSS_TIME = "suggest_gnss_time";
+
+ /**
+ * A shell command that injects a external time suggestion.
+ * @hide
+ */
+ String SHELL_COMMAND_SUGGEST_EXTERNAL_TIME = "suggest_external_time";
+
+ /**
* A shared utility method to create a {@link ManualTimeSuggestion}.
*
* @hide
diff --git a/core/java/android/app/timedetector/TimeSuggestionHelper.java b/core/java/android/app/timedetector/TimeSuggestionHelper.java
new file mode 100644
index 000000000000..9b99be61b3c8
--- /dev/null
+++ b/core/java/android/app/timedetector/TimeSuggestionHelper.java
@@ -0,0 +1,209 @@
+/*
+ * 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.timedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.ShellCommand;
+import android.os.TimestampedValue;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A delegate class to support time suggestion classes that could diverge in the future. This class
+ * exists purely for code re-use and provides support methods. It avoids class inheritance
+ * deliberately to allow each suggestion to evolve in different directions later without affecting
+ * SDK APIs.
+ *
+ * <p>{@code unixEpochTime} is the suggested time. The {@code unixEpochTime.value} is the number of
+ * milliseconds elapsed since 1/1/1970 00:00:00 UTC according to the Unix time system. The {@code
+ * unixEpochTime.referenceTimeMillis} is the value of the elapsed realtime clock when the {@code
+ * unixEpochTime.value} was established. Note that the elapsed realtime clock is considered accurate
+ * but it is volatile, so time suggestions cannot be persisted across device resets.
+ *
+ * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to
+ * record why the suggestion exists and how it was entered. This information exists only to aid in
+ * debugging and therefore is used by {@link #toString()}, but it is not for use in detection
+ * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
+ *
+ * @hide
+ */
+public final class TimeSuggestionHelper {
+
+ @NonNull private final Class<?> mHelpedClass;
+ @NonNull private final TimestampedValue<Long> mUnixEpochTime;
+ @Nullable private ArrayList<String> mDebugInfo;
+
+ /** Creates a helper for the specified class, containing the supplied properties. */
+ public TimeSuggestionHelper(@NonNull Class<?> helpedClass,
+ @NonNull TimestampedValue<Long> unixEpochTime) {
+ mHelpedClass = Objects.requireNonNull(helpedClass);
+ mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
+ Objects.requireNonNull(unixEpochTime.getValue());
+ }
+
+ /** See {@link TimeSuggestionHelper} for property details. */
+ @NonNull
+ public TimestampedValue<Long> getUnixEpochTime() {
+ return mUnixEpochTime;
+ }
+
+ /** See {@link TimeSuggestionHelper} for information about {@code debugInfo}. */
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging.
+ *
+ * <p>See {@link TimeSuggestionHelper} for more information about {@code debugInfo}.
+ */
+ public void addDebugInfo(@NonNull String debugInfo) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.add(debugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ addDebugInfo(Arrays.asList(debugInfos));
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging.
+ *
+ * <p>See {@link TimeSuggestionHelper} for more information about {@code debugInfo}.
+ */
+ public void addDebugInfo(@NonNull List<String> debugInfo) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>(debugInfo.size());
+ }
+ mDebugInfo.addAll(debugInfo);
+ }
+
+ /**
+ * Implemented in case users call this insteam of {@link #handleEquals(TimeSuggestionHelper)}.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeSuggestionHelper that = (TimeSuggestionHelper) o;
+ return handleEquals(that);
+ }
+
+ /** Used to implement {@link Object#equals(Object)}. */
+ public boolean handleEquals(TimeSuggestionHelper o) {
+ return Objects.equals(mHelpedClass, o.mHelpedClass)
+ && Objects.equals(mUnixEpochTime, o.mUnixEpochTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUnixEpochTime);
+ }
+
+ /** Used to implement {@link Object#toString()}. */
+ public String handleToString() {
+ return mHelpedClass.getSimpleName() + "{"
+ + "mUnixEpochTime=" + mUnixEpochTime
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ /** Constructs a helper with suggestion state from a Parcel. */
+ public static TimeSuggestionHelper handleCreateFromParcel(@NonNull Class<?> helpedClass,
+ @NonNull Parcel in) {
+ @SuppressWarnings("unchecked")
+ TimestampedValue<Long> unixEpochTime = in.readParcelable(
+ null /* classLoader */, TimestampedValue.class);
+ TimeSuggestionHelper suggestionHelper =
+ new TimeSuggestionHelper(helpedClass, unixEpochTime);
+ suggestionHelper.mDebugInfo = in.readArrayList(null /* classLoader */, String.class);
+ return suggestionHelper;
+ }
+
+ /** Writes the helper suggestion state to a Parcel. */
+ public void handleWriteToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mUnixEpochTime, 0);
+ dest.writeList(mDebugInfo);
+ }
+
+ /** Parses command line args to create a {@link TimeSuggestionHelper}. */
+ public static TimeSuggestionHelper handleParseCommandLineArg(
+ @NonNull Class<?> helpedClass, @NonNull ShellCommand cmd)
+ throws IllegalArgumentException {
+ Long referenceTimeMillis = null;
+ Long unixEpochTimeMillis = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--reference_time": {
+ referenceTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ case "--unix_epoch_time": {
+ unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (referenceTimeMillis == null) {
+ throw new IllegalArgumentException("No referenceTimeMillis specified.");
+ }
+ if (unixEpochTimeMillis == null) {
+ throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+ }
+
+ TimestampedValue<Long> timeSignal =
+ new TimestampedValue<>(referenceTimeMillis, unixEpochTimeMillis);
+ TimeSuggestionHelper suggestionHelper = new TimeSuggestionHelper(helpedClass, timeSignal);
+ suggestionHelper.addDebugInfo("Command line injection");
+ return suggestionHelper;
+ }
+
+ /** Prints the command line args needed to create a {@link TimeSuggestionHelper}. */
+ public static void handlePrintCommandLineOpts(
+ @NonNull PrintWriter pw, @NonNull String typeName, @NonNull Class<?> clazz) {
+ pw.printf("%s suggestion options:\n", typeName);
+ pw.println(" --reference_time <elapsed realtime millis>");
+ pw.println(" --unix_epoch_time <Unix epoch time millis>");
+ pw.println();
+ pw.println("See " + clazz.getName() + " for more information");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
new file mode 100644
index 000000000000..90b33058d4e8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.ShellCommand;
+
+import org.junit.Test;
+
+/**
+ * Tests for non-SDK methods on {@link ExternalTimeSuggestion}.
+ * Also see {@link android.app.time.cts.ExternalTimeSuggestionTest}
+ */
+public class ExternalTimeSuggestionTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noReferenceTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321");
+ ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345");
+ ExternalTimeSuggestion expectedSuggestion = new ExternalTimeSuggestion(54321L, 12345L);
+ ExternalTimeSuggestion actualSuggestion =
+ ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+ ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java
index e248010319e1..af403a20ae7b 100644
--- a/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java
@@ -18,10 +18,12 @@ package android.app.timedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
import org.junit.Test;
@@ -63,4 +65,36 @@ public class GnssTimeSuggestionTest {
GnssTimeSuggestion rtSuggestion = roundTripParcelable(suggestion);
assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noReferenceTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321");
+ GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345");
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+ GnssTimeSuggestion expectedSuggestion = new GnssTimeSuggestion(timeSignal);
+ GnssTimeSuggestion actualSuggestion =
+ GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+ GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
}
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 750ffa1c9a54..94218cdea0ed 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -18,10 +18,12 @@ package android.app.timedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
import org.junit.Test;
@@ -63,4 +65,36 @@ public class ManualTimeSuggestionTest {
ManualTimeSuggestion rtSuggestion = roundTripParcelable(suggestion);
assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noReferenceTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321");
+ ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345");
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+ ManualTimeSuggestion expectedSuggestion = new ManualTimeSuggestion(timeSignal);
+ ManualTimeSuggestion actualSuggestion =
+ ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+ ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
}
diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
index b88c36f20bc6..0e09dd3e9aab 100644
--- a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
@@ -18,10 +18,12 @@ package android.app.timedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
import org.junit.Test;
@@ -63,4 +65,36 @@ public class NetworkTimeSuggestionTest {
NetworkTimeSuggestion rtSuggestion = roundTripParcelable(suggestion);
assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noReferenceTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--unix_epoch_time 12345");
+ NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321");
+ NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345");
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+ NetworkTimeSuggestion expectedSuggestion = new NetworkTimeSuggestion(timeSignal);
+ NetworkTimeSuggestion actualSuggestion =
+ NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+ NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
}
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index cc7557977e80..bb995a852637 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -18,10 +18,12 @@ package android.app.timedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.ShellCommand;
import android.os.TimestampedValue;
import org.junit.Test;
@@ -95,4 +97,45 @@ public class TelephonyTimeSuggestionTest {
assertEquals(suggestion1.getDebugInfo(), rtSuggestion1.getDebugInfo());
}
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noSlotIndex() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--reference_time 54321 --unix_epoch_time 12345");
+ TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noReferenceTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--slot_index 0 --unix_epoch_time 12345");
+ TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noUnixEpochTime() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--slot_index 0 --reference_time 54321");
+ TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_validSuggestion() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--slot_index 0 --reference_time 54321 --unix_epoch_time 12345");
+ TelephonyTimeSuggestion expectedSuggestion =
+ new TelephonyTimeSuggestion.Builder(0)
+ .setUnixEpochTime(new TimestampedValue<>(54321L, 12345L))
+ .build();
+ TelephonyTimeSuggestion actualSuggestion =
+ TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ "--slot_index 0 --reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+ TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
+ }
}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ShellCommandTestSupport.java b/core/tests/coretests/src/android/app/timezonedetector/ShellCommandTestSupport.java
index 8d8290c7bdc9..4efaed11168e 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ShellCommandTestSupport.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ShellCommandTestSupport.java
@@ -26,14 +26,14 @@ import java.util.Arrays;
import java.util.List;
/** Utility methods related to {@link ShellCommand} objects used in several tests. */
-final class ShellCommandTestSupport {
+public final class ShellCommandTestSupport {
private ShellCommandTestSupport() {}
- static ShellCommand createShellCommandWithArgsAndOptions(String argsWithSpaces) {
+ public static ShellCommand createShellCommandWithArgsAndOptions(String argsWithSpaces) {
return createShellCommandWithArgsAndOptions(Arrays.asList(argsWithSpaces.split(" ")));
}
- static ShellCommand createShellCommandWithArgsAndOptions(List<String> args) {
+ public static ShellCommand createShellCommandWithArgsAndOptions(List<String> args) {
ShellCommand command = mock(ShellCommand.class);
class ArgProvider {
private int mCount;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ff4c2c63f8b4..c4d4261ba080 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -478,6 +478,12 @@
<uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
<uses-permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" />
+ <!-- Permissions used for manual testing of time detection behavior. -->
+ <uses-permission android:name="android.permission.SUGGEST_MANUAL_TIME" />
+ <uses-permission android:name="android.permission.SUGGEST_TELEPHONY_TIME" />
+ <uses-permission android:name="android.permission.SUGGEST_NETWORK_TIME" />
+ <uses-permission android:name="android.permission.SUGGEST_GNSS_TIME" />
+
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 7649958fe6c9..9d263516db3a 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -22,6 +22,7 @@ import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -166,6 +167,11 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
mWakeLock.release();
}
+ @Override
+ public boolean deviceHasY2038Issue() {
+ return Build.SUPPORTED_32_BIT_ABIS.length > 0;
+ }
+
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index 721986bc6e93..cc5e6fe433fa 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -17,14 +17,26 @@ package com.android.server.timedetector;
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_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;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_NETWORK_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE;
import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE;
+import android.app.time.ExternalTimeSuggestion;
+import android.app.timedetector.GnssTimeSuggestion;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.ShellCommand;
import java.io.PrintWriter;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
/** Implements the shell command interface for {@link TimeDetectorService}. */
class TimeDetectorShellCommand extends ShellCommand {
@@ -44,6 +56,16 @@ class TimeDetectorShellCommand extends ShellCommand {
switch (cmd) {
case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED:
return runIsAutoDetectionEnabled();
+ case SHELL_COMMAND_SUGGEST_MANUAL_TIME:
+ return runSuggestManualTime();
+ case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME:
+ return runSuggestTelephonyTime();
+ case SHELL_COMMAND_SUGGEST_NETWORK_TIME:
+ return runSuggestNetworkTime();
+ case SHELL_COMMAND_SUGGEST_GNSS_TIME:
+ return runSuggestGnssTime();
+ case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
+ return runSuggestExternalTime();
default: {
return handleDefaultCommands(cmd);
}
@@ -59,6 +81,53 @@ class TimeDetectorShellCommand extends ShellCommand {
return 0;
}
+ private int runSuggestManualTime() {
+ return runSuggestTime(
+ () -> ManualTimeSuggestion.parseCommandLineArg(this),
+ mInterface::suggestManualTime);
+ }
+
+ private int runSuggestTelephonyTime() {
+ return runSuggestTime(
+ () -> TelephonyTimeSuggestion.parseCommandLineArg(this),
+ mInterface::suggestTelephonyTime);
+ }
+
+ private int runSuggestNetworkTime() {
+ return runSuggestTime(
+ () -> NetworkTimeSuggestion.parseCommandLineArg(this),
+ mInterface::suggestNetworkTime);
+ }
+
+ private int runSuggestGnssTime() {
+ return runSuggestTime(
+ () -> GnssTimeSuggestion.parseCommandLineArg(this),
+ mInterface::suggestGnssTime);
+ }
+
+ private int runSuggestExternalTime() {
+ return runSuggestTime(
+ () -> ExternalTimeSuggestion.parseCommandLineArg(this),
+ mInterface::suggestExternalTime);
+ }
+
+ private <T> int runSuggestTime(Supplier<T> suggestionParser, Consumer<T> invoker) {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ T suggestion = suggestionParser.get();
+ if (suggestion == null) {
+ pw.println("Error: suggestion not specified");
+ return 1;
+ }
+ invoker.accept(suggestion);
+ pw.println("Suggestion " + suggestion + " injected.");
+ return 0;
+ } catch (RuntimeException e) {
+ pw.println(e);
+ return 1;
+ }
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -68,6 +137,22 @@ class TimeDetectorShellCommand extends ShellCommand {
pw.printf(" %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
pw.printf(" Prints true/false according to the automatic time detection setting.\n");
pw.println();
+ pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME);
+ pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME);
+ pw.printf(" %s <network suggestion opts>\n", SHELL_COMMAND_SUGGEST_NETWORK_TIME);
+ pw.printf(" %s <gnss suggestion opts>\n", SHELL_COMMAND_SUGGEST_GNSS_TIME);
+ pw.printf(" %s <external suggestion opts>\n", SHELL_COMMAND_SUGGEST_EXTERNAL_TIME);
+ pw.println();
+ ManualTimeSuggestion.printCommandLineOpts(pw);
+ pw.println();
+ TelephonyTimeSuggestion.printCommandLineOpts(pw);
+ pw.println();
+ NetworkTimeSuggestion.printCommandLineOpts(pw);
+ pw.println();
+ GnssTimeSuggestion.printCommandLineOpts(pw);
+ pw.println();
+ ExternalTimeSuggestion.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/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index ae4d46c387b9..33ab1047c763 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -85,6 +85,9 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
*/
private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
+ /** The value in Unix epoch milliseconds of the Y2038 issue. */
+ private static final long Y2038_LIMIT_IN_MILLIS = 1000L * Integer.MAX_VALUE;
+
/**
* A log that records the decisions / decision metadata that affected the device's system clock
* time. This is logged in bug reports to assist with debugging issues with detection.
@@ -185,6 +188,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
/** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
void releaseWakeLock();
+
+ /**
+ * Returns {@code true} if the device may be at risk of time_t overflow (because bionic
+ * defines time_t as a 32-bit signed integer for 32-bit processes).
+ */
+ boolean deviceHasY2038Issue();
}
static TimeDetectorStrategy create(
@@ -333,6 +342,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
.mapToObj(TimeDetectorStrategy::originToString)
.collect(joining(",", "[", "]"));
ipw.println("mEnvironment.autoOriginPriorities()=" + priorities);
+ ipw.println("mEnvironment.deviceHasY2038Issue()=" + mEnvironment.deviceHasY2038Issue());
ipw.println("Time change log:");
ipw.increaseIndent(); // level 2
@@ -413,6 +423,16 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
+ ", suggestion=" + suggestion);
return false;
}
+
+ if (newUnixEpochTime.getValue() > Y2038_LIMIT_IN_MILLIS
+ && mEnvironment.deviceHasY2038Issue()) {
+ // This check won't prevent a device's system clock exceeding Integer.MAX_VALUE Unix
+ // seconds through the normal passage of time, but it will stop it jumping above 2038
+ // because of a "bad" suggestion. b/204193177
+ Slog.w(LOG_TAG, "Suggested value is above max time supported by this device."
+ + " suggestion=" + suggestion);
+ return false;
+ }
return true;
}
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 2d9903f9cf60..2248ddb9e69a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -1131,6 +1131,49 @@ public class TimeDetectorStrategyImplTest {
.verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
}
+ @Test
+ public void manualY2038SuggestionsAreRejectedOnAffectedDevices() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(false)
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
+ .pokeDeviceHasY2038Issues(true);
+
+ Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
+ ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(y2038IssueTime);
+ mScript.simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
+ public void telephonyY2038SuggestionsAreRejectedOnAffectedDevices() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
+ .pokeDeviceHasY2038Issues(true);
+
+ final int slotIndex = 0;
+ Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
+ TelephonyTimeSuggestion timeSuggestion =
+ mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime);
+ mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
+ public void telephonyY2038SuggestionsAreNotRejectedOnUnaffectedDevices() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
+ .pokeDeviceHasY2038Issues(false);
+
+ final int slotIndex = 0;
+ Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
+ TelephonyTimeSuggestion timeSuggestion =
+ mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime);
+ mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(y2038IssueTime.toEpochMilli());
+ }
+
/**
* A fake implementation of {@link TimeDetectorStrategyImpl.Environment}. Besides tracking
* changes and behaving like the real thing should, it also asserts preconditions.
@@ -1143,6 +1186,7 @@ public class TimeDetectorStrategyImplTest {
private int mSystemClockUpdateThresholdMillis = 2000;
private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY;
private ConfigurationChangeListener mConfigChangeListener;
+ private boolean mDeviceHas2038Issues = false;
// Tracking operations.
private boolean mSystemClockWasSet;
@@ -1208,6 +1252,15 @@ public class TimeDetectorStrategyImplTest {
mWakeLockAcquired = false;
}
+ public void setDeviceHas2038Issues(boolean hasIssues) {
+ mDeviceHas2038Issues = hasIssues;
+ }
+
+ @Override
+ public boolean deviceHasY2038Issue() {
+ return mDeviceHas2038Issues;
+ }
+
// Methods below are for managing the fake's behavior.
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -1304,6 +1357,11 @@ public class TimeDetectorStrategyImplTest {
return this;
}
+ Script pokeDeviceHasY2038Issues(boolean hasIssues) {
+ mFakeEnvironment.setDeviceHas2038Issues(hasIssues);
+ return this;
+ }
+
long peekElapsedRealtimeMillis() {
return mFakeEnvironment.peekElapsedRealtimeMillis();
}