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 36 import java.util.Objects; 37 import java.util.function.Supplier; 38 39 /** 40 * A singleton that connects with a remote NTP server as its trusted time source. This class 41 * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the 42 * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()} 43 * will block during that request. 44 * 45 * @hide 46 */ 47 public class NtpTrustedTime implements TrustedTime { 48 49 /** 50 * The result of a successful NTP query. 51 * 52 * @hide 53 */ 54 public static class TimeResult { 55 private final long mTimeMillis; 56 private final long mElapsedRealtimeMillis; 57 private final long mCertaintyMillis; 58 TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis)59 public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) { 60 mTimeMillis = timeMillis; 61 mElapsedRealtimeMillis = elapsedRealtimeMillis; 62 mCertaintyMillis = certaintyMillis; 63 } 64 getTimeMillis()65 public long getTimeMillis() { 66 return mTimeMillis; 67 } 68 getElapsedRealtimeMillis()69 public long getElapsedRealtimeMillis() { 70 return mElapsedRealtimeMillis; 71 } 72 getCertaintyMillis()73 public long getCertaintyMillis() { 74 return mCertaintyMillis; 75 } 76 77 /** Calculates and returns the current time accounting for the age of this result. */ currentTimeMillis()78 public long currentTimeMillis() { 79 return mTimeMillis + getAgeMillis(); 80 } 81 82 /** Calculates and returns the age of this result. */ getAgeMillis()83 public long getAgeMillis() { 84 return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis; 85 } 86 87 @Override toString()88 public String toString() { 89 return "TimeResult{" 90 + "mTimeMillis=" + mTimeMillis 91 + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis 92 + ", mCertaintyMillis=" + mCertaintyMillis 93 + '}'; 94 } 95 } 96 97 private static final String TAG = "NtpTrustedTime"; 98 private static final boolean LOGD = false; 99 100 private static NtpTrustedTime sSingleton; 101 102 @NonNull 103 private final Context mContext; 104 105 /** 106 * A supplier that returns the ConnectivityManager. The Supplier can return null if 107 * ConnectivityService isn't running yet. 108 */ 109 private final Supplier<ConnectivityManager> mConnectivityManagerSupplier = 110 new Supplier<ConnectivityManager>() { 111 private ConnectivityManager mConnectivityManager; 112 113 @Nullable 114 @Override 115 public synchronized ConnectivityManager get() { 116 // We can't do this at initialization time: ConnectivityService might not be running 117 // yet. 118 if (mConnectivityManager == null) { 119 mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); 120 } 121 return mConnectivityManager; 122 } 123 }; 124 125 // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during 126 // forceRefresh(). 127 private volatile TimeResult mTimeResult; 128 NtpTrustedTime(Context context)129 private NtpTrustedTime(Context context) { 130 mContext = Objects.requireNonNull(context); 131 } 132 133 @UnsupportedAppUsage getInstance(Context context)134 public static synchronized NtpTrustedTime getInstance(Context context) { 135 if (sSingleton == null) { 136 Context appContext = context.getApplicationContext(); 137 sSingleton = new NtpTrustedTime(appContext); 138 } 139 return sSingleton; 140 } 141 142 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) forceRefresh()143 public boolean forceRefresh() { 144 synchronized (this) { 145 NtpConnectionInfo connectionInfo = getNtpConnectionInfo(); 146 if (connectionInfo == null) { 147 // missing server config, so no trusted time available 148 if (LOGD) Log.d(TAG, "forceRefresh: invalid server config"); 149 return false; 150 } 151 152 ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get(); 153 if (connectivityManager == null) { 154 if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager"); 155 return false; 156 } 157 final Network network = connectivityManager.getActiveNetwork(); 158 final NetworkInfo ni = connectivityManager.getNetworkInfo(network); 159 if (ni == null || !ni.isConnected()) { 160 if (LOGD) Log.d(TAG, "forceRefresh: no connectivity"); 161 return false; 162 } 163 164 if (LOGD) Log.d(TAG, "forceRefresh() from cache miss"); 165 final SntpClient client = new SntpClient(); 166 final String serverName = connectionInfo.getServer(); 167 final int timeoutMillis = connectionInfo.getTimeoutMillis(); 168 if (client.requestTime(serverName, timeoutMillis, network)) { 169 long ntpCertainty = client.getRoundTripTime() / 2; 170 mTimeResult = new TimeResult( 171 client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty); 172 return true; 173 } else { 174 return false; 175 } 176 } 177 } 178 179 /** 180 * Only kept for UnsupportedAppUsage. 181 * 182 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 183 */ 184 @Deprecated 185 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) hasCache()186 public boolean hasCache() { 187 return mTimeResult != null; 188 } 189 190 /** 191 * Only kept for UnsupportedAppUsage. 192 * 193 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 194 */ 195 @Deprecated 196 @Override getCacheAge()197 public long getCacheAge() { 198 TimeResult timeResult = mTimeResult; 199 if (timeResult != null) { 200 return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis(); 201 } else { 202 return Long.MAX_VALUE; 203 } 204 } 205 206 /** 207 * Only kept for UnsupportedAppUsage. 208 * 209 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 210 */ 211 @Deprecated 212 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) currentTimeMillis()213 public long currentTimeMillis() { 214 TimeResult timeResult = mTimeResult; 215 if (timeResult == null) { 216 throw new IllegalStateException("Missing authoritative time source"); 217 } 218 if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); 219 220 // current time is age after the last ntp cache; callers who 221 // want fresh values will hit forceRefresh() first. 222 return timeResult.currentTimeMillis(); 223 } 224 225 /** 226 * Only kept for UnsupportedAppUsage. 227 * 228 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 229 */ 230 @Deprecated 231 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTime()232 public long getCachedNtpTime() { 233 if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit"); 234 TimeResult timeResult = mTimeResult; 235 return timeResult == null ? 0 : timeResult.getTimeMillis(); 236 } 237 238 /** 239 * Only kept for UnsupportedAppUsage. 240 * 241 * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. 242 */ 243 @Deprecated 244 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getCachedNtpTimeReference()245 public long getCachedNtpTimeReference() { 246 TimeResult timeResult = mTimeResult; 247 return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis(); 248 } 249 250 /** 251 * Returns an object containing the latest NTP information available. Can return {@code null} if 252 * no information is available. 253 */ 254 @Nullable getCachedTimeResult()255 public TimeResult getCachedTimeResult() { 256 return mTimeResult; 257 } 258 259 private static class NtpConnectionInfo { 260 261 @NonNull private final String mServer; 262 private final int mTimeoutMillis; 263 NtpConnectionInfo(@onNull String server, int timeoutMillis)264 NtpConnectionInfo(@NonNull String server, int timeoutMillis) { 265 mServer = Objects.requireNonNull(server); 266 mTimeoutMillis = timeoutMillis; 267 } 268 269 @NonNull getServer()270 public String getServer() { 271 return mServer; 272 } 273 getTimeoutMillis()274 int getTimeoutMillis() { 275 return mTimeoutMillis; 276 } 277 } 278 279 @GuardedBy("this") getNtpConnectionInfo()280 private NtpConnectionInfo getNtpConnectionInfo() { 281 final ContentResolver resolver = mContext.getContentResolver(); 282 283 final Resources res = mContext.getResources(); 284 final String defaultServer = res.getString( 285 com.android.internal.R.string.config_ntpServer); 286 final int defaultTimeoutMillis = res.getInteger( 287 com.android.internal.R.integer.config_ntpTimeout); 288 289 final String secureServer = Settings.Global.getString( 290 resolver, Settings.Global.NTP_SERVER); 291 final int timeoutMillis = Settings.Global.getInt( 292 resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis); 293 294 final String server = secureServer != null ? secureServer : defaultServer; 295 return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis); 296 } 297 } 298