1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.net.ConnectivityManager; 26 import android.net.Network; 27 import android.net.NetworkInfo; 28 import android.net.SntpClient; 29 import android.os.Build; 30 import android.os.SystemClock; 31 import android.provider.Settings; 32 import android.text.TextUtils; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.io.PrintWriter; 38 import java.net.InetSocketAddress; 39 import java.net.URI; 40 import java.net.URISyntaxException; 41 import java.time.Duration; 42 import java.time.Instant; 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.List; 46 import java.util.Objects; 47 48 /** 49 * A singleton that connects with a remote NTP server as its trusted time source. This class 50 * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the 51 * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()} 52 * will block during that request. 53 * 54 * @hide 55 */ 56 public abstract class NtpTrustedTime implements TrustedTime { 57 58 private static final String URI_SCHEME_NTP = "ntp"; 59 @VisibleForTesting 60 public static final String NTP_SETTING_SERVER_NAME_DELIMITER = "|"; 61 private static final String NTP_SETTING_SERVER_NAME_DELIMITER_REGEXP = "\\|"; 62 63 /** 64 * NTP server configuration. 65 * 66 * @hide 67 */ 68 public static final class NtpConfig { 69 70 @NonNull private final List<URI> mServerUris; 71 @NonNull private final Duration mTimeout; 72 73 /** 74 * Creates an instance with the supplied properties. There must be at least one NTP server 75 * URI and the timeout must be non-zero / non-negative. 76 * 77 * <p>If the arguments are invalid then an {@link IllegalArgumentException} will be thrown. 78 * See {@link #parseNtpUriStrict(String)} and {@link #parseNtpServerSetting(String)} to 79 * create valid URIs. 80 */ NtpConfig(@onNull List<URI> serverUris, @NonNull Duration timeout)81 public NtpConfig(@NonNull List<URI> serverUris, @NonNull Duration timeout) 82 throws IllegalArgumentException { 83 84 Objects.requireNonNull(serverUris); 85 if (serverUris.isEmpty()) { 86 throw new IllegalArgumentException("Server URIs is empty"); 87 } 88 89 List<URI> validatedServerUris = new ArrayList<>(); 90 for (URI serverUri : serverUris) { 91 try { 92 URI validatedServerUri = validateNtpServerUri( 93 Objects.requireNonNull(serverUri)); 94 validatedServerUris.add(validatedServerUri); 95 } catch (URISyntaxException e) { 96 throw new IllegalArgumentException("Bad server URI", e); 97 } 98 } 99 mServerUris = Collections.unmodifiableList(validatedServerUris); 100 101 if (timeout.isNegative() || timeout.isZero()) { 102 throw new IllegalArgumentException("timeout < 0"); 103 } 104 mTimeout = timeout; 105 } 106 107 /** Returns a non-empty, immutable list of NTP server URIs. */ 108 @NonNull getServerUris()109 public List<URI> getServerUris() { 110 return mServerUris; 111 } 112 113 @NonNull getTimeout()114 public Duration getTimeout() { 115 return mTimeout; 116 } 117 118 @Override toString()119 public String toString() { 120 return "NtpConnectionInfo{" 121 + "mServerUris=" + mServerUris 122 + ", mTimeout=" + mTimeout 123 + '}'; 124 } 125 } 126 127 /** 128 * The result of a successful NTP query. 129 * 130 * @hide 131 */ 132 // Non-final for mocking frameworks 133 public static class TimeResult { 134 private final long mUnixEpochTimeMillis; 135 private final long mElapsedRealtimeMillis; 136 private final int mUncertaintyMillis; 137 @NonNull private final InetSocketAddress mNtpServerSocketAddress; 138 TimeResult( long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis, @NonNull InetSocketAddress ntpServerSocketAddress)139 public TimeResult( 140 long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis, 141 @NonNull InetSocketAddress ntpServerSocketAddress) { 142 mUnixEpochTimeMillis = unixEpochTimeMillis; 143 mElapsedRealtimeMillis = elapsedRealtimeMillis; 144 mUncertaintyMillis = uncertaintyMillis; 145 mNtpServerSocketAddress = Objects.requireNonNull(ntpServerSocketAddress); 146 } 147 getTimeMillis()148 public long getTimeMillis() { 149 return mUnixEpochTimeMillis; 150 } 151 getElapsedRealtimeMillis()152 public long getElapsedRealtimeMillis() { 153 return mElapsedRealtimeMillis; 154 } 155 getUncertaintyMillis()156 public int getUncertaintyMillis() { 157 return mUncertaintyMillis; 158 } 159 160 /** 161 * Calculates and returns the current Unix epoch time accounting for the age of this result. 162 */ currentTimeMillis()163 public long currentTimeMillis() { 164 return mUnixEpochTimeMillis + getAgeMillis(); 165 } 166 167 /** Calculates and returns the age of this result. */ getAgeMillis()168 public long getAgeMillis() { 169 return getAgeMillis(SystemClock.elapsedRealtime()); 170 } 171 172 /** 173 * Calculates and returns the age of this result relative to currentElapsedRealtimeMillis. 174 * 175 * @param currentElapsedRealtimeMillis - reference elapsed real time 176 */ getAgeMillis(long currentElapsedRealtimeMillis)177 public long getAgeMillis(long currentElapsedRealtimeMillis) { 178 return currentElapsedRealtimeMillis - mElapsedRealtimeMillis; 179 } 180 181 @Override equals(Object o)182 public boolean equals(Object o) { 183 if (this == o) { 184 return true; 185 } 186 if (!(o instanceof TimeResult)) { 187 return false; 188 } 189 TimeResult that = (TimeResult) o; 190 return mUnixEpochTimeMillis == that.mUnixEpochTimeMillis 191 && mElapsedRealtimeMillis == that.mElapsedRealtimeMillis 192 && mUncertaintyMillis == that.mUncertaintyMillis 193 && mNtpServerSocketAddress.equals( 194 that.mNtpServerSocketAddress); 195 } 196 197 @Override hashCode()198 public int hashCode() { 199 return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis, mUncertaintyMillis, 200 mNtpServerSocketAddress); 201 } 202 203 @Override toString()204 public String toString() { 205 return "TimeResult{" 206 + "unixEpochTime=" + Instant.ofEpochMilli(mUnixEpochTimeMillis) 207 + ", elapsedRealtime=" + Duration.ofMillis(mElapsedRealtimeMillis) 208 + ", mUncertaintyMillis=" + mUncertaintyMillis 209 + ", mNtpServerSocketAddress=" + mNtpServerSocketAddress 210 + '}'; 211 } 212 } 213 214 private static final String TAG = "NtpTrustedTime"; 215 private static final boolean LOGD = false; 216 217 private static NtpTrustedTime sSingleton; 218 219 /** An in-memory config override for use during tests. */ 220 @GuardedBy("this") 221 @Nullable 222 private NtpConfig mNtpConfigForTests; 223 224 @GuardedBy("this") 225 @Nullable 226 private URI mLastSuccessfulNtpServerUri; 227 228 // Declared volatile and accessed outside synchronized blocks to avoid blocking reads during 229 // forceRefresh(). 230 private volatile TimeResult mTimeResult; 231 NtpTrustedTime()232 protected NtpTrustedTime() { 233 } 234 235 @UnsupportedAppUsage getInstance(Context context)236 public static synchronized NtpTrustedTime getInstance(Context context) { 237 if (sSingleton == null) { 238 Context appContext = context.getApplicationContext(); 239 sSingleton = new NtpTrustedTimeImpl(appContext); 240 } 241 return sSingleton; 242 } 243 244 /** 245 * Overrides the NTP server config for tests. Passing {@code null} to a parameter clears the 246 * test value, i.e. so the normal value will be used next time. 247 */ setServerConfigForTests(@onNull NtpConfig ntpConfig)248 public void setServerConfigForTests(@NonNull NtpConfig ntpConfig) { 249 synchronized (this) { 250 mNtpConfigForTests = ntpConfig; 251 } 252 } 253 254 /** Forces a refresh using the default network. */ 255 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) forceRefresh()256 public boolean forceRefresh() { 257 synchronized (this) { 258 Network network = getDefaultNetwork(); 259 if (network == null) { 260 if (LOGD) Log.d(TAG, "forceRefresh: no network available"); 261 return false; 262 } 263 264 return forceRefreshLocked(network); 265 } 266 } 267 268 /** Forces a refresh using the specified network. */ forceRefresh(@onNull Network network)269 public boolean forceRefresh(@NonNull Network network) { 270 Objects.requireNonNull(network); 271 272 synchronized (this) { 273 return forceRefreshLocked(network); 274 } 275 } 276 277 @GuardedBy("this") forceRefreshLocked(@onNull Network network)278 private boolean forceRefreshLocked(@NonNull Network network) { 279 Objects.requireNonNull(network); 280 281 if (!isNetworkConnected(network)) { 282 if (LOGD) Log.d(TAG, "forceRefreshLocked: network=" + network + " is not connected"); 283 return false; 284 } 285 286 NtpConfig ntpConfig = getNtpConfig(); 287 if (ntpConfig == null) { 288 // missing server config, so no NTP time available 289 if (LOGD) Log.d(TAG, "forceRefreshLocked: invalid server config"); 290 return false; 291 } 292 293 if (LOGD) { 294 Log.d(TAG, "forceRefreshLocked: NTP request network=" + network 295 + " ntpConfig=" + ntpConfig); 296 } 297 298 List<URI> unorderedServerUris = ntpConfig.getServerUris(); 299 300 // Android supports multiple NTP server URIs for situations where servers might be 301 // unreachable for some devices due to network topology, e.g. we understand that devices 302 // travelling to China often have difficulty accessing "time.android.com". Android 303 // partners may want to configure alternative URIs for devices sold globally, or those 304 // that are likely to travel to part of the world without access to the full internet. 305 // 306 // The server URI list is expected to contain one element in the general case, with two 307 // or three as the anticipated maximum. The list is never empty. Server URIs are 308 // considered to be in a rough priority order of servers to try initially (no 309 // randomization), but besides that there is assumed to be no preference. 310 // 311 // The server selection algorithm below tries to stick with a successfully accessed NTP 312 // server's URI where possible: 313 // 314 // The algorithm based on the assumption that a cluster of NTP servers sharing the same 315 // host name, particularly commercially run ones, are likely to agree more closely on 316 // the time than servers from different URIs, so it's best to be sticky. Switching 317 // between URIs could result in flip-flopping between reference clocks or involve 318 // talking to server clusters with different approaches to leap second handling. 319 // 320 // Stickiness may also be useful if some server URIs early in the list are permanently 321 // black-holing requests, or if the responses are not routed back. In those cases it's 322 // best not to try those URIs more than we have to, as might happen if the algorithm 323 // always started at the beginning of the list. 324 // 325 // Generally, we have to assume that any of the configured servers are going to be "good 326 // enough" as an external reference clock when reachable, so the stickiness is a very 327 // lightly applied bias. There's no tracking of failure rates or back-off on a per-URI 328 // basis; higher level code is expected to handle rate limiting of NTP requests in the 329 // event of failure to contact any server. 330 331 List<URI> orderedServerUris = new ArrayList<>(); 332 for (URI serverUri : unorderedServerUris) { 333 if (serverUri.equals(mLastSuccessfulNtpServerUri)) { 334 orderedServerUris.add(0, serverUri); 335 } else { 336 orderedServerUris.add(serverUri); 337 } 338 } 339 340 for (URI serverUri : orderedServerUris) { 341 TimeResult timeResult = queryNtpServer(network, serverUri, ntpConfig.getTimeout()); 342 // Only overwrite previous state if the request was successful. 343 if (timeResult != null) { 344 mLastSuccessfulNtpServerUri = serverUri; 345 mTimeResult = timeResult; 346 return true; 347 } 348 } 349 return false; 350 } 351 352 @GuardedBy("this") getNtpConfig()353 private NtpConfig getNtpConfig() { 354 if (mNtpConfigForTests != null) { 355 return mNtpConfigForTests; 356 } 357 return getNtpConfigInternal(); 358 } 359 360 /** 361 * Returns the {@link NtpConfig} to use during an NTP query. This method can return {@code null} 362 * if there is no config, or the config found is invalid. 363 * 364 * <p>This method has been made public for easy replacement during tests. 365 */ 366 @VisibleForTesting 367 @Nullable getNtpConfigInternal()368 public abstract NtpConfig getNtpConfigInternal(); 369 370 /** 371 * Returns the default {@link Network} to use during an NTP query when no network is specified. 372 * This method can return {@code null} if the device hasn't fully initialized or there is no 373 * active network. 374 * 375 * <p>This method has been made public for easy replacement during tests. 376 */ 377 @VisibleForTesting 378 @Nullable getDefaultNetwork()379 public abstract Network getDefaultNetwork(); 380 381 /** 382 * Returns {@code true} if there is likely to be connectivity on the supplied network. 383 * 384 * <p>This method has been made public for easy replacement during tests. 385 */ 386 @VisibleForTesting isNetworkConnected(@onNull Network network)387 public abstract boolean isNetworkConnected(@NonNull Network network); 388 389 /** 390 * Queries the specified NTP server. This is a blocking call. Returns {@code null} if the query 391 * fails. 392 * 393 * <p>This method has been made public for easy replacement during tests. 394 */ 395 @VisibleForTesting 396 @Nullable queryNtpServer( @onNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout)397 public abstract TimeResult queryNtpServer( 398 @NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout); 399 400 /** 401 * Only kept for UnsupportedAppUsage. 402 * 403 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 404 */ 405 @Deprecated 406 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) hasCache()407 public boolean hasCache() { 408 return mTimeResult != null; 409 } 410 411 /** 412 * Only kept for UnsupportedAppUsage. 413 * 414 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 415 */ 416 @Deprecated 417 @Override getCacheAge()418 public long getCacheAge() { 419 TimeResult timeResult = mTimeResult; 420 if (timeResult != null) { 421 return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis(); 422 } else { 423 return Long.MAX_VALUE; 424 } 425 } 426 427 /** 428 * Only kept for UnsupportedAppUsage. 429 * 430 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 431 */ 432 @Deprecated 433 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) currentTimeMillis()434 public long currentTimeMillis() { 435 TimeResult timeResult = mTimeResult; 436 if (timeResult == null) { 437 throw new IllegalStateException("Missing authoritative time source"); 438 } 439 if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); 440 441 // current time is age after the last ntp cache; callers who 442 // want fresh values will hit forceRefresh() first. 443 return timeResult.currentTimeMillis(); 444 } 445 446 /** 447 * Only kept for UnsupportedAppUsage. 448 * 449 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 450 */ 451 @Deprecated 452 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTime()453 public long getCachedNtpTime() { 454 if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit"); 455 TimeResult timeResult = mTimeResult; 456 return timeResult == null ? 0 : timeResult.getTimeMillis(); 457 } 458 459 /** 460 * Only kept for UnsupportedAppUsage. 461 * 462 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 463 */ 464 @Deprecated 465 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTimeReference()466 public long getCachedNtpTimeReference() { 467 TimeResult timeResult = mTimeResult; 468 return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis(); 469 } 470 471 /** 472 * Returns an object containing the latest NTP information available. Can return {@code null} if 473 * no information is available. 474 */ 475 @Nullable getCachedTimeResult()476 public TimeResult getCachedTimeResult() { 477 return mTimeResult; 478 } 479 480 /** Sets the last received NTP time. Intended for use during tests. */ setCachedTimeResult(TimeResult timeResult)481 public void setCachedTimeResult(TimeResult timeResult) { 482 synchronized (this) { 483 mTimeResult = timeResult; 484 } 485 } 486 487 /** Clears the last received NTP time. Intended for use during tests. */ clearCachedTimeResult()488 public void clearCachedTimeResult() { 489 synchronized (this) { 490 mTimeResult = null; 491 } 492 } 493 494 /** 495 * Parses and returns an NTP server config URI, or throws an exception if the URI doesn't 496 * conform to expectations. 497 * 498 * <p>NTP server config URIs are in the form "ntp://{hostname}[:port]". This is not a registered 499 * IANA URI scheme. 500 */ 501 @NonNull parseNtpUriStrict(@onNull String ntpServerUriString)502 public static URI parseNtpUriStrict(@NonNull String ntpServerUriString) 503 throws URISyntaxException { 504 // java.net.URI is used in preference to android.net.Uri, since android.net.Uri is very 505 // forgiving of obvious errors. URI catches issues sooner. 506 URI unvalidatedUri = new URI(ntpServerUriString); 507 return validateNtpServerUri(unvalidatedUri); 508 } 509 510 /** 511 * Parses a setting string and returns a list of URIs that will be accepted by {@link 512 * NtpConfig}, or {@code null} if the string is invalid. 513 * 514 * <p>The setting string is expected to be one or more server values separated by a pipe ("|") 515 * character. 516 * 517 * <p>NTP server config URIs are in the form "ntp://{hostname}[:port]". This is not a registered 518 * IANA URI scheme. 519 * 520 * <p>Unlike {@link #parseNtpUriStrict(String)} this method will not throw an exception. It 521 * checks each value for a leading "ntp:" and will call through to {@link 522 * #parseNtpUriStrict(String)} to attempt to parse it, returning {@code null} if it fails. 523 * To support legacy settings values, it will also accept string values that only consists of a 524 * server name, which will be coerced into a URI in the form "ntp://{server name}". 525 */ 526 @VisibleForTesting 527 @Nullable parseNtpServerSetting(@ullable String ntpServerSetting)528 public static List<URI> parseNtpServerSetting(@Nullable String ntpServerSetting) { 529 if (TextUtils.isEmpty(ntpServerSetting)) { 530 return null; 531 } else { 532 String[] values = ntpServerSetting.split(NTP_SETTING_SERVER_NAME_DELIMITER_REGEXP); 533 if (values.length == 0) { 534 return null; 535 } 536 537 List<URI> uris = new ArrayList<>(); 538 for (String value : values) { 539 if (value.startsWith(URI_SCHEME_NTP + ":")) { 540 try { 541 uris.add(parseNtpUriStrict(value)); 542 } catch (URISyntaxException e) { 543 Log.w(TAG, "Rejected NTP uri setting=" + ntpServerSetting, e); 544 return null; 545 } 546 } else { 547 // This is the legacy settings path. Assumes that the string is just a host name 548 // and creates a URI in the form ntp://<host name> 549 try { 550 URI uri = new URI(URI_SCHEME_NTP, /*host=*/value, 551 /*path=*/null, /*fragment=*/null); 552 // Paranoia: validate just in case the host name somehow results in a bad 553 // URI. 554 URI validatedUri = validateNtpServerUri(uri); 555 uris.add(validatedUri); 556 } catch (URISyntaxException e) { 557 Log.w(TAG, "Rejected NTP legacy setting=" + ntpServerSetting, e); 558 return null; 559 } 560 } 561 } 562 return uris; 563 } 564 } 565 566 /** 567 * Checks that the supplied URI can be used to identify an NTP server. 568 * This method currently ignores Uri components that are not used, only checking the parts that 569 * must be present. Returns the supplied {@code uri} if validation is successful. 570 */ 571 @NonNull validateNtpServerUri(@onNull URI uri)572 private static URI validateNtpServerUri(@NonNull URI uri) throws URISyntaxException { 573 if (!uri.isAbsolute()) { 574 throw new URISyntaxException(uri.toString(), "Relative URI not supported"); 575 } 576 if (!URI_SCHEME_NTP.equals(uri.getScheme())) { 577 throw new URISyntaxException(uri.toString(), "Unrecognized scheme"); 578 } 579 String host = uri.getHost(); 580 if (TextUtils.isEmpty(host)) { 581 throw new URISyntaxException(uri.toString(), "Missing host"); 582 } 583 return uri; 584 } 585 586 /** Prints debug information. */ dump(PrintWriter pw)587 public void dump(PrintWriter pw) { 588 synchronized (this) { 589 pw.println("getNtpConfig()=" + getNtpConfig()); 590 pw.println("mNtpConfigForTests=" + mNtpConfigForTests); 591 pw.println("mLastSuccessfulNtpServerUri=" + mLastSuccessfulNtpServerUri); 592 pw.println("mTimeResult=" + mTimeResult); 593 if (mTimeResult != null) { 594 pw.println("mTimeResult.getAgeMillis()=" 595 + Duration.ofMillis(mTimeResult.getAgeMillis())); 596 } 597 } 598 } 599 600 /** 601 * The real implementation of {@link NtpTrustedTime}. Contains the parts that are more difficult 602 * to test. 603 */ 604 private static final class NtpTrustedTimeImpl extends NtpTrustedTime { 605 606 @GuardedBy("this") 607 private ConnectivityManager mConnectivityManager; 608 609 @NonNull 610 private final Context mContext; 611 NtpTrustedTimeImpl(@onNull Context context)612 private NtpTrustedTimeImpl(@NonNull Context context) { 613 mContext = Objects.requireNonNull(context); 614 } 615 616 @Override 617 @VisibleForTesting 618 @Nullable getNtpConfigInternal()619 public NtpConfig getNtpConfigInternal() { 620 final ContentResolver resolver = mContext.getContentResolver(); 621 final Resources res = mContext.getResources(); 622 623 // The Settings value has priority over static config. Check settings first. 624 final String serverGlobalSetting = 625 Settings.Global.getString(resolver, Settings.Global.NTP_SERVER); 626 final List<URI> settingsServerUris = parseNtpServerSetting(serverGlobalSetting); 627 628 List<URI> ntpServerUris; 629 if (settingsServerUris != null) { 630 ntpServerUris = settingsServerUris; 631 } else { 632 String[] configValues = 633 res.getStringArray(com.android.internal.R.array.config_ntpServers); 634 try { 635 List<URI> configServerUris = new ArrayList<>(); 636 for (String configValue : configValues) { 637 configServerUris.add(parseNtpUriStrict(configValue)); 638 } 639 ntpServerUris = configServerUris; 640 } catch (URISyntaxException e) { 641 ntpServerUris = null; 642 } 643 } 644 645 final int defaultTimeoutMillis = 646 res.getInteger(com.android.internal.R.integer.config_ntpTimeout); 647 final Duration timeout = Duration.ofMillis(Settings.Global.getInt( 648 resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis)); 649 return ntpServerUris == null ? null : new NtpConfig(ntpServerUris, timeout); 650 } 651 652 @Override getDefaultNetwork()653 public Network getDefaultNetwork() { 654 ConnectivityManager connectivityManager = getConnectivityManager(); 655 if (connectivityManager == null) { 656 return null; 657 } 658 return connectivityManager.getActiveNetwork(); 659 } 660 661 @Override isNetworkConnected(@onNull Network network)662 public boolean isNetworkConnected(@NonNull Network network) { 663 ConnectivityManager connectivityManager = getConnectivityManager(); 664 if (connectivityManager == null) { 665 return false; 666 } 667 final NetworkInfo ni = connectivityManager.getNetworkInfo(network); 668 669 // This connectivity check is to avoid performing a DNS lookup for the time server on a 670 // unconnected network. There are races to obtain time in Android when connectivity 671 // changes, which means that forceRefresh() can be called by various components before 672 // the network is actually available. This led in the past to DNS lookup failures being 673 // cached (~2 seconds) thereby preventing the device successfully making an NTP request 674 // when connectivity had actually been established. 675 // A side effect of check is that tests that run a fake NTP server on the device itself 676 // will only be able to use it if the active network is connected, even though loopback 677 // addresses are actually reachable. 678 if (ni == null || !ni.isConnected()) { 679 if (LOGD) Log.d(TAG, "getNetwork: no connectivity"); 680 return false; 681 } 682 return true; 683 } 684 getConnectivityManager()685 private synchronized ConnectivityManager getConnectivityManager() { 686 if (mConnectivityManager == null) { 687 mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); 688 } 689 if (mConnectivityManager == null) { 690 if (LOGD) Log.d(TAG, "getConnectivityManager: no ConnectivityManager"); 691 } 692 return mConnectivityManager; 693 } 694 695 @Override 696 @Nullable queryNtpServer( @onNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout)697 public TimeResult queryNtpServer( 698 @NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout) { 699 700 final SntpClient client = new SntpClient(); 701 final String serverName = ntpServerUri.getHost(); 702 final int port = ntpServerUri.getPort() == -1 703 ? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort(); 704 final int timeoutMillis = saturatedCast(timeout.toMillis()); 705 if (client.requestTime(serverName, port, timeoutMillis, network)) { 706 int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2); 707 InetSocketAddress ntpServerSocketAddress = client.getServerSocketAddress(); 708 return new TimeResult( 709 client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis, 710 ntpServerSocketAddress); 711 } else { 712 return null; 713 } 714 } 715 716 /** 717 * Casts a {@code long} to an {@code int}, clamping the value within the int range. 718 */ saturatedCast(long longValue)719 private static int saturatedCast(long longValue) { 720 if (longValue > Integer.MAX_VALUE) { 721 return Integer.MAX_VALUE; 722 } 723 if (longValue < Integer.MIN_VALUE) { 724 return Integer.MIN_VALUE; 725 } 726 return (int) longValue; 727 } 728 } 729 } 730