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