Merge "Add a new NetworkTimeHelper impl"

This commit is contained in:
Neil Fuller
2023-02-14 15:30:57 +00:00
committed by Android (Google) Code Review
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

@@ -2095,6 +2095,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();
@@ -2108,14 +2116,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);
}
}
}