Add a path for GNSS time suggestions
Add a path for GNSS time suggestions to get to the time detector.
Bug: 157265008
Test: atest services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
Test: atest android.app.timedetector
Change-Id: I5cb12b5545652ed885b72a3170940050ce0628a6
Merged-In: I5cb12b5545652ed885b72a3170940050ce0628a6
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.aidl b/core/java/android/app/timedetector/GnssTimeSuggestion.aidl
new file mode 100644
index 0000000..81475ec
--- /dev/null
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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;
+
+parcelable GnssTimeSuggestion;
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java
new file mode 100644
index 0000000..6478a2d
--- /dev/null
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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.Parcelable;
+import android.os.TimestampedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A time signal from a GNSS source.
+ *
+ * <p>{@code utcTime} is the suggested time. The {@code utcTime.value} is the number of milliseconds
+ * elapsed since 1/1/1970 00:00:00 UTC. The {@code utcTime.referenceTimeMillis} is the value of the
+ * elapsed realtime clock when the {@code utcTime.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 GnssTimeSuggestion implements Parcelable {
+
+ public static final @NonNull Creator<GnssTimeSuggestion> CREATOR =
+ new Creator<GnssTimeSuggestion>() {
+ public GnssTimeSuggestion createFromParcel(Parcel in) {
+ return GnssTimeSuggestion.createFromParcel(in);
+ }
+
+ public GnssTimeSuggestion[] newArray(int size) {
+ return new GnssTimeSuggestion[size];
+ }
+ };
+
+ @NonNull private final TimestampedValue<Long> mUtcTime;
+ @Nullable private ArrayList<String> mDebugInfo;
+
+ public GnssTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
+ mUtcTime = Objects.requireNonNull(utcTime);
+ Objects.requireNonNull(utcTime.getValue());
+ }
+
+ private static GnssTimeSuggestion createFromParcel(Parcel in) {
+ TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+ GnssTimeSuggestion suggestion = new GnssTimeSuggestion(utcTime);
+ @SuppressWarnings("unchecked")
+ ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+ suggestion.mDebugInfo = debugInfo;
+ return suggestion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mUtcTime, 0);
+ dest.writeList(mDebugInfo);
+ }
+
+ @NonNull
+ public TimestampedValue<Long> getUtcTime() {
+ return mUtcTime;
+ }
+
+ @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. The
+ * 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));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GnssTimeSuggestion that = (GnssTimeSuggestion) o;
+ return Objects.equals(mUtcTime, that.mUtcTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUtcTime);
+ }
+
+ @Override
+ public String toString() {
+ return "GnssTimeSuggestion{"
+ + "mUtcTime=" + mUtcTime
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 7bea5d7..87e7233 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,6 +16,7 @@
package android.app.timedetector;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -34,6 +35,7 @@
* {@hide}
*/
interface ITimeDetectorService {
+ void suggestGnssTime(in GnssTimeSuggestion timeSuggestion);
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestNetworkTime(in NetworkTimeSuggestion 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 162e182..52016b6 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -71,4 +71,12 @@
*/
@RequiresPermission(android.Manifest.permission.SET_TIME)
void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion);
+
+ /**
+ * Suggests the time according to a gnss time source.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_TIME)
+ void suggestGnssTime(GnssTimeSuggestion timeSuggestion);
}
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index ac02c89..b0aa3c8 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -74,4 +74,16 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public void suggestGnssTime(GnssTimeSuggestion timeSuggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "suggestGnssTime called: " + timeSuggestion);
+ }
+ try {
+ mITimeDetectorService.suggestGnssTime(timeSuggestion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java
new file mode 100644
index 0000000..e248010
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timedetector/GnssTimeSuggestionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 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 static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.TimestampedValue;
+
+import org.junit.Test;
+
+public class GnssTimeSuggestionTest {
+
+ private static final TimestampedValue<Long> ARBITRARY_TIME =
+ new TimestampedValue<>(1111L, 2222L);
+
+ @Test
+ public void testEquals() {
+ GnssTimeSuggestion one = new GnssTimeSuggestion(ARBITRARY_TIME);
+ assertEquals(one, one);
+
+ GnssTimeSuggestion two = new GnssTimeSuggestion(ARBITRARY_TIME);
+ assertEquals(one, two);
+ assertEquals(two, one);
+
+ TimestampedValue<Long> differentTime = new TimestampedValue<>(
+ ARBITRARY_TIME.getReferenceTimeMillis() + 1,
+ ARBITRARY_TIME.getValue());
+ GnssTimeSuggestion three = new GnssTimeSuggestion(differentTime);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+
+ // DebugInfo must not be considered in equals().
+ one.addDebugInfo("Debug info 1");
+ two.addDebugInfo("Debug info 2");
+ assertEquals(one, two);
+ }
+
+ @Test
+ public void testParcelable() {
+ GnssTimeSuggestion suggestion = new GnssTimeSuggestion(ARBITRARY_TIME);
+ assertRoundTripParcelable(suggestion);
+
+ // DebugInfo should also be stored (but is not checked by equals()
+ suggestion.addDebugInfo("This is debug info");
+ GnssTimeSuggestion rtSuggestion = roundTripParcelable(suggestion);
+ assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 6dac6b1..84776ea 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
@@ -122,6 +123,14 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
}
+ @Override
+ public void suggestGnssTime(@NonNull GnssTimeSuggestion timeSignal) {
+ enforceSuggestGnssTimePermission();
+ Objects.requireNonNull(timeSignal);
+
+ mHandler.post(() -> mTimeDetectorStrategy.suggestGnssTime(timeSignal));
+ }
+
/** Internal method for handling the auto time setting being changed. */
@VisibleForTesting
public void handleAutoTimeDetectionChanged() {
@@ -153,4 +162,10 @@
android.Manifest.permission.SET_TIME,
"set time");
}
+
+ private void enforceSuggestGnssTimePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_TIME,
+ "suggest gnss time");
+ }
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index abd4c8c..de6412c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -40,7 +41,7 @@
*/
public interface TimeDetectorStrategy {
- @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK })
+ @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK, ORIGIN_GNSS })
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
@@ -56,6 +57,10 @@
@Origin
int ORIGIN_NETWORK = 3;
+ /** Used when a time value originated from a gnss signal. */
+ @Origin
+ int ORIGIN_GNSS = 4;
+
/** Processes the suggested time from telephony sources. */
void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
@@ -70,6 +75,9 @@
/** Processes the suggested time from network sources. */
void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion);
+ /** Processes the suggested time from gnss sources. */
+ void suggestGnssTime(@NonNull GnssTimeSuggestion timeSuggestion);
+
/**
* Handles the auto-time configuration changing For example, when the auto-time setting is
* toggled on or off.
@@ -102,6 +110,8 @@
return "network";
case ORIGIN_TELEPHONY:
return "telephony";
+ case ORIGIN_GNSS:
+ return "gnss";
default:
throw new IllegalArgumentException("origin=" + origin);
}
@@ -119,6 +129,8 @@
return ORIGIN_NETWORK;
case "telephony":
return ORIGIN_TELEPHONY;
+ case "gnss":
+ return ORIGIN_GNSS;
default:
throw new IllegalArgumentException("originString=" + originString);
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index b3c8c90..6fe3782 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -109,6 +110,10 @@
private final ReferenceWithHistory<NetworkTimeSuggestion> mLastNetworkSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @GuardedBy("this")
+ private final ReferenceWithHistory<GnssTimeSuggestion> mLastGnssSuggestion =
+ new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
/**
* The interface used by the strategy to interact with the surrounding service.
*
@@ -166,6 +171,20 @@
}
@Override
+ public synchronized void suggestGnssTime(@NonNull GnssTimeSuggestion timeSuggestion) {
+ final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
+
+ if (!validateAutoSuggestionTime(newUtcTime, timeSuggestion)) {
+ return;
+ }
+
+ mLastGnssSuggestion.set(timeSuggestion);
+
+ String reason = "GNSS time suggestion received: suggestion=" + timeSuggestion;
+ doAutoTimeDetection(reason);
+ }
+
+ @Override
public synchronized boolean suggestManualTime(@NonNull ManualTimeSuggestion suggestion) {
final TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
@@ -280,6 +299,11 @@
mLastNetworkSuggestion.dump(ipw);
ipw.decreaseIndent(); // level 2
+ ipw.println("Gnss suggestion history:");
+ ipw.increaseIndent(); // level 2
+ mLastGnssSuggestion.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
ipw.decreaseIndent(); // level 1
ipw.flush();
}
@@ -386,6 +410,14 @@
+ ", networkSuggestion=" + networkSuggestion
+ ", detectionReason=" + detectionReason;
}
+ } else if (origin == ORIGIN_GNSS) {
+ GnssTimeSuggestion gnssTimeSuggestion = findLatestValidGnssSuggestion();
+ if (gnssTimeSuggestion != null) {
+ newUtcTime = gnssTimeSuggestion.getUtcTime();
+ cause = "Found good gnss suggestion."
+ + ", gnssTimeSuggestion=" + gnssTimeSuggestion
+ + ", detectionReason=" + detectionReason;
+ }
} else {
Slog.w(LOG_TAG, "Unknown or unsupported origin=" + origin
+ " in " + Arrays.toString(originPriorities)
@@ -527,6 +559,26 @@
return networkSuggestion;
}
+ /** Returns the latest, valid, gnss suggestion. Returns {@code null} if there isn't one. */
+ @GuardedBy("this")
+ @Nullable
+ private GnssTimeSuggestion findLatestValidGnssSuggestion() {
+ GnssTimeSuggestion gnssTimeSuggestion = mLastGnssSuggestion.get();
+ if (gnssTimeSuggestion == null) {
+ // No gnss suggestions received. This is normal if there's no gnss signal.
+ return null;
+ }
+
+ TimestampedValue<Long> utcTime = gnssTimeSuggestion.getUtcTime();
+ long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+ if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
+ // The latest suggestion is not valid, usually due to its age.
+ return null;
+ }
+
+ return gnssTimeSuggestion;
+ }
+
@GuardedBy("this")
private boolean setSystemClockIfRequired(
@Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
@@ -655,6 +707,16 @@
}
/**
+ * Returns the latest valid gnss suggestion. Not intended for general use: it is used during
+ * tests to check strategy behavior.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized GnssTimeSuggestion findLatestValidGnssSuggestionForTests() {
+ return findLatestValidGnssSuggestion();
+ }
+
+ /**
* A method used to inspect state during tests. Not intended for general use.
*/
@VisibleForTesting
@@ -672,6 +734,15 @@
return mLastNetworkSuggestion.get();
}
+ /**
+ * A method used to inspect state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized GnssTimeSuggestion getLatestGnssSuggestion() {
+ return mLastGnssSuggestion.get();
+ }
+
private static boolean validateSuggestionUtcTime(
long elapsedRealtimeMillis, TimestampedValue<Long> utcTime) {
long referenceTimeMillis = utcTime.getReferenceTimeMillis();
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 22addf9..f0b1be5 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -172,6 +173,36 @@
mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion);
}
+ @Test(expected = SecurityException.class)
+ public void testSuggestGnssTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+ GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
+
+ try {
+ mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion);
+ fail();
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME), anyString());
+ }
+ }
+
+ @Test
+ public void testSuggestGnssTime() throws Exception {
+ doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+ GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
+ mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion);
+ mTestHandler.assertTotalMessagesEnqueued(1);
+
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME), anyString());
+
+ mTestHandler.waitForMessagesToBeProcessed();
+ mStubbedTimeDetectorStrategy.verifySuggestGnssTimeCalled(gnssTimeSuggestion);
+ }
+
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
@@ -216,12 +247,18 @@
return new NetworkTimeSuggestion(timeValue);
}
+ private static GnssTimeSuggestion createGnssTimeSuggestion() {
+ TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+ return new GnssTimeSuggestion(timeValue);
+ }
+
private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
// Call tracking.
private TelephonyTimeSuggestion mLastTelephonySuggestion;
private ManualTimeSuggestion mLastManualSuggestion;
private NetworkTimeSuggestion mLastNetworkSuggestion;
+ private GnssTimeSuggestion mLastGnssSuggestion;
private boolean mHandleAutoTimeDetectionChangedCalled;
private boolean mDumpCalled;
@@ -242,6 +279,11 @@
}
@Override
+ public void suggestGnssTime(GnssTimeSuggestion timeSuggestion) {
+ mLastGnssSuggestion = timeSuggestion;
+ }
+
+ @Override
public void handleAutoTimeConfigChanged() {
mHandleAutoTimeDetectionChangedCalled = true;
}
@@ -255,6 +297,7 @@
mLastTelephonySuggestion = null;
mLastManualSuggestion = null;
mLastNetworkSuggestion = null;
+ mLastGnssSuggestion = null;
mHandleAutoTimeDetectionChangedCalled = false;
mDumpCalled = false;
}
@@ -271,6 +314,10 @@
assertEquals(expectedSuggestion, mLastNetworkSuggestion);
}
+ void verifySuggestGnssTimeCalled(GnssTimeSuggestion expectedSuggestion) {
+ assertEquals(expectedSuggestion, mLastGnssSuggestion);
+ }
+
void verifyHandleAutoTimeDetectionChangedCalled() {
assertTrue(mHandleAutoTimeDetectionChangedCalled);
}
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 21396fd..b1adb0b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -16,6 +16,7 @@
package com.android.server.timedetector;
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
@@ -25,6 +26,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
@@ -569,7 +571,53 @@
}
@Test
- public void highPrioritySuggestionsShouldBeatLowerPrioritySuggestions() {
+ public void testSuggestGnssTime_autoTimeEnabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoOriginPriorities(ORIGIN_GNSS)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ GnssTimeSuggestion timeSuggestion =
+ mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
+
+ mScript.simulateTimePassing();
+
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
+ mScript.simulateGnssTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+ }
+
+ @Test
+ public void testSuggestGnssTime_autoTimeDisabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoOriginPriorities(ORIGIN_GNSS)
+ .pokeAutoTimeDetectionEnabled(false);
+
+ GnssTimeSuggestion timeSuggestion =
+ mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
+
+ mScript.simulateTimePassing()
+ .simulateGnssTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
+ public void gnssTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoOriginPriorities(ORIGIN_GNSS)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
+ GnssTimeSuggestion timeSuggestion = mScript
+ .generateGnssTimeSuggestion(suggestedTime);
+
+ mScript.simulateGnssTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestGnssSuggestion(null);
+ }
+
+ @Test
+ public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK);
@@ -672,22 +720,130 @@
}
@Test
+ public void highPrioritySuggestionsBeatLowerPrioritySuggestions_networkGnssOrigins() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_NETWORK, ORIGIN_GNSS);
+
+ // Three obviously different times that could not be mistaken for each other.
+ Instant gnssTime1 = ARBITRARY_TEST_TIME;
+ Instant gnssTime2 = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+ Instant networkTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
+ // A small increment used to simulate the passage of time, but not enough to interfere with
+ // macro-level time changes associated with suggestion age.
+ final long smallTimeIncrementMillis = 101;
+
+ // A gnss suggestion is made. It should be used because there is no network suggestion.
+ GnssTimeSuggestion gnssTimeSuggestion1 =
+ mScript.generateGnssTimeSuggestion(gnssTime1);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulateGnssTimeSuggestion(gnssTimeSuggestion1)
+ .verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(gnssTimeSuggestion1.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestNetworkSuggestion(null)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion1);
+ assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion());
+ assertNull("No network suggestions were made:", mScript.peekLatestValidNetworkSuggestion());
+
+ // Simulate a little time passing.
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Now a network suggestion is made. Network suggestions are prioritized over gnss
+ // suggestions so it should "win".
+ NetworkTimeSuggestion networkTimeSuggestion =
+ mScript.generateNetworkTimeSuggestion(networkTime);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulateNetworkTimeSuggestion(networkTimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion1);
+ assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion());
+ assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
+
+ // Simulate some significant time passing: half the time allowed before a time signal
+ // becomes "too old to use".
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Now another gnss suggestion is made. Network suggestions are prioritized over
+ // gnss suggestions so the latest network suggestion should still "win".
+ GnssTimeSuggestion gnssTimeSuggestion2 =
+ mScript.generateGnssTimeSuggestion(gnssTime2);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulateGnssTimeSuggestion(gnssTimeSuggestion2)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Check internal state.
+ mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion2);
+ assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
+ assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
+
+ // Simulate some significant time passing: half the time allowed before a time signal
+ // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to
+ // be used but networkTimeSuggestion2 is not.
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2);
+
+ // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
+ // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle
+ // to re-run the detection logic. This may change in future but until then we rely on a
+ // steady stream of suggestions to re-evaluate.
+ mScript.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Check internal state.
+ mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion2);
+ assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
+ assertNull(
+ "Network suggestion should be expired:",
+ mScript.peekLatestValidNetworkSuggestion());
+
+ // Toggle auto-time off and on to force the detection logic to run.
+ mScript.simulateAutoTimeDetectionToggle()
+ .simulateTimePassing(smallTimeIncrementMillis)
+ .simulateAutoTimeDetectionToggle();
+
+ // Verify the latest gnss time now wins.
+ mScript.verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(gnssTimeSuggestion2.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion2);
+ assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
+ assertNull(
+ "Network suggestion should still be expired:",
+ mScript.peekLatestValidNetworkSuggestion());
+ }
+
+ @Test
public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_lowerPriorityComesFirst() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
- .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK);
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_GNSS);
Instant networkTime = ARBITRARY_TEST_TIME;
- Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+ Instant gnssTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+ Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
+ GnssTimeSuggestion gnssTimeSuggestion =
+ mScript.generateGnssTimeSuggestion(gnssTime);
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
mScript.simulateNetworkTimeSuggestion(networkTimeSuggestion)
+ .simulateGnssTimeSuggestion(gnssTimeSuggestion)
.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion)
.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
}
@@ -696,20 +852,25 @@
public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_higherPriorityComesFirst() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
- .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK);
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_GNSS);
Instant networkTime = ARBITRARY_TEST_TIME;
Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+ Instant gnssTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
+ GnssTimeSuggestion gnssTimeSuggestion =
+ mScript.generateGnssTimeSuggestion(gnssTime);
mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.simulateNetworkTimeSuggestion(networkTimeSuggestion)
+ .simulateGnssTimeSuggestion(gnssTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
+ .assertLatestGnssSuggestion(gnssTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
}
@@ -728,7 +889,37 @@
}
@Test
- public void suggestionsFromSourceNotListedInPrioritiesList_areIgnored() {
+ public void whenHigherPrioritySuggestionsAreNotAvailable_fallbacksToNext() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_GNSS);
+
+ GnssTimeSuggestion timeSuggestion =
+ mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
+
+ mScript.simulateGnssTimeSuggestion(timeSuggestion)
+ .assertLatestGnssSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
+ }
+
+ @Test
+ public void suggestionsFromTelephonyOriginNotInPriorityList_areIgnored() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_NETWORK);
+
+ int slotIndex = ARBITRARY_SLOT_INDEX;
+ Instant testTime = ARBITRARY_TEST_TIME;
+ TelephonyTimeSuggestion timeSuggestion =
+ mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
+
+ mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
+ .assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
+ public void suggestionsFromNetworkOriginNotInPriorityList_areIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
@@ -742,6 +933,20 @@
}
@Test
+ public void suggestionsFromGnssOriginNotInPriorityList_areIgnored() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true)
+ .pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
+
+ GnssTimeSuggestion timeSuggestion = mScript.generateGnssTimeSuggestion(
+ ARBITRARY_TEST_TIME);
+
+ mScript.simulateGnssTimeSuggestion(timeSuggestion)
+ .assertLatestGnssSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
public void autoOriginPrioritiesList_doesNotAffectManualSuggestion() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false)
@@ -945,6 +1150,11 @@
return this;
}
+ Script simulateGnssTimeSuggestion(GnssTimeSuggestion timeSuggestion) {
+ mTimeDetectorStrategy.suggestGnssTime(timeSuggestion);
+ return this;
+ }
+
Script simulateAutoTimeDetectionToggle() {
mFakeCallback.simulateAutoTimeZoneDetectionToggle();
mTimeDetectorStrategy.handleAutoTimeConfigChanged();
@@ -995,6 +1205,14 @@
}
/**
+ * White box test info: Asserts the latest gnss suggestion is as expected.
+ */
+ Script assertLatestGnssSuggestion(GnssTimeSuggestion expected) {
+ assertEquals(expected, mTimeDetectorStrategy.getLatestGnssSuggestion());
+ 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.
*/
@@ -1011,6 +1229,14 @@
}
/**
+ * White box test info: Returns the gnss suggestion that would be used, if any, given the
+ * current elapsed real time clock and regardless of origin prioritization.
+ */
+ GnssTimeSuggestion peekLatestValidGnssSuggestion() {
+ return mTimeDetectorStrategy.findLatestValidGnssSuggestionForTests();
+ }
+
+ /**
* Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
@@ -1057,6 +1283,18 @@
}
/**
+ * Generates a GnssTimeSuggestion using the current elapsed realtime clock for the
+ * reference time.
+ */
+ GnssTimeSuggestion generateGnssTimeSuggestion(Instant suggestedTime) {
+ TimestampedValue<Long> utcTime =
+ new TimestampedValue<>(
+ mFakeCallback.peekElapsedRealtimeMillis(),
+ suggestedTime.toEpochMilli());
+ return new GnssTimeSuggestion(utcTime);
+ }
+
+ /**
* Calculates what the supplied time would be when adjusted for the movement of the fake
* elapsed realtime clock.
*/