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 /** A lock to prevent multiple refreshes taking place at the same time. */ 220 private final Object mRefreshLock = new Object(); 221 222 /** A lock to ensure safe read/writes to configuration. */ 223 private final Object mConfigLock = new Object(); 224 225 /** An in-memory config override for use during tests. */ 226 @GuardedBy("mConfigLock") 227 @Nullable 228 private NtpConfig mNtpConfigForTests; 229 230 /** 231 * The latest time result. 232 * 233 * <p>Written when holding {@link #mRefreshLock} but declared volatile and can be read outside 234 * synchronized blocks to avoid blocking dump() during {@link #forceRefresh}. 235 */ 236 @Nullable 237 private volatile TimeResult mTimeResult; 238 239 /** 240 * The last successful NTP server URI, i.e. the one used to obtain {@link #mTimeResult} when it 241 * is non-null. 242 * 243 * <p>Written when holding {@link #mRefreshLock} but declared volatile and can be read outside 244 * synchronized blocks to avoid blocking dump() during {@link #forceRefresh}. 245 */ 246 @Nullable 247 private volatile URI mLastSuccessfulNtpServerUri; 248 NtpTrustedTime()249 protected NtpTrustedTime() { 250 } 251 252 @UnsupportedAppUsage getInstance(Context context)253 public static synchronized NtpTrustedTime getInstance(Context context) { 254 if (sSingleton == null) { 255 Context appContext = context.getApplicationContext(); 256 sSingleton = new NtpTrustedTimeImpl(appContext); 257 } 258 return sSingleton; 259 } 260 261 /** 262 * Overrides the NTP server config for tests. Passing {@code null} to a parameter clears the 263 * test value, i.e. so the normal value will be used next time. 264 */ setServerConfigForTests(@onNull NtpConfig ntpConfig)265 public void setServerConfigForTests(@NonNull NtpConfig ntpConfig) { 266 synchronized (mConfigLock) { 267 mNtpConfigForTests = ntpConfig; 268 } 269 } 270 271 /** Forces a refresh using the default network. */ 272 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) forceRefresh()273 public boolean forceRefresh() { 274 synchronized (mRefreshLock) { 275 Network network = getDefaultNetwork(); 276 if (network == null) { 277 if (LOGD) Log.d(TAG, "forceRefresh: no network available"); 278 return false; 279 } 280 281 return forceRefreshLocked(network); 282 } 283 } 284 285 /** Forces a refresh using the specified network. */ forceRefresh(@onNull Network network)286 public boolean forceRefresh(@NonNull Network network) { 287 Objects.requireNonNull(network); 288 289 synchronized (mRefreshLock) { 290 // Prevent concurrent refreshes. 291 return forceRefreshLocked(network); 292 } 293 } 294 295 @GuardedBy("mRefreshLock") forceRefreshLocked(@onNull Network network)296 private boolean forceRefreshLocked(@NonNull Network network) { 297 Objects.requireNonNull(network); 298 299 if (!isNetworkConnected(network)) { 300 if (LOGD) Log.d(TAG, "forceRefreshLocked: network=" + network + " is not connected"); 301 return false; 302 } 303 304 NtpConfig ntpConfig = getNtpConfig(); 305 if (ntpConfig == null) { 306 // missing server config, so no NTP time available 307 if (LOGD) Log.d(TAG, "forceRefreshLocked: invalid server config"); 308 return false; 309 } 310 311 if (LOGD) { 312 Log.d(TAG, "forceRefreshLocked: NTP request network=" + network 313 + " ntpConfig=" + ntpConfig); 314 } 315 316 List<URI> unorderedServerUris = ntpConfig.getServerUris(); 317 318 // Android supports multiple NTP server URIs for situations where servers might be 319 // unreachable for some devices due to network topology, e.g. we understand that devices 320 // travelling to China often have difficulty accessing "time.android.com". Android 321 // partners may want to configure alternative URIs for devices sold globally, or those 322 // that are likely to travel to part of the world without access to the full internet. 323 // 324 // The server URI list is expected to contain one element in the general case, with two 325 // or three as the anticipated maximum. The list is never empty. Server URIs are 326 // considered to be in a rough priority order of servers to try initially (no 327 // randomization), but besides that there is assumed to be no preference. 328 // 329 // The server selection algorithm below tries to stick with a successfully accessed NTP 330 // server's URI where possible: 331 // 332 // The algorithm based on the assumption that a cluster of NTP servers sharing the same 333 // host name, particularly commercially run ones, are likely to agree more closely on 334 // the time than servers from different URIs, so it's best to be sticky. Switching 335 // between URIs could result in flip-flopping between reference clocks or involve 336 // talking to server clusters with different approaches to leap second handling. 337 // 338 // Stickiness may also be useful if some server URIs early in the list are permanently 339 // black-holing requests, or if the responses are not routed back. In those cases it's 340 // best not to try those URIs more than we have to, as might happen if the algorithm 341 // always started at the beginning of the list. 342 // 343 // Generally, we have to assume that any of the configured servers are going to be "good 344 // enough" as an external reference clock when reachable, so the stickiness is a very 345 // lightly applied bias. There's no tracking of failure rates or back-off on a per-URI 346 // basis; higher level code is expected to handle rate limiting of NTP requests in the 347 // event of failure to contact any server. 348 349 List<URI> orderedServerUris = new ArrayList<>(); 350 for (URI serverUri : unorderedServerUris) { 351 if (serverUri.equals(mLastSuccessfulNtpServerUri)) { 352 orderedServerUris.add(0, serverUri); 353 } else { 354 orderedServerUris.add(serverUri); 355 } 356 } 357 358 for (URI serverUri : orderedServerUris) { 359 TimeResult timeResult = queryNtpServer(network, serverUri, ntpConfig.getTimeout()); 360 // Only overwrite previous state if the request was successful. 361 if (timeResult != null) { 362 mLastSuccessfulNtpServerUri = serverUri; 363 mTimeResult = timeResult; 364 return true; 365 } 366 } 367 return false; 368 } 369 getNtpConfig()370 private NtpConfig getNtpConfig() { 371 synchronized (mConfigLock) { 372 if (mNtpConfigForTests != null) { 373 return mNtpConfigForTests; 374 } 375 return getNtpConfigInternal(); 376 } 377 } 378 379 /** 380 * Returns the {@link NtpConfig} to use during an NTP query. This method can return {@code null} 381 * if there is no config, or the config found is invalid. 382 * 383 * <p>This method has been made public for easy replacement during tests. 384 */ 385 @GuardedBy("mConfigLock") 386 @VisibleForTesting 387 @Nullable getNtpConfigInternal()388 public abstract NtpConfig getNtpConfigInternal(); 389 390 /** 391 * Returns the default {@link Network} to use during an NTP query when no network is specified. 392 * This method can return {@code null} if the device hasn't fully initialized or there is no 393 * active network. 394 * 395 * <p>This method has been made public for easy replacement during tests. 396 */ 397 @VisibleForTesting 398 @Nullable getDefaultNetwork()399 public abstract Network getDefaultNetwork(); 400 401 /** 402 * Returns {@code true} if there is likely to be connectivity on the supplied network. 403 * 404 * <p>This method has been made public for easy replacement during tests. 405 */ 406 @VisibleForTesting isNetworkConnected(@onNull Network network)407 public abstract boolean isNetworkConnected(@NonNull Network network); 408 409 /** 410 * Queries the specified NTP server. This is a blocking call. Returns {@code null} if the query 411 * fails. 412 * 413 * <p>This method has been made public for easy replacement during tests. 414 */ 415 @VisibleForTesting 416 @Nullable queryNtpServer( @onNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout)417 public abstract TimeResult queryNtpServer( 418 @NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout); 419 420 /** 421 * Only kept for UnsupportedAppUsage. 422 * 423 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 424 */ 425 @Deprecated 426 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) hasCache()427 public boolean hasCache() { 428 return mTimeResult != null; 429 } 430 431 /** 432 * Only kept for UnsupportedAppUsage. 433 * 434 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 435 */ 436 @Deprecated 437 @Override getCacheAge()438 public long getCacheAge() { 439 TimeResult timeResult = mTimeResult; 440 if (timeResult != null) { 441 return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis(); 442 } else { 443 return Long.MAX_VALUE; 444 } 445 } 446 447 /** 448 * Only kept for UnsupportedAppUsage. 449 * 450 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 451 */ 452 @Deprecated 453 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) currentTimeMillis()454 public long currentTimeMillis() { 455 TimeResult timeResult = mTimeResult; 456 if (timeResult == null) { 457 throw new IllegalStateException("Missing authoritative time source"); 458 } 459 if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); 460 461 // current time is age after the last ntp cache; callers who 462 // want fresh values will hit forceRefresh() first. 463 return timeResult.currentTimeMillis(); 464 } 465 466 /** 467 * Only kept for UnsupportedAppUsage. 468 * 469 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 470 */ 471 @Deprecated 472 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTime()473 public long getCachedNtpTime() { 474 if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit"); 475 TimeResult timeResult = mTimeResult; 476 return timeResult == null ? 0 : timeResult.getTimeMillis(); 477 } 478 479 /** 480 * Only kept for UnsupportedAppUsage. 481 * 482 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 483 */ 484 @Deprecated 485 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTimeReference()486 public long getCachedNtpTimeReference() { 487 TimeResult timeResult = mTimeResult; 488 return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis(); 489 } 490 491 /** 492 * Returns an object containing the latest NTP information available. Can return {@code null} if 493 * no information is available. 494 */ 495 @Nullable getCachedTimeResult()496 public TimeResult getCachedTimeResult() { 497 return mTimeResult; 498 } 499 500 /** Sets the last received NTP time. Intended for use during tests. */ setCachedTimeResult(TimeResult timeResult)501 public void setCachedTimeResult(TimeResult timeResult) { 502 synchronized (mRefreshLock) { 503 mTimeResult = timeResult; 504 } 505 } 506 507 /** Clears the last received NTP time. Intended for use during tests. */ clearCachedTimeResult()508 public void clearCachedTimeResult() { 509 synchronized (mRefreshLock) { 510 mTimeResult = null; 511 } 512 } 513 514 /** 515 * Parses and returns an NTP server config URI, or throws an exception if the URI doesn't 516 * conform to expectations. 517 * 518 * <p>NTP server config URIs are in the form "ntp://{hostname}[:port]". This is not a registered 519 * IANA URI scheme. 520 */ 521 @NonNull parseNtpUriStrict(@onNull String ntpServerUriString)522 public static URI parseNtpUriStrict(@NonNull String ntpServerUriString) 523 throws URISyntaxException { 524 // java.net.URI is used in preference to android.net.Uri, since android.net.Uri is very 525 // forgiving of obvious errors. URI catches issues sooner. 526 URI unvalidatedUri = new URI(ntpServerUriString); 527 return validateNtpServerUri(unvalidatedUri); 528 } 529 530 /** 531 * Parses a setting string and returns a list of URIs that will be accepted by {@link 532 * NtpConfig}, or {@code null} if the string is invalid. 533 * 534 * <p>The setting string is expected to be one or more server values separated by a pipe ("|") 535 * character. 536 * 537 * <p>NTP server config URIs are in the form "ntp://{hostname}[:port]". This is not a registered 538 * IANA URI scheme. 539 * 540 * <p>Unlike {@link #parseNtpUriStrict(String)} this method will not throw an exception. It 541 * checks each value for a leading "ntp:" and will call through to {@link 542 * #parseNtpUriStrict(String)} to attempt to parse it, returning {@code null} if it fails. 543 * To support legacy settings values, it will also accept string values that only consists of a 544 * server name, which will be coerced into a URI in the form "ntp://{server name}". 545 */ 546 @VisibleForTesting 547 @Nullable parseNtpServerSetting(@ullable String ntpServerSetting)548 public static List<URI> parseNtpServerSetting(@Nullable String ntpServerSetting) { 549 if (TextUtils.isEmpty(ntpServerSetting)) { 550 return null; 551 } else { 552 String[] values = ntpServerSetting.split(NTP_SETTING_SERVER_NAME_DELIMITER_REGEXP); 553 if (values.length == 0) { 554 return null; 555 } 556 557 List<URI> uris = new ArrayList<>(); 558 for (String value : values) { 559 if (value.startsWith(URI_SCHEME_NTP + ":")) { 560 try { 561 uris.add(parseNtpUriStrict(value)); 562 } catch (URISyntaxException e) { 563 Log.w(TAG, "Rejected NTP uri setting=" + ntpServerSetting, e); 564 return null; 565 } 566 } else { 567 // This is the legacy settings path. Assumes that the string is just a host name 568 // and creates a URI in the form ntp://<host name> 569 try { 570 URI uri = new URI(URI_SCHEME_NTP, /*host=*/value, 571 /*path=*/null, /*fragment=*/null); 572 // Paranoia: validate just in case the host name somehow results in a bad 573 // URI. 574 URI validatedUri = validateNtpServerUri(uri); 575 uris.add(validatedUri); 576 } catch (URISyntaxException e) { 577 Log.w(TAG, "Rejected NTP legacy setting=" + ntpServerSetting, e); 578 return null; 579 } 580 } 581 } 582 return uris; 583 } 584 } 585 586 /** 587 * Checks that the supplied URI can be used to identify an NTP server. 588 * This method currently ignores Uri components that are not used, only checking the parts that 589 * must be present. Returns the supplied {@code uri} if validation is successful. 590 */ 591 @NonNull validateNtpServerUri(@onNull URI uri)592 private static URI validateNtpServerUri(@NonNull URI uri) throws URISyntaxException { 593 if (!uri.isAbsolute()) { 594 throw new URISyntaxException(uri.toString(), "Relative URI not supported"); 595 } 596 if (!URI_SCHEME_NTP.equals(uri.getScheme())) { 597 throw new URISyntaxException(uri.toString(), "Unrecognized scheme"); 598 } 599 String host = uri.getHost(); 600 if (TextUtils.isEmpty(host)) { 601 throw new URISyntaxException(uri.toString(), "Missing host"); 602 } 603 return uri; 604 } 605 606 /** Prints debug information. */ dump(PrintWriter pw)607 public void dump(PrintWriter pw) { 608 synchronized (mConfigLock) { 609 pw.println("getNtpConfig()=" + getNtpConfig()); 610 pw.println("mNtpConfigForTests=" + mNtpConfigForTests); 611 } 612 613 pw.println("mLastSuccessfulNtpServerUri=" + mLastSuccessfulNtpServerUri); 614 615 TimeResult timeResult = mTimeResult; 616 pw.println("mTimeResult=" + timeResult); 617 if (timeResult != null) { 618 pw.println("mTimeResult.getAgeMillis()=" 619 + Duration.ofMillis(timeResult.getAgeMillis())); 620 } 621 } 622 623 /** 624 * The real implementation of {@link NtpTrustedTime}. Contains the parts that are more difficult 625 * to test. 626 */ 627 private static final class NtpTrustedTimeImpl extends NtpTrustedTime { 628 629 @GuardedBy("this") 630 private ConnectivityManager mConnectivityManager; 631 632 @NonNull 633 private final Context mContext; 634 NtpTrustedTimeImpl(@onNull Context context)635 private NtpTrustedTimeImpl(@NonNull Context context) { 636 mContext = Objects.requireNonNull(context); 637 } 638 639 @Override 640 @VisibleForTesting 641 @Nullable getNtpConfigInternal()642 public NtpConfig getNtpConfigInternal() { 643 final ContentResolver resolver = mContext.getContentResolver(); 644 final Resources res = mContext.getResources(); 645 646 // The Settings value has priority over static config. Check settings first. 647 final String serverGlobalSetting = 648 Settings.Global.getString(resolver, Settings.Global.NTP_SERVER); 649 final List<URI> settingsServerUris = parseNtpServerSetting(serverGlobalSetting); 650 651 List<URI> ntpServerUris; 652 if (settingsServerUris != null) { 653 ntpServerUris = settingsServerUris; 654 } else { 655 String[] configValues = 656 res.getStringArray(com.android.internal.R.array.config_ntpServers); 657 try { 658 List<URI> configServerUris = new ArrayList<>(); 659 for (String configValue : configValues) { 660 configServerUris.add(parseNtpUriStrict(configValue)); 661 } 662 ntpServerUris = configServerUris; 663 } catch (URISyntaxException e) { 664 ntpServerUris = null; 665 } 666 } 667 668 final int defaultTimeoutMillis = 669 res.getInteger(com.android.internal.R.integer.config_ntpTimeout); 670 final Duration timeout = Duration.ofMillis(Settings.Global.getInt( 671 resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis)); 672 return ntpServerUris == null ? null : new NtpConfig(ntpServerUris, timeout); 673 } 674 675 @Override getDefaultNetwork()676 public Network getDefaultNetwork() { 677 ConnectivityManager connectivityManager = getConnectivityManager(); 678 if (connectivityManager == null) { 679 return null; 680 } 681 return connectivityManager.getActiveNetwork(); 682 } 683 684 @Override isNetworkConnected(@onNull Network network)685 public boolean isNetworkConnected(@NonNull Network network) { 686 ConnectivityManager connectivityManager = getConnectivityManager(); 687 if (connectivityManager == null) { 688 return false; 689 } 690 final NetworkInfo ni = connectivityManager.getNetworkInfo(network); 691 692 // This connectivity check is to avoid performing a DNS lookup for the time server on a 693 // unconnected network. There are races to obtain time in Android when connectivity 694 // changes, which means that forceRefresh() can be called by various components before 695 // the network is actually available. This led in the past to DNS lookup failures being 696 // cached (~2 seconds) thereby preventing the device successfully making an NTP request 697 // when connectivity had actually been established. 698 // A side effect of check is that tests that run a fake NTP server on the device itself 699 // will only be able to use it if the active network is connected, even though loopback 700 // addresses are actually reachable. 701 if (ni == null || !ni.isConnected()) { 702 if (LOGD) Log.d(TAG, "getNetwork: no connectivity"); 703 return false; 704 } 705 return true; 706 } 707 getConnectivityManager()708 private synchronized ConnectivityManager getConnectivityManager() { 709 if (mConnectivityManager == null) { 710 mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); 711 } 712 if (mConnectivityManager == null) { 713 if (LOGD) Log.d(TAG, "getConnectivityManager: no ConnectivityManager"); 714 } 715 return mConnectivityManager; 716 } 717 718 @Override 719 @Nullable queryNtpServer( @onNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout)720 public TimeResult queryNtpServer( 721 @NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout) { 722 723 final SntpClient client = new SntpClient(); 724 final String serverName = ntpServerUri.getHost(); 725 final int port = ntpServerUri.getPort() == -1 726 ? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort(); 727 final int timeoutMillis = saturatedCast(timeout.toMillis()); 728 if (client.requestTime(serverName, port, timeoutMillis, network)) { 729 int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2); 730 InetSocketAddress ntpServerSocketAddress = client.getServerSocketAddress(); 731 return new TimeResult( 732 client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis, 733 ntpServerSocketAddress); 734 } else { 735 return null; 736 } 737 } 738 739 /** 740 * Casts a {@code long} to an {@code int}, clamping the value within the int range. 741 */ saturatedCast(long longValue)742 private static int saturatedCast(long longValue) { 743 if (longValue > Integer.MAX_VALUE) { 744 return Integer.MAX_VALUE; 745 } 746 if (longValue < Integer.MIN_VALUE) { 747 return Integer.MIN_VALUE; 748 } 749 return (int) longValue; 750 } 751 } 752 } 753