diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java index b05f7cf241e37..0fb04b9d7299f 100644 --- a/core/java/android/net/SntpClient.java +++ b/core/java/android/net/SntpClient.java @@ -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: *
SntpClient client = new SntpClient();
@@ -49,6 +49,10 @@ import java.util.function.Supplier;
  *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
  * }
  * 
+ * + *

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, diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index c544839f58896..12e33f56b3c63 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -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 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; - } - }; - /** 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. + * + *

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 + * + *

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. + * + *

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 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; + } + } } diff --git a/core/tests/coretests/src/android/util/NtpTrustedTimeTest.java b/core/tests/coretests/src/android/util/NtpTrustedTimeTest.java index 4d088b41310df..67a4f44a9863f 100644 --- a/core/tests/coretests/src/android/util/NtpTrustedTimeTest.java +++ b/core/tests/coretests/src/android/util/NtpTrustedTimeTest.java @@ -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()); + } } diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java index 48888f2e00dde..1f8b0de8d903a 100644 --- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java @@ -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); } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 1ee23fd962e68..b9c74baaf3d3a 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -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(),