Merge "Further refactoring for multiple NTP servers"

This commit is contained in:
TreeHugger Robot
2022-07-14 11:37:31 +00:00
committed by Android (Google) Code Review
5 changed files with 399 additions and 113 deletions

View File

@@ -16,6 +16,7 @@
package android.net;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.sntp.Duration64;
import android.net.sntp.Timestamp64;
@@ -29,6 +30,7 @@ import com.android.internal.util.TrafficStatsConstants;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -39,9 +41,7 @@ import java.util.Random;
import java.util.function.Supplier;
/**
* {@hide}
*
* Simple SNTP client class for retrieving network time.
* Simple, single-use SNTP client class for retrieving network time.
*
* Sample usage:
* <pre>SntpClient client = new SntpClient();
@@ -49,6 +49,10 @@ import java.util.function.Supplier;
* long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
* }
* </pre>
*
* <p>This class is not thread-safe.
*
* @hide
*/
public class SntpClient {
private static final String TAG = "SntpClient";
@@ -87,6 +91,9 @@ public class SntpClient {
// The round trip (network) time in milliseconds
private long mRoundTripTime;
// Details of the NTP server used to obtain the time last.
@Nullable private InetSocketAddress mServerSocketAddress;
private static class InvalidServerReplyException extends Exception {
public InvalidServerReplyException(String message) {
super(message);
@@ -202,6 +209,7 @@ public class SntpClient {
mNtpTime = responseTime.plus(clockOffsetDuration).toEpochMilli();
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTimeMillis;
mServerSocketAddress = new InetSocketAddress(address, port);
} catch (Exception e) {
EventLogTags.writeNtpFailure(address.toString(), e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
@@ -284,6 +292,14 @@ public class SntpClient {
return mRoundTripTime;
}
/**
* Returns the address of the NTP server used in the NTP transaction
*/
@Nullable
public InetSocketAddress getServerSocketAddress() {
return mServerSocketAddress;
}
private static void checkValidServerReply(
byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp,
Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp,

View File

@@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
@@ -50,7 +51,7 @@ import java.util.function.Supplier;
*
* @hide
*/
public class NtpTrustedTime implements TrustedTime {
public abstract class NtpTrustedTime implements TrustedTime {
private static final String URI_SCHEME_NTP = "ntp";
@@ -107,16 +108,19 @@ public class NtpTrustedTime implements TrustedTime {
*
* @hide
*/
public static class TimeResult {
public static final class TimeResult {
private final long mUnixEpochTimeMillis;
private final long mElapsedRealtimeMillis;
private final int mUncertaintyMillis;
@NonNull private final InetSocketAddress mNtpServerSocketAddress;
public TimeResult(
long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis) {
long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis,
@NonNull InetSocketAddress ntpServerSocketAddress) {
mUnixEpochTimeMillis = unixEpochTimeMillis;
mElapsedRealtimeMillis = elapsedRealtimeMillis;
mUncertaintyMillis = uncertaintyMillis;
mNtpServerSocketAddress = Objects.requireNonNull(ntpServerSocketAddress);
}
public long getTimeMillis() {
@@ -152,12 +156,35 @@ public class NtpTrustedTime implements TrustedTime {
return currentElapsedRealtimeMillis - mElapsedRealtimeMillis;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TimeResult)) {
return false;
}
TimeResult that = (TimeResult) o;
return mUnixEpochTimeMillis == that.mUnixEpochTimeMillis
&& mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
&& mUncertaintyMillis == that.mUncertaintyMillis
&& mNtpServerSocketAddress.equals(
that.mNtpServerSocketAddress);
}
@Override
public int hashCode() {
return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis, mUncertaintyMillis,
mNtpServerSocketAddress);
}
@Override
public String toString() {
return "TimeResult{"
+ "unixEpochTime=" + Instant.ofEpochMilli(mUnixEpochTimeMillis)
+ ", elapsedRealtime=" + Duration.ofMillis(mElapsedRealtimeMillis)
+ ", mUncertaintyMillis=" + mUncertaintyMillis
+ ", mNtpServerSocketAddress=" + mNtpServerSocketAddress
+ '}';
}
}
@@ -167,46 +194,23 @@ public class NtpTrustedTime implements TrustedTime {
private static NtpTrustedTime sSingleton;
@NonNull
private final Context mContext;
/**
* A supplier that returns the ConnectivityManager. The Supplier can return null if
* ConnectivityService isn't running yet.
*/
private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
new Supplier<ConnectivityManager>() {
private ConnectivityManager mConnectivityManager;
@Nullable
@Override
public synchronized ConnectivityManager get() {
// We can't do this at initialization time: ConnectivityService might not be running
// yet.
if (mConnectivityManager == null) {
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
return mConnectivityManager;
}
};
/** An in-memory config override for use during tests. */
@GuardedBy("this")
@Nullable
private NtpConfig mNtpConfigForTests;
// Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
// Declared volatile and accessed outside synchronized blocks to avoid blocking reads during
// forceRefresh().
private volatile TimeResult mTimeResult;
private NtpTrustedTime(Context context) {
mContext = Objects.requireNonNull(context);
protected NtpTrustedTime() {
}
@UnsupportedAppUsage
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
Context appContext = context.getApplicationContext();
sSingleton = new NtpTrustedTime(appContext);
sSingleton = new NtpTrustedTimeImpl(appContext);
}
return sSingleton;
}
@@ -224,65 +228,71 @@ public class NtpTrustedTime implements TrustedTime {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean forceRefresh() {
synchronized (this) {
NtpConfig connectionInfo = getNtpConfig();
if (connectionInfo == null) {
NtpConfig ntpConfig = getNtpConfig();
if (ntpConfig == null) {
// missing server config, so no NTP time available
if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
return false;
}
ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
if (connectivityManager == null) {
if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
return false;
}
final Network network = connectivityManager.getActiveNetwork();
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
// This connectivity check is to avoid performing a DNS lookup for the time server on a
// unconnected network. There are races to obtain time in Android when connectivity
// changes, which means that forceRefresh() can be called by various components before
// the network is actually available. This led in the past to DNS lookup failures being
// cached (~2 seconds) thereby preventing the device successfully making an NTP request
// when connectivity had actually been established.
// A side effect of check is that tests that run a fake NTP server on the device itself
// will only be able to use it if the active network is connected, even though loopback
// addresses are actually reachable.
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
Network network = getNetwork();
if (network == null) {
if (LOGD) Log.d(TAG, "forceRefresh: no network available");
return false;
}
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
final SntpClient client = new SntpClient();
final URI ntpServerUri = connectionInfo.getServerUri();
final String serverName = ntpServerUri.getHost();
final int port = ntpServerUri.getPort() == -1
? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort();
final int timeoutMillis = saturatedCast(connectionInfo.getTimeout().toMillis());
if (client.requestTime(serverName, port, timeoutMillis, network)) {
int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2);
mTimeResult = new TimeResult(
client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis);
return true;
} else {
return false;
if (LOGD) {
Log.d(TAG, "forceRefresh: NTP request network=" + network
+ " ntpConfig=" + ntpConfig);
}
TimeResult timeResult =
queryNtpServer(network, ntpConfig.getServerUri(), ntpConfig.getTimeout());
if (timeResult != null) {
// Keep any previous time result.
mTimeResult = timeResult;
}
return timeResult != null;
}
}
@GuardedBy("this")
private NtpConfig getNtpConfig() {
if (mNtpConfigForTests != null) {
return mNtpConfigForTests;
}
return getNtpConfigInternal();
}
/**
* Casts a {@code long} to an {@code int}, clamping the value within the int range.
* Returns the {@link NtpConfig} to use during an NTP query. This method can return {@code null}
* if there is no config, or the config found is invalid.
*
* <p>This method has been made public for easy replacement during tests.
*/
private static int saturatedCast(long longValue) {
if (longValue > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (longValue < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) longValue;
}
@VisibleForTesting
@Nullable
public abstract NtpConfig getNtpConfigInternal();
/**
* Returns the {@link Network} to use during an NTP query. This method can return {@code null}
* if there is no connectivity
*
* <p>This method has been made public for easy replacement during tests.
*/
@VisibleForTesting
@Nullable
public abstract Network getNetwork();
/**
* Queries the specified NTP server. This is a blocking call. Returns {@code null} if the query
* fails.
*
* <p>This method has been made public for easy replacement during tests.
*/
@VisibleForTesting
@Nullable
public abstract TimeResult queryNtpServer(
@NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout);
/**
* Only kept for UnsupportedAppUsage.
@@ -371,39 +381,6 @@ public class NtpTrustedTime implements TrustedTime {
}
}
@GuardedBy("this")
private NtpConfig getNtpConfig() {
if (mNtpConfigForTests != null) {
return mNtpConfigForTests;
}
final ContentResolver resolver = mContext.getContentResolver();
final Resources res = mContext.getResources();
// The Settings value has priority over static config. Check settings first.
final String serverGlobalSetting =
Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);
final URI settingsServerInfo = parseNtpServerSetting(serverGlobalSetting);
URI ntpServerUri;
if (settingsServerInfo != null) {
ntpServerUri = settingsServerInfo;
} else {
String configValue = res.getString(com.android.internal.R.string.config_ntpServer);
try {
ntpServerUri = parseNtpUriStrict(configValue);
} catch (URISyntaxException e) {
ntpServerUri = null;
}
}
final int defaultTimeoutMillis =
res.getInteger(com.android.internal.R.integer.config_ntpTimeout);
final Duration timeout = Duration.ofMillis(Settings.Global.getInt(
resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis));
return ntpServerUri == null ? null : new NtpConfig(ntpServerUri, timeout);
}
/**
* Parses and returns an NTP server config URI, or throws an exception if the URI doesn't
* conform to expectations.
@@ -487,4 +464,129 @@ public class NtpTrustedTime implements TrustedTime {
}
}
}
/**
* The real implementation of {@link NtpTrustedTime}. Contains the parts that are more difficult
* to test.
*/
private static final class NtpTrustedTimeImpl extends NtpTrustedTime {
/**
* A supplier that returns the ConnectivityManager. The Supplier can return null if
* ConnectivityService isn't running yet.
*/
private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
new Supplier<>() {
private ConnectivityManager mConnectivityManager;
@Nullable
@Override
public synchronized ConnectivityManager get() {
// We can't do this at initialization time: ConnectivityService might not be running
// yet.
if (mConnectivityManager == null) {
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
return mConnectivityManager;
}
};
@NonNull
private final Context mContext;
private NtpTrustedTimeImpl(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
}
@Override
@VisibleForTesting
@Nullable
public NtpConfig getNtpConfigInternal() {
final ContentResolver resolver = mContext.getContentResolver();
final Resources res = mContext.getResources();
// The Settings value has priority over static config. Check settings first.
final String serverGlobalSetting =
Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);
final URI settingsServerInfo = parseNtpServerSetting(serverGlobalSetting);
URI ntpServerUri;
if (settingsServerInfo != null) {
ntpServerUri = settingsServerInfo;
} else {
String configValue = res.getString(com.android.internal.R.string.config_ntpServer);
try {
ntpServerUri = parseNtpUriStrict(configValue);
} catch (URISyntaxException e) {
ntpServerUri = null;
}
}
final int defaultTimeoutMillis =
res.getInteger(com.android.internal.R.integer.config_ntpTimeout);
final Duration timeout = Duration.ofMillis(Settings.Global.getInt(
resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis));
return ntpServerUri == null ? null : new NtpConfig(ntpServerUri, timeout);
}
@Override
public Network getNetwork() {
ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
if (connectivityManager == null) {
if (LOGD) Log.d(TAG, "getNetwork: no ConnectivityManager");
return null;
}
final Network network = connectivityManager.getActiveNetwork();
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
// This connectivity check is to avoid performing a DNS lookup for the time server on a
// unconnected network. There are races to obtain time in Android when connectivity
// changes, which means that forceRefresh() can be called by various components before
// the network is actually available. This led in the past to DNS lookup failures being
// cached (~2 seconds) thereby preventing the device successfully making an NTP request
// when connectivity had actually been established.
// A side effect of check is that tests that run a fake NTP server on the device itself
// will only be able to use it if the active network is connected, even though loopback
// addresses are actually reachable.
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "getNetwork: no connectivity");
return null;
}
return network;
}
@Override
@Nullable
public TimeResult queryNtpServer(
@NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout) {
final SntpClient client = new SntpClient();
final String serverName = ntpServerUri.getHost();
final int port = ntpServerUri.getPort() == -1
? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort();
final int timeoutMillis = saturatedCast(timeout.toMillis());
if (client.requestTime(serverName, port, timeoutMillis, network)) {
int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2);
InetSocketAddress ntpServerSocketAddress = client.getServerSocketAddress();
return new TimeResult(
client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis,
ntpServerSocketAddress);
} else {
return null;
}
}
/**
* Casts a {@code long} to an {@code int}, clamping the value within the int range.
*/
private static int saturatedCast(long longValue) {
if (longValue > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (longValue < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) longValue;
}
}
}

View File

@@ -16,9 +16,20 @@
package android.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.net.Network;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -26,6 +37,7 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
@@ -138,4 +150,158 @@ public class NtpTrustedTimeTest {
public void testNtpConfig_negativeTimeout() {
new NtpTrustedTime.NtpConfig(VALID_SERVER_URI, Duration.ofMillis(-1));
}
@Test
public void testForceRefresh_nullConfig() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(null);
assertFalse(ntpTrustedTime.forceRefresh());
assertFalse(ntpTrustedTime.hasCache());
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
assertNull(ntpTrustedTime.getCachedTimeResult());
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
verify(ntpTrustedTime, never()).getNetwork();
verify(ntpTrustedTime, never()).queryNtpServer(any(), any(), any());
}
@Test
public void testForceRefresh_noConnectivity() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
URI serverUri = URI.create("ntp://ntpserver.name");
Duration timeout = Duration.ofSeconds(5);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
new NtpTrustedTime.NtpConfig(serverUri, timeout));
when(ntpTrustedTime.getNetwork()).thenReturn(null);
assertFalse(ntpTrustedTime.forceRefresh());
assertFalse(ntpTrustedTime.hasCache());
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
assertNull(ntpTrustedTime.getCachedTimeResult());
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
verify(ntpTrustedTime, times(1)).getNetwork();
verify(ntpTrustedTime, never()).queryNtpServer(any(), any(), any());
}
@Test
public void testForceRefresh_queryFailed() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
URI serverUri = URI.create("ntp://ntpserver.name");
Duration timeout = Duration.ofSeconds(5);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
new NtpTrustedTime.NtpConfig(serverUri, timeout));
Network network = mock(Network.class);
when(ntpTrustedTime.getNetwork()).thenReturn(network);
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(null);
assertFalse(ntpTrustedTime.forceRefresh());
assertFalse(ntpTrustedTime.hasCache());
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
assertNull(ntpTrustedTime.getCachedTimeResult());
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
verify(ntpTrustedTime, times(1)).getNetwork();
verify(ntpTrustedTime, times(1)).queryNtpServer(network, serverUri, timeout);
}
@Test
public void testForceRefresh_querySucceeded() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
URI serverUri = URI.create("ntp://ntpserver.name");
Duration timeout = Duration.ofSeconds(5);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
new NtpTrustedTime.NtpConfig(serverUri, timeout));
Network network = mock(Network.class);
when(ntpTrustedTime.getNetwork()).thenReturn(network);
NtpTrustedTime.TimeResult successResult = new NtpTrustedTime.TimeResult(123L, 456L, 789,
InetSocketAddress.createUnresolved("placeholder", 123));
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult);
assertTrue(ntpTrustedTime.forceRefresh());
assertTrue(ntpTrustedTime.hasCache());
assertEquals(successResult.getTimeMillis(), ntpTrustedTime.getCachedNtpTime());
assertEquals(successResult.getElapsedRealtimeMillis(),
ntpTrustedTime.getCachedNtpTimeReference());
assertTrue(ntpTrustedTime.getCacheAge() != Long.MAX_VALUE);
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
verify(ntpTrustedTime, times(1)).getNetwork();
verify(ntpTrustedTime, times(1)).queryNtpServer(network, serverUri, timeout);
}
@Test
public void testForceRefresh_keepsOldValueOnFailure() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
URI serverUri = URI.create("ntp://ntpserver.name");
Duration timeout = Duration.ofSeconds(5);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
new NtpTrustedTime.NtpConfig(serverUri, timeout));
Network network = mock(Network.class);
when(ntpTrustedTime.getNetwork()).thenReturn(network);
NtpTrustedTime.TimeResult successResult = new NtpTrustedTime.TimeResult(123L, 456L, 789,
InetSocketAddress.createUnresolved("placeholder", 123));
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult);
assertTrue(ntpTrustedTime.forceRefresh());
assertTrue(ntpTrustedTime.hasCache());
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(null);
assertFalse(ntpTrustedTime.forceRefresh());
assertTrue(ntpTrustedTime.hasCache());
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
}
@Test
public void testForceRefresh_keepsNewValueOnSuccess() {
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
URI serverUri = URI.create("ntp://ntpserver.name");
Duration timeout = Duration.ofSeconds(5);
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
new NtpTrustedTime.NtpConfig(serverUri, timeout));
Network network = mock(Network.class);
when(ntpTrustedTime.getNetwork()).thenReturn(network);
NtpTrustedTime.TimeResult successResult1 = new NtpTrustedTime.TimeResult(123L, 456L, 789,
InetSocketAddress.createUnresolved("placeholder", 123));
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult1);
assertTrue(ntpTrustedTime.forceRefresh());
assertTrue(ntpTrustedTime.hasCache());
assertEquals(successResult1, ntpTrustedTime.getCachedTimeResult());
NtpTrustedTime.TimeResult successResult2 = new NtpTrustedTime.TimeResult(123L, 456L, 789,
InetSocketAddress.createUnresolved("placeholder", 123));
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult2);
assertTrue(ntpTrustedTime.forceRefresh());
assertTrue(ntpTrustedTime.hasCache());
assertEquals(successResult2, ntpTrustedTime.getCachedTimeResult());
}
}

View File

@@ -271,6 +271,7 @@ public class NetworkTimeUpdateService extends Binder {
NetworkTimeSuggestion timeSuggestion =
new NetworkTimeSuggestion(timeSignal, ntpResult.getUncertaintyMillis());
timeSuggestion.addDebugInfo(debugInfo);
timeSuggestion.addDebugInfo(ntpResult.toString());
mTimeDetectorInternal.suggestNetworkTime(timeSuggestion);
}

View File

@@ -61,6 +61,7 @@ import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.time.Instant;
@RunWith(AndroidJUnit4.class)
@@ -405,8 +406,8 @@ public class TimeDetectorServiceTest {
@Test
public void testLatestNetworkTime() {
NtpTrustedTime.TimeResult latestNetworkTime =
new NtpTrustedTime.TimeResult(1234L, 54321L, 999);
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
TimePoint expected = new TimePoint(latestNetworkTime.getTimeMillis(),