Loading core/java/android/net/SntpClient.java +19 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading @@ -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"; Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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, Loading core/java/android/util/NtpTrustedTime.java +209 −107 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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() { Loading Loading @@ -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 + '}'; } } Loading @@ -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; } Loading @@ -224,66 +228,72 @@ 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"); Network network = getNetwork(); if (network == null) { if (LOGD) Log.d(TAG, "forceRefresh: no network available"); 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"); return false; if (LOGD) { Log.d(TAG, "forceRefresh: NTP request network=" + network + " ntpConfig=" + ntpConfig); } 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; TimeResult timeResult = queryNtpServer(network, ntpConfig.getServerUri(), ntpConfig.getTimeout()); if (timeResult != null) { // Keep any previous time result. mTimeResult = timeResult; } return timeResult != 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; @GuardedBy("this") private NtpConfig getNtpConfig() { if (mNtpConfigForTests != null) { return mNtpConfigForTests; } return (int) longValue; return getNtpConfigInternal(); } /** * 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. */ @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. * Loading Loading @@ -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. Loading Loading @@ -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; } } } core/tests/coretests/src/android/util/NtpTrustedTimeTest.java +166 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java +1 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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(), Loading Loading
core/java/android/net/SntpClient.java +19 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading @@ -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"; Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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, Loading
core/java/android/util/NtpTrustedTime.java +209 −107 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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() { Loading Loading @@ -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 + '}'; } } Loading @@ -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; } Loading @@ -224,66 +228,72 @@ 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"); Network network = getNetwork(); if (network == null) { if (LOGD) Log.d(TAG, "forceRefresh: no network available"); 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"); return false; if (LOGD) { Log.d(TAG, "forceRefresh: NTP request network=" + network + " ntpConfig=" + ntpConfig); } 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; TimeResult timeResult = queryNtpServer(network, ntpConfig.getServerUri(), ntpConfig.getTimeout()); if (timeResult != null) { // Keep any previous time result. mTimeResult = timeResult; } return timeResult != 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; @GuardedBy("this") private NtpConfig getNtpConfig() { if (mNtpConfigForTests != null) { return mNtpConfigForTests; } return (int) longValue; return getNtpConfigInternal(); } /** * 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. */ @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. * Loading Loading @@ -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. Loading Loading @@ -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; } } }
core/tests/coretests/src/android/util/NtpTrustedTimeTest.java +166 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java +1 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading
services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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(), Loading