• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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