Add a new NetworkTimeHelper impl

Add a new NetworkTimeHelper impl and changes to support it in the
time detector service code.

Background:

When the location code that became NtpNetworkTimeHelper was first
written, Android devices were not guaranteed to be requesting the time
regularly from network time sources: it was only done if the user had
enabled automatic time detection.

That changed a few releases ago, and so the location code should always
be able to ask the time detector for the latest network time signal.

This is part of a wider goal to remove several dependencies in the
Android platform on low-level NTP client code (NtpTrustedTime class).
The NTP protocol usage should be an implementation detail, not something
that is widely known to unrelated classes that want an accurate time.

With the new impl, the SDK SystemClock.currentNetworkTimeClock() call
will ask the time detector service for the latest network time too, and
not interact with the NTP client singleton directly as it does today. It
currently needs to use the NTP client because both the location code and
time detector use the NtpTrustedTime singleton independently and it is
therefore the closest thing on Android today to an authority of "what is
the latest network time the device has obtained?".

Once the time detector is the authority on "latest network time", it
will allow the platform to apply stringent checks to things like time
sync accuracy, which is currently not well checked and heavily dependent
on network round-trip time and network delay symmmetry.

This refactoring is also potentially important for form factors like
Wear, which disable NetworkTimeUpdateService and therefore won't trigger
NtpTrustedTime while attempting to sync. There's a good chance the
location time sync is also broken on Wear (if present) because of the
unusual networking constraints. The API
SystemClock.currentNetworkTimeClock(), which was added to the public SDK
in Android T, may be unreliable or broken on Wear. In future, Wear could
call suggestNetworkTime() on the time detector service from its own
equivalent of NetworkTimeUpdateService and restore
SystemClock.currentNetworkTimeClock() behavior, while also supporting
the location stack's needs (if that is also used on Wear).

Centralizing network sync under NetworkTimeUpdateService will mean that
fewer components on devices will be syncing time for their own ends,
potentially reducing load on time servers too.

This centralization also supports options for changing how "network
time" is obtained in future, e.g. allowing easier integration of newer
protocols like NTS or Roughtime, or partner plug-ins to support
proprietary protocols.

New implementation details:

The TimeZoneDetectorNetworkTimeHelper implementation retrieves the
latest network time suggestion from the TimeDetectorInternal API. It
attempts to pass time to the GNSS code as often as the original
implementation, even when a new time signal isn't available and its
potentially repeating itself, in case GNSS code has become reliant on
that. Generally, it's hard to tell what the contract should be,
particularly with the unusual behavior around "on demand" Vs "periodic"
and the historic bug there.

The new implementation should become the default when it is considered
safe to do so, i.e. after testing when we are confident that
NetworkTimeUpdateService is behaving as well as the old
NtpNetworkTimeHelper impl when detecting connectivity, etc. This can be
done with a single boolean compile-time flag.

Other changes:

The time detector is now a dependency of the location stack, so the
SystemServer service bootstrap ordering has been adjusted.

Bug: 222295093
Test: atest services/robotests/src/com/android/server/location/gnss/TimeDetectorNetworkTimeHelperTest.java
Test: atest services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
Change-Id: I2f9a14776e9fafe426213df7cb0307a3fe541fad
This commit is contained in:
Neil Fuller
2022-12-13 15:05:38 +00:00
parent eea7870783
commit bd64b8d543
15 changed files with 1038 additions and 63 deletions

View File

@@ -31,6 +31,14 @@ import java.io.PrintWriter;
*/
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
@@ -47,7 +55,13 @@ abstract class NetworkTimeHelper {
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 @@ abstract class NetworkTimeHelper {
* 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 @@ abstract class NetworkTimeHelper {
* Dumps internal state during bugreports useful for debugging.
*/
abstract void dump(@NonNull PrintWriter pw);
}

View File

@@ -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);
}
}
}

View File

@@ -129,4 +129,9 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
public void dumpDebugLog(@NonNull PrintWriter printWriter) {
SystemClockTime.dump(printWriter);
}
@Override
public void runAsync(@NonNull Runnable runnable) {
mHandler.post(runnable);
}
}

View File

@@ -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,10 +64,25 @@ public interface TimeDetectorInternal {
/**
* 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

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.os.Handler;
import com.android.server.timezonedetector.CurrentUserIdentityInjector;
import com.android.server.timezonedetector.StateChangeListener;
import java.util.Objects;
@@ -86,6 +87,19 @@ public class TimeDetectorInternalImpl implements TimeDetectorInternal {
mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(suggestion));
}
@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);

View File

@@ -47,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
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 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
// 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;
}
}
}

View File

@@ -29,6 +29,7 @@ import android.util.IndentingPrintWriter;
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,10 +105,18 @@ public interface TimeDetectorStrategy extends Dumpable {
/**
* 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.

View File

@@ -36,6 +36,7 @@ import android.app.timedetector.ManualTimeSuggestion;
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 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
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 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
* 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 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
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 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
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 final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
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);

View File

@@ -2086,6 +2086,14 @@ public final class SystemServer implements Dumpable {
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();
@@ -2099,14 +2107,6 @@ public final class SystemServer implements Dumpable {
}
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);

View File

@@ -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);
}
}
}

View File

@@ -24,6 +24,8 @@ import android.app.timedetector.ManualTimeSuggestion;
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 @@ import android.util.IndentingPrintWriter;
public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
// State
private TimeState mTimeState;
private NetworkTimeSuggestion mLatestNetworkTimeSuggestion;
@Override
public TimeState getTimeState() {
@@ -61,9 +64,13 @@ public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
public void suggestNetworkTime(NetworkTimeSuggestion suggestion) {
}
@Override
public void addNetworkTimeUpdateListener(StateChangeListener networkSuggestionUpdateListener) {
}
@Override
public NetworkTimeSuggestion getLatestNetworkSuggestion() {
return null;
return mLatestNetworkTimeSuggestion;
}
@Override
@@ -81,4 +88,8 @@ public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
@Override
public void dump(IndentingPrintWriter pw, String[] args) {
}
void setLatestNetworkTime(NetworkTimeSuggestion networkTimeSuggestion) {
mLatestNetworkTimeSuggestion = networkTimeSuggestion;
}
}

View File

@@ -54,6 +54,7 @@ import android.util.NtpTrustedTime;
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 @@ public class TimeDetectorServiceTest {
@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

View File

@@ -41,6 +41,7 @@ import android.os.TimestampedValue;
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.Duration;
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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
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 @@ public class TimeDetectorStrategyImplTest {
.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 @@ public class TimeDetectorStrategyImplTest {
*/
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 @@ public class TimeDetectorStrategyImplTest {
// 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 @@ public class TimeDetectorStrategyImplTest {
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
assertEquals(expectedConfidence, mSystemClockConfidence);
}
@@ -2118,6 +2202,17 @@ public class TimeDetectorStrategyImplTest {
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();

View File

@@ -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);
}
}

View File

@@ -2001,26 +2001,4 @@ public class TimeZoneDetectorStrategyImplTest {
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);
}
}
}