Merge "Add a new NetworkTimeHelper impl"
diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
index 3a25146..f5114b7 100644
--- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
@@ -32,6 +32,14 @@
abstract class NetworkTimeHelper {
/**
+ * This compile-time value can be changed to switch between new and old ways to obtain network
+ * time for GNSS. If you have to turn this from {@code true} to {@code false} then please create
+ * a platform bug. This switch will be removed in a future release. If there are problems with
+ * the new impl we'd like to hear about them.
+ */
+ static final boolean USE_TIME_DETECTOR_IMPL = false;
+
+ /**
* The callback interface used by {@link NetworkTimeHelper} to report the time to {@link
* GnssLocationProvider}. The callback can happen at any time using the thread associated with
* the looper passed to {@link #create(Context, Looper, InjectTimeCallback)}.
@@ -47,7 +55,13 @@
static NetworkTimeHelper create(
@NonNull Context context, @NonNull Looper looper,
@NonNull InjectTimeCallback injectTimeCallback) {
- return new NtpNetworkTimeHelper(context, looper, injectTimeCallback);
+ if (USE_TIME_DETECTOR_IMPL) {
+ TimeDetectorNetworkTimeHelper.Environment environment =
+ new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper);
+ return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback);
+ } else {
+ return new NtpNetworkTimeHelper(context, looper, injectTimeCallback);
+ }
}
/**
@@ -74,7 +88,9 @@
* Notifies that network connectivity has been established.
*
* <p>Called by {@link GnssLocationProvider} when the device establishes a data network
- * connection.
+ * connection. This call should be removed eventually because it should be handled by the {@link
+ * NetworkTimeHelper} implementation itself, but has been retained for compatibility while
+ * switching implementations.
*/
abstract void onNetworkAvailable();
@@ -82,4 +98,5 @@
* Dumps internal state during bugreports useful for debugging.
*/
abstract void dump(@NonNull PrintWriter pw);
+
}
diff --git a/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java
new file mode 100644
index 0000000..15366d3
--- /dev/null
+++ b/services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java
@@ -0,0 +1,339 @@
+/*
+ * 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 com.android.server.location.gnss;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.timedetector.NetworkTimeSuggestion;
+import com.android.server.timedetector.TimeDetectorInternal;
+import com.android.server.timezonedetector.StateChangeListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Handles injecting network time to GNSS by using information from the platform time detector.
+ */
+public class TimeDetectorNetworkTimeHelper extends NetworkTimeHelper {
+
+ /** Returns {@code true} if the TimeDetectorNetworkTimeHelper is being used. */
+ public static boolean isInUse() {
+ return NetworkTimeHelper.USE_TIME_DETECTOR_IMPL;
+ }
+
+ /**
+ * An interface exposed for easier testing that the surrounding class uses for interacting with
+ * platform services, handlers, etc.
+ */
+ interface Environment {
+
+ /**
+ * Returns the current elapsed realtime value. The same as calling {@link
+ * SystemClock#elapsedRealtime()} but easier to fake in tests.
+ */
+ @ElapsedRealtimeLong long elapsedRealtimeMillis();
+
+ /**
+ * Returns the latest / best network time available from the time detector service.
+ */
+ @Nullable NetworkTimeSuggestion getLatestNetworkTime();
+
+ /**
+ * Sets a listener that will receive a callback when the value returned by {@link
+ * #getLatestNetworkTime()} has changed.
+ */
+ void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener);
+
+ /**
+ * Requests asynchronous execution of {@link
+ * TimeDetectorNetworkTimeHelper#queryAndInjectNetworkTime}, to execute as soon as possible.
+ * The thread used is the same as used by {@link #requestDelayedTimeQueryCallback}.
+ * Only one immediate callback can be requested at a time; requesting a new immediate
+ * callback will clear any previously requested one.
+ */
+ void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason);
+
+ /**
+ * Requests a delayed call to
+ * {@link TimeDetectorNetworkTimeHelper#delayedQueryAndInjectNetworkTime()}.
+ * The thread used is the same as used by {@link #requestImmediateTimeQueryCallback}.
+ * Only one delayed callback can be scheduled at a time; requesting a new delayed callback
+ * will clear any previously requested one.
+ */
+ void requestDelayedTimeQueryCallback(
+ TimeDetectorNetworkTimeHelper helper, @DurationMillisLong long delayMillis);
+
+ /**
+ * Clear a delayed time query callback. This has no effect if no delayed callback is
+ * currently set.
+ */
+ void clearDelayedTimeQueryCallback();
+ }
+
+ private static final String TAG = "TDNetworkTimeHelper";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /** The maximum age of a network time signal that will be passed to GNSS. */
+ @VisibleForTesting
+ static final int MAX_NETWORK_TIME_AGE_MILLIS = 24 * 60 * 60 * 1000;
+
+ /**
+ * The maximum time that is allowed to pass before a network time signal should be evaluated to
+ * be passed to GNSS when mOnDemandTimeInjection == false.
+ */
+ static final int NTP_REFRESH_INTERVAL_MILLIS = MAX_NETWORK_TIME_AGE_MILLIS;
+
+ private final LocalLog mDumpLog = new LocalLog(10, /*useLocalTimestamps=*/false);
+
+ /** The object the helper uses to interact with other components. */
+ @NonNull private final Environment mEnvironment;
+ @NonNull private final InjectTimeCallback mInjectTimeCallback;
+
+ /** Set to true if the GNSS engine requested on-demand NTP time injections. */
+ @GuardedBy("this")
+ private boolean mPeriodicTimeInjectionEnabled;
+
+ /**
+ * Set to true when a network time has been injected. Used to ensure that a network time is
+ * injected if this object wasn't listening when a network time signal first became available.
+ */
+ @GuardedBy("this")
+ private boolean mNetworkTimeInjected;
+
+ TimeDetectorNetworkTimeHelper(
+ @NonNull Environment environment, @NonNull InjectTimeCallback injectTimeCallback) {
+ mInjectTimeCallback = Objects.requireNonNull(injectTimeCallback);
+ mEnvironment = Objects.requireNonNull(environment);
+
+ // Start listening for new network time updates immediately.
+ mEnvironment.setNetworkTimeUpdateListener(this::onNetworkTimeAvailable);
+ }
+
+ @Override
+ synchronized void setPeriodicTimeInjectionMode(boolean periodicTimeInjectionEnabled) {
+ // Periodic time injection has a complicated history. See b/73893222. When it is true, it
+ // doesn't mean ONLY send it periodically.
+ //
+ // periodicTimeInjectionEnabled == true means the GNSS would like to be told the time
+ // periodically in addition to all the other triggers (e.g. network available).
+
+ mPeriodicTimeInjectionEnabled = periodicTimeInjectionEnabled;
+ if (!periodicTimeInjectionEnabled) {
+ // Cancel any previously scheduled periodic query.
+ removePeriodicNetworkTimeQuery();
+ }
+
+ // Inject the latest network time in all cases if it is available.
+ // Calling queryAndInjectNetworkTime() will cause a time signal to be injected if one is
+ // available AND will cause the next periodic query to be scheduled.
+ String reason = "setPeriodicTimeInjectionMode(" + periodicTimeInjectionEnabled + ")";
+ mEnvironment.requestImmediateTimeQueryCallback(this, reason);
+ }
+
+ void onNetworkTimeAvailable() {
+ // A new network time could become available at any time. Make sure it is passed to GNSS.
+ mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkTimeAvailable");
+ }
+
+ @Override
+ void onNetworkAvailable() {
+ // In the original NetworkTimeHelper implementation, onNetworkAvailable() would cause an NTP
+ // refresh to be made if it had previously been blocked by network issues. This
+ // implementation generally relies on components associated with the time detector to
+ // monitor the network and call onNetworkTimeAvailable() when a time is available. However,
+ // it also checks mNetworkTimeInjected in case this component wasn't listening for
+ // onNetworkTimeAvailable() when the last one became available.
+ synchronized (this) {
+ if (!mNetworkTimeInjected) {
+ // Guard against ordering issues: This check should ensure that if a network time
+ // became available before this class started listening then the initial network
+ // time will still be injected.
+ mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkAvailable");
+ }
+ }
+ }
+
+ @Override
+ void demandUtcTimeInjection() {
+ mEnvironment.requestImmediateTimeQueryCallback(this, "demandUtcTimeInjection");
+ }
+
+ // This method should always be invoked on the mEnvironment thread.
+ void delayedQueryAndInjectNetworkTime() {
+ queryAndInjectNetworkTime("delayedTimeQueryCallback");
+ }
+
+ // This method should always be invoked on the mEnvironment thread.
+ synchronized void queryAndInjectNetworkTime(@NonNull String reason) {
+ NetworkTimeSuggestion latestNetworkTime = mEnvironment.getLatestNetworkTime();
+
+ maybeInjectNetworkTime(latestNetworkTime, reason);
+
+ // Deschedule (if needed) any previously scheduled periodic query.
+ removePeriodicNetworkTimeQuery();
+
+ if (mPeriodicTimeInjectionEnabled) {
+ int maxDelayMillis = NTP_REFRESH_INTERVAL_MILLIS;
+ String debugMsg = "queryAndInjectNtpTime: Scheduling periodic query"
+ + " reason=" + reason
+ + " latestNetworkTime=" + latestNetworkTime
+ + " maxDelayMillis=" + maxDelayMillis;
+ logToDumpLog(debugMsg);
+
+ // GNSS is expecting periodic injections, so schedule the next one.
+ mEnvironment.requestDelayedTimeQueryCallback(this, maxDelayMillis);
+ }
+ }
+
+ private long calculateTimeSignalAgeMillis(
+ @Nullable NetworkTimeSuggestion networkTimeSuggestion) {
+ if (networkTimeSuggestion == null) {
+ return Long.MAX_VALUE;
+ }
+
+ long suggestionElapsedRealtimeMillis =
+ networkTimeSuggestion.getUnixEpochTime().getElapsedRealtimeMillis();
+ long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+ return currentElapsedRealtimeMillis - suggestionElapsedRealtimeMillis;
+ }
+
+ @GuardedBy("this")
+ private void maybeInjectNetworkTime(
+ @Nullable NetworkTimeSuggestion latestNetworkTime, @NonNull String reason) {
+ // Historically, time would only be injected if it was under a certain age. This has been
+ // kept in case it is assumed by GNSS implementations.
+ if (calculateTimeSignalAgeMillis(latestNetworkTime) > MAX_NETWORK_TIME_AGE_MILLIS) {
+ String debugMsg = "maybeInjectNetworkTime: Not injecting latest network time"
+ + " latestNetworkTime=" + latestNetworkTime
+ + " reason=" + reason;
+ logToDumpLog(debugMsg);
+ return;
+ }
+
+ UnixEpochTime unixEpochTime = latestNetworkTime.getUnixEpochTime();
+ long unixEpochTimeMillis = unixEpochTime.getUnixEpochTimeMillis();
+ long currentTimeMillis = System.currentTimeMillis();
+ String debugMsg = "maybeInjectNetworkTime: Injecting latest network time"
+ + " latestNetworkTime=" + latestNetworkTime
+ + " reason=" + reason
+ + " System time offset millis=" + (unixEpochTimeMillis - currentTimeMillis);
+ logToDumpLog(debugMsg);
+
+ long timeReferenceMillis = unixEpochTime.getElapsedRealtimeMillis();
+ int uncertaintyMillis = latestNetworkTime.getUncertaintyMillis();
+ mInjectTimeCallback.injectTime(unixEpochTimeMillis, timeReferenceMillis, uncertaintyMillis);
+ mNetworkTimeInjected = true;
+ }
+
+ @Override
+ void dump(@NonNull PrintWriter pw) {
+ pw.println("TimeDetectorNetworkTimeHelper:");
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
+ synchronized (this) {
+ ipw.println("mPeriodicTimeInjectionEnabled=" + mPeriodicTimeInjectionEnabled);
+ }
+
+ ipw.println("Debug log:");
+ mDumpLog.dump(ipw);
+ }
+
+ private void logToDumpLog(@NonNull String message) {
+ mDumpLog.log(message);
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ private void removePeriodicNetworkTimeQuery() {
+ // De-schedule any previously scheduled refresh. This is idempotent and has no effect if
+ // there isn't one.
+ mEnvironment.clearDelayedTimeQueryCallback();
+ }
+
+ /** The real implementation of {@link Environment} used outside of tests. */
+ static class EnvironmentImpl implements Environment {
+
+ /** Used to ensure one scheduled runnable is queued at a time. */
+ private final Object mScheduledRunnableToken = new Object();
+ /** Used to ensure one immediate runnable is queued at a time. */
+ private final Object mImmediateRunnableToken = new Object();
+ private final Handler mHandler;
+ private final TimeDetectorInternal mTimeDetectorInternal;
+
+ EnvironmentImpl(Looper looper) {
+ mHandler = new Handler(looper);
+ mTimeDetectorInternal = LocalServices.getService(TimeDetectorInternal.class);
+ }
+
+ @Override
+ public long elapsedRealtimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public NetworkTimeSuggestion getLatestNetworkTime() {
+ return mTimeDetectorInternal.getLatestNetworkSuggestion();
+ }
+
+ @Override
+ public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) {
+ mTimeDetectorInternal.addNetworkTimeUpdateListener(stateChangeListener);
+ }
+
+ @Override
+ public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ String reason) {
+ // Ensure only one immediate callback is scheduled at a time. There's no
+ // post(Runnable, Object), so we postDelayed() with a zero wait.
+ synchronized (this) {
+ mHandler.removeCallbacksAndMessages(mImmediateRunnableToken);
+ mHandler.postDelayed(() -> helper.queryAndInjectNetworkTime(reason),
+ mImmediateRunnableToken, 0);
+ }
+ }
+
+ @Override
+ public void requestDelayedTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ long delayMillis) {
+ synchronized (this) {
+ clearDelayedTimeQueryCallback();
+ mHandler.postDelayed(helper::delayedQueryAndInjectNetworkTime,
+ mScheduledRunnableToken, delayMillis);
+ }
+ }
+
+ @Override
+ public synchronized void clearDelayedTimeQueryCallback() {
+ mHandler.removeCallbacksAndMessages(mScheduledRunnableToken);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 5801920..fc960d8 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -129,4 +129,9 @@
public void dumpDebugLog(@NonNull PrintWriter printWriter) {
SystemClockTime.dump(printWriter);
}
+
+ @Override
+ public void runAsync(@NonNull Runnable runnable) {
+ mHandler.post(runnable);
+ }
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java b/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
index 5df5cbc..4b65c55 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorInternal.java
@@ -17,10 +17,13 @@
package com.android.server.timedetector;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import android.app.timedetector.ManualTimeSuggestion;
+import com.android.server.timezonedetector.StateChangeListener;
+
/**
* The internal (in-process) system server API for the time detector service.
*
@@ -61,11 +64,26 @@
/**
* Suggests a network time to the time detector. The suggestion may not be used by the time
* detector to set the device's time depending on device configuration and user settings, but
- * can replace previous network suggestions received.
+ * can replace previous network suggestions received. See also
+ * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and
+ * {@link #getLatestNetworkSuggestion()}.
*/
void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion);
/**
+ * Adds a listener that will be notified when a new network time is available. See {@link
+ * #getLatestNetworkSuggestion()}.
+ */
+ void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkSuggestionUpdateListener);
+
+ /**
+ * Returns the latest / best network time received by the time detector.
+ */
+ @Nullable
+ NetworkTimeSuggestion getLatestNetworkSuggestion();
+
+ /**
* Suggests a GNSS-derived time to the time detector. The suggestion may not be used by the time
* detector to set the device's time depending on device configuration and user settings, but
* can replace previous GNSS suggestions received.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
index af168f8..7e96a43 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java
@@ -24,6 +24,7 @@
import android.os.Handler;
import com.android.server.timezonedetector.CurrentUserIdentityInjector;
+import com.android.server.timezonedetector.StateChangeListener;
import java.util.Objects;
@@ -87,6 +88,19 @@
}
@Override
+ public void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkTimeUpdateListener) {
+ Objects.requireNonNull(networkTimeUpdateListener);
+ mTimeDetectorStrategy.addNetworkTimeUpdateListener(networkTimeUpdateListener);
+ }
+
+ @Override
+ @NonNull
+ public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
+ }
+
+ @Override
public void suggestGnssTime(@NonNull GnssTimeSuggestion suggestion) {
Objects.requireNonNull(suggestion);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index a9dcff4..0da967a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -47,6 +47,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper;
import com.android.server.timezonedetector.CallerIdentityInjector;
import com.android.server.timezonedetector.CurrentUserIdentityInjector;
@@ -405,13 +406,19 @@
// TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
// be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
// detector. mNtpTrustedTime can be removed once this happens.
- NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
- if (ntpResult != null) {
- UnixEpochTime unixEpochTime = new UnixEpochTime(
- ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
- return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // The new implementation.
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
} else {
- return null;
+ // The old implementation.
+ NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+ if (ntpResult != null) {
+ UnixEpochTime unixEpochTime = new UnixEpochTime(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+ } else {
+ return null;
+ }
}
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index dbd7172..11cec66 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -29,6 +29,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.timezonedetector.Dumpable;
+import com.android.server.timezonedetector.StateChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -104,11 +105,19 @@
/**
* Processes the suggested network time. The suggestion may not be used to set the device's time
* depending on device configuration and user settings, but can replace previous network
- * suggestions received.
+ * suggestions received. See also
+ * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and
+ * {@link #getLatestNetworkSuggestion()}.
*/
void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion);
/**
+ * Adds a listener that will be notified when a new network time is available. See {@link
+ * #getLatestNetworkSuggestion()}.
+ */
+ void addNetworkTimeUpdateListener(@NonNull StateChangeListener networkSuggestionUpdateListener);
+
+ /**
* Returns the latest (accepted) network time suggestion. Returns {@code null} if there isn't
* one.
*/
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index d679bbe..b293bac 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -36,6 +36,7 @@
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
import android.os.Handler;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -125,6 +126,9 @@
private final ReferenceWithHistory<ExternalTimeSuggestion> mLastExternalSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @GuardedBy("this")
+ private final ArraySet<StateChangeListener> mNetworkTimeUpdateListeners = new ArraySet<>();
+
/**
* Used by {@link TimeDetectorStrategyImpl} to interact with device configuration / settings
* / system properties. It can be faked for testing.
@@ -180,6 +184,11 @@
* Dumps the time debug log to the supplied {@link PrintWriter}.
*/
void dumpDebugLog(PrintWriter printWriter);
+
+ /**
+ * Requests that the supplied runnable is invoked asynchronously.
+ */
+ void runAsync(@NonNull Runnable runnable);
}
static TimeDetectorStrategy create(
@@ -307,6 +316,7 @@
NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get();
if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(suggestion)) {
mLastNetworkSuggestion.set(suggestion);
+ notifyNetworkTimeUpdateListenersAsynchronously();
}
// Now perform auto time detection. The new suggestion may be used to modify the system
@@ -315,6 +325,20 @@
doAutoTimeDetection(reason);
}
+ @GuardedBy("this")
+ private void notifyNetworkTimeUpdateListenersAsynchronously() {
+ for (StateChangeListener listener : mNetworkTimeUpdateListeners) {
+ // This is queuing asynchronous notification, so no need to surrender the "this" lock.
+ mEnvironment.runAsync(listener::onChange);
+ }
+ }
+
+ @Override
+ public synchronized void addNetworkTimeUpdateListener(
+ @NonNull StateChangeListener networkSuggestionUpdateListener) {
+ mNetworkTimeUpdateListeners.add(networkSuggestionUpdateListener);
+ }
+
@Override
@Nullable
public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() {
@@ -325,6 +349,8 @@
public synchronized void clearLatestNetworkSuggestion() {
mLastNetworkSuggestion.set(null);
+ notifyNetworkTimeUpdateListenersAsynchronously();
+
// The loss of network time may change the time signal to use to set the system clock.
String reason = "Network time cleared";
doAutoTimeDetection(reason);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b117cae..850b5b6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2095,6 +2095,14 @@
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
t.traceEnd();
+ t.traceBegin("StartTimeDetectorService");
+ try {
+ mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+ } catch (Throwable e) {
+ reportWtf("starting TimeDetectorService service", e);
+ }
+ t.traceEnd();
+
t.traceBegin("StartLocationManagerService");
mSystemServiceManager.startService(LocationManagerService.Lifecycle.class);
t.traceEnd();
@@ -2108,14 +2116,6 @@
}
t.traceEnd();
- t.traceBegin("StartTimeDetectorService");
- try {
- mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
- } catch (Throwable e) {
- reportWtf("starting TimeDetectorService service", e);
- }
- t.traceEnd();
-
t.traceBegin("StartTimeZoneDetectorService");
try {
mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
diff --git a/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java
new file mode 100644
index 0000000..3e2e46c
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java
@@ -0,0 +1,399 @@
+/*
+ * 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 com.android.server.location.gnss;
+
+import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.MAX_NETWORK_TIME_AGE_MILLIS;
+import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.NTP_REFRESH_INTERVAL_MILLIS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.app.time.UnixEpochTime;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.location.gnss.NetworkTimeHelper.InjectTimeCallback;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.Environment;
+import com.android.server.timedetector.NetworkTimeSuggestion;
+import com.android.server.timezonedetector.StateChangeListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Unit tests for {@link TimeDetectorNetworkTimeHelper}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class TimeDetectorNetworkTimeHelperTest {
+
+ private static final NetworkTimeSuggestion ARBITRARY_NETWORK_TIME =
+ new NetworkTimeSuggestion(new UnixEpochTime(1234L, 7777L), 123);
+
+ private FakeEnvironment mFakeEnvironment;
+ @Mock private InjectTimeCallback mMockInjectTimeCallback;
+ private TimeDetectorNetworkTimeHelper mTimeDetectorNetworkTimeHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeEnvironment = new FakeEnvironment();
+ mTimeDetectorNetworkTimeHelper = new TimeDetectorNetworkTimeHelper(
+ mFakeEnvironment, mMockInjectTimeCallback);
+
+ // TimeDetectorNetworkTimeHelper should register for network time updates during
+ // construction.
+ mFakeEnvironment.assertHasNetworkTimeChangeListener();
+ }
+
+ @Test
+ public void setPeriodicTimeInjectionMode_true() {
+ testSetPeriodicTimeInjectionMode(true);
+ }
+
+ @Test
+ public void setPeriodicTimeInjectionMode_false() {
+ testSetPeriodicTimeInjectionMode(false);
+ }
+
+ private void testSetPeriodicTimeInjectionMode(boolean periodicTimeInjectionMode) {
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ int millisElapsedSinceNetworkTimeReceived = 1000;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ long currentElapsedRealtimeMillis =
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ + millisElapsedSinceNetworkTimeReceived;
+ mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
+
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(periodicTimeInjectionMode);
+
+ // All injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // Any call to setPeriodicTimeInjectionMode() should result in an (async) injected time
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // Check whether the scheduled async is set up / not set up for the periodic request.
+ if (periodicTimeInjectionMode) {
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ } else {
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ }
+ }
+
+ @Test
+ public void periodicInjectionBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // Configure periodic time injections. Doing so should cause a time query, but no time is
+ // available.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // A periodic check should be scheduled.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ // A network time becomes available: This should cause the registered listener to trigger.
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // A new periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoImmediateCallback();
+
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+
+ int arbitraryIterationCount = 3;
+ for (int i = 0; i < arbitraryIterationCount; i++) {
+ // Advance by the amount needed for the scheduled work to run. That work should query
+ // and inject.
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // A new periodic check should be scheduled.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+ }
+
+ @Test
+ public void networkTimeAvailableBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // No periodic time injections. This call causes a time query, but no time is available yet.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ // A network time becomes available: This should cause the registered listener to trigger
+ // and cause time to be injected.
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+
+ @Test
+ public void networkConnectivityAvailableBehavior() {
+ // Set the elapsed realtime clock to an arbitrary start value.
+ mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
+
+ // No periodic time injections. This call causes a time query, but no time is available yet.
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
+
+ // All query/injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // No time available, so no injection.
+ verifyNoMoreInteractions(mMockInjectTimeCallback);
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+
+ // Time passes...
+ mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
+
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ // Simulate location code noticing that connectivity has changed and notifying the helper.
+ mTimeDetectorNetworkTimeHelper.onNetworkAvailable();
+
+ // All query/injections are async, so we have to simulate the async work taking place,
+ // causing a query, time injection and a re-schedule.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+ mFakeEnvironment.simulateTimeAdvancing(1);
+ verify(mMockInjectTimeCallback).injectTime(
+ networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
+ networkTime.getUncertaintyMillis());
+
+ // No periodic check should be scheduled.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasNoImmediateCallback();
+ }
+
+ @Test
+ public void oldTimesNotInjected() {
+ NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
+ mFakeEnvironment.pokeLatestNetworkTime(networkTime);
+
+ int millisElapsedSinceNetworkTimeReceived = MAX_NETWORK_TIME_AGE_MILLIS;
+ long currentElapsedRealtimeMillis =
+ networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ + millisElapsedSinceNetworkTimeReceived;
+ mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
+
+ mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
+
+ // All injections are async, so we have to simulate the async work taking place.
+ mFakeEnvironment.assertHasNoScheduledAsyncCallback();
+ mFakeEnvironment.assertHasImmediateCallback();
+
+ // The age of the network time will now be MAX_NETWORK_TIME_AGE_MILLIS + 1, which is too
+ // old to inject.
+ mFakeEnvironment.simulateTimeAdvancing(1);
+
+ // Old network times should not be injected.
+ verify(mMockInjectTimeCallback, never()).injectTime(anyLong(), anyLong(), anyInt());
+
+ // Check whether the scheduled async is set up / not set up for the periodic request.
+ mFakeEnvironment.assertHasScheduledAsyncCallback(
+ mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
+ }
+
+ /** A fake implementation of {@link Environment} for use by this test. */
+ private static class FakeEnvironment implements Environment {
+
+ private StateChangeListener mNetworkTimeUpdateListener;
+
+ private long mCurrentElapsedRealtimeMillis;
+ private NetworkTimeSuggestion mLatestNetworkTime;
+
+ private TimeDetectorNetworkTimeHelper mImmediateAsyncCallback;
+ private String mImmediateAsyncCallbackReason;
+
+ private TimeDetectorNetworkTimeHelper mScheduledAsyncCallback;
+ private long mScheduledAsyncRunnableTimeMillis;
+
+ @Override
+ public long elapsedRealtimeMillis() {
+ return mCurrentElapsedRealtimeMillis;
+ }
+
+ @Override
+ public NetworkTimeSuggestion getLatestNetworkTime() {
+ return mLatestNetworkTime;
+ }
+
+ @Override
+ public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) {
+ mNetworkTimeUpdateListener = stateChangeListener;
+ }
+
+ @Override
+ public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
+ String reason) {
+ if (mImmediateAsyncCallback != null) {
+ fail("Only one immediate callback expected at a time, found reason: "
+ + mImmediateAsyncCallbackReason);
+ }
+ mImmediateAsyncCallback = helper;
+ mImmediateAsyncCallbackReason = reason;
+ }
+
+ @Override
+ public void requestDelayedTimeQueryCallback(
+ TimeDetectorNetworkTimeHelper instance, long delayMillis) {
+ mScheduledAsyncCallback = instance;
+ mScheduledAsyncRunnableTimeMillis = mCurrentElapsedRealtimeMillis + delayMillis;
+ }
+
+ @Override
+ public void clearDelayedTimeQueryCallback() {
+ mScheduledAsyncCallback = null;
+ mScheduledAsyncRunnableTimeMillis = -1;
+ }
+
+ void pokeLatestNetworkTime(NetworkTimeSuggestion networkTime) {
+ mLatestNetworkTime = networkTime;
+ }
+
+ void pokeElapsedRealtimeMillis(long currentElapsedRealtimeMillis) {
+ mCurrentElapsedRealtimeMillis = currentElapsedRealtimeMillis;
+ }
+
+ void simulateLatestNetworkTimeChange(NetworkTimeSuggestion networkTime) {
+ mLatestNetworkTime = networkTime;
+ mNetworkTimeUpdateListener.onChange();
+ }
+
+ void simulateTimeAdvancing(long durationMillis) {
+ mCurrentElapsedRealtimeMillis += durationMillis;
+
+ if (mImmediateAsyncCallback != null) {
+ TimeDetectorNetworkTimeHelper helper = mImmediateAsyncCallback;
+ String reason = mImmediateAsyncCallbackReason;
+ mImmediateAsyncCallback = null;
+ mImmediateAsyncCallbackReason = null;
+ helper.queryAndInjectNetworkTime(reason);
+ }
+
+ if (mScheduledAsyncCallback != null
+ && mCurrentElapsedRealtimeMillis >= mScheduledAsyncRunnableTimeMillis) {
+ TimeDetectorNetworkTimeHelper helper = mScheduledAsyncCallback;
+ mScheduledAsyncCallback = null;
+ mScheduledAsyncRunnableTimeMillis = -1;
+ helper.delayedQueryAndInjectNetworkTime();
+ }
+ }
+
+ void assertHasNetworkTimeChangeListener() {
+ assertNotNull(mNetworkTimeUpdateListener);
+ }
+
+ void assertHasImmediateCallback() {
+ assertNotNull(mImmediateAsyncCallback);
+ }
+
+ void assertHasNoImmediateCallback() {
+ assertNull(mImmediateAsyncCallback);
+ }
+
+ void assertHasScheduledAsyncCallback(long expectedScheduledAsyncRunnableTimeMillis) {
+ assertNotNull(mScheduledAsyncCallback);
+ assertEquals(expectedScheduledAsyncRunnableTimeMillis,
+ mScheduledAsyncRunnableTimeMillis);
+ }
+
+ void assertHasNoScheduledAsyncCallback() {
+ assertNull(mScheduledAsyncCallback);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
index 704b06b..87aa272 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -24,6 +24,8 @@
import android.app.timedetector.TelephonyTimeSuggestion;
import android.util.IndentingPrintWriter;
+import com.android.server.timezonedetector.StateChangeListener;
+
/**
* A fake implementation of {@link com.android.server.timedetector.TimeDetectorStrategy} for use
* in tests.
@@ -31,6 +33,7 @@
public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
// State
private TimeState mTimeState;
+ private NetworkTimeSuggestion mLatestNetworkTimeSuggestion;
@Override
public TimeState getTimeState() {
@@ -62,8 +65,12 @@
}
@Override
+ public void addNetworkTimeUpdateListener(StateChangeListener networkSuggestionUpdateListener) {
+ }
+
+ @Override
public NetworkTimeSuggestion getLatestNetworkSuggestion() {
- return null;
+ return mLatestNetworkTimeSuggestion;
}
@Override
@@ -81,4 +88,8 @@
@Override
public void dump(IndentingPrintWriter pw, String[] args) {
}
+
+ void setLatestNetworkTime(NetworkTimeSuggestion networkTimeSuggestion) {
+ mLatestNetworkTimeSuggestion = networkTimeSuggestion;
+ }
}
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 0b339ad..5a0867f 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -54,6 +54,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper;
import com.android.server.timezonedetector.TestCallerIdentityInjector;
import com.android.server.timezonedetector.TestHandler;
@@ -420,21 +421,35 @@
@Test
public void testGetLatestNetworkSuggestion() {
- NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
- 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
- when(mMockNtpTrustedTime.getCachedTimeResult())
- .thenReturn(latestNetworkTime);
- UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
- latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
- NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
- expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
- assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+
+ assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+ } else {
+ NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
+ 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
+ when(mMockNtpTrustedTime.getCachedTimeResult())
+ .thenReturn(latestNetworkTime);
+ UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+ latestNetworkTime.getElapsedRealtimeMillis(),
+ latestNetworkTime.getTimeMillis());
+ NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
+ expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
+ assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ }
}
@Test
public void testGetLatestNetworkSuggestion_noTimeAvailable() {
- when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ } else {
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ }
}
@Test
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 37da2a2..4df21e0 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -41,6 +41,7 @@
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.StateChangeListener;
+import com.android.server.timezonedetector.TestStateChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +52,8 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import junitparams.JUnitParamsRunner;
@@ -863,8 +866,11 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_NETWORK)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion =
script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -877,6 +883,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -885,7 +896,10 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
.setOriginPriorities(ORIGIN_NETWORK)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion =
script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -894,6 +908,11 @@
.simulateNetworkTimeSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -902,7 +921,11 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_NETWORK, ORIGIN_EXTERNAL)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
// Create two different time suggestions for the current elapsedRealtimeMillis.
ExternalTimeSuggestion externalTimeSuggestion =
@@ -927,6 +950,11 @@
script.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
// Clear the network time. This should cause the device to change back to the external time,
@@ -937,6 +965,11 @@
script.simulateClearLatestNetworkSuggestion()
.assertLatestNetworkSuggestion(null)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+
+ // Confirm network time update listeners are asynchronously notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
}
@@ -947,8 +980,11 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -957,6 +993,11 @@
.assertLatestNetworkSuggestion(null)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is not notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
}
@Test
@@ -966,8 +1007,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -976,6 +1019,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -985,8 +1033,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -995,6 +1045,11 @@
.assertLatestNetworkSuggestion(null)
.verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is not notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
}
@Test
@@ -1004,8 +1059,10 @@
.setOriginPriorities(ORIGIN_NETWORK)
.setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
.build();
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
Script script = new Script().simulateConfigurationInternalChange(configInternal)
- .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+ .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
NetworkTimeSuggestion timeSuggestion =
@@ -1014,6 +1071,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
.verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -1800,7 +1862,10 @@
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
.setOriginPriorities(ORIGIN_TELEPHONY)
.build();
- Script script = new Script().simulateConfigurationInternalChange(configInternal);
+ TestStateChangeListener networkTimeUpdateListener = new TestStateChangeListener();
+ Script script = new Script()
+ .simulateConfigurationInternalChange(configInternal)
+ .addNetworkTimeUpdateListener(networkTimeUpdateListener);
NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(
ARBITRARY_TEST_TIME);
@@ -1809,6 +1874,11 @@
.assertLatestNetworkSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Confirm the network time update listener is notified of a change.
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(0);
+ script.simulateAsyncRunnableExecution();
+ networkTimeUpdateListener.assertNotificationsReceivedAndReset(1);
}
@Test
@@ -1867,6 +1937,8 @@
*/
private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment {
+ private final List<Runnable> mAsyncRunnables = new ArrayList<>();
+
private ConfigurationInternal mConfigurationInternal;
private boolean mWakeLockAcquired;
private long mElapsedRealtimeMillis;
@@ -1951,8 +2023,20 @@
// No-op for tests
}
+ @Override
+ public void runAsync(Runnable runnable) {
+ mAsyncRunnables.add(runnable);
+ }
+
// Methods below are for managing the fake's behavior.
+ void runAsyncRunnables() {
+ for (Runnable runnable : mAsyncRunnables) {
+ runnable.run();
+ }
+ mAsyncRunnables.clear();
+ }
+
void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
mConfigurationInternalChangeListener.onChange();
@@ -1992,7 +2076,7 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
+ void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -2118,6 +2202,17 @@
return this;
}
+ /** Calls {@link TimeDetectorStrategy#addNetworkTimeUpdateListener(StateChangeListener)}. */
+ Script addNetworkTimeUpdateListener(StateChangeListener listener) {
+ mTimeDetectorStrategy.addNetworkTimeUpdateListener(listener);
+ return this;
+ }
+
+ Script simulateAsyncRunnableExecution() {
+ mFakeEnvironment.runAsyncRunnables();
+ return this;
+ }
+
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeEnvironment.verifySystemClockNotSet();
mFakeEnvironment.resetCallTracking();
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java
new file mode 100644
index 0000000..9cbf0a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestStateChangeListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 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 com.android.server.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestStateChangeListener implements StateChangeListener {
+
+ private int mNotificationsReceived;
+
+ @Override
+ public void onChange() {
+ mNotificationsReceived++;
+ }
+
+ public void assertNotificationsReceivedAndReset(int expectedCount) {
+ assertNotificationsReceived(expectedCount);
+ resetNotificationsReceivedCount();
+ }
+
+ private void resetNotificationsReceivedCount() {
+ mNotificationsReceived = 0;
+ }
+
+ private void assertNotificationsReceived(int expectedCount) {
+ assertEquals(expectedCount, mNotificationsReceived);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 590aba9..03d406f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -2001,26 +2001,4 @@
return new TelephonyTestCase(matchType, quality, expectedScore);
}
- private static class TestStateChangeListener implements StateChangeListener {
-
- private int mNotificationsReceived;
-
- @Override
- public void onChange() {
- mNotificationsReceived++;
- }
-
- public void assertNotificationsReceivedAndReset(int expectedCount) {
- assertNotificationsReceived(expectedCount);
- resetNotificationsReceivedCount();
- }
-
- private void resetNotificationsReceivedCount() {
- mNotificationsReceived = 0;
- }
-
- private void assertNotificationsReceived(int expectedCount) {
- assertEquals(expectedCount, mNotificationsReceived);
- }
- }
}