1 package com.android.server.location;
2 
3 import android.content.Context;
4 import android.net.ConnectivityManager;
5 import android.net.NetworkInfo;
6 import android.os.Handler;
7 import android.os.Looper;
8 import android.os.PowerManager;
9 import android.os.PowerManager.WakeLock;
10 import android.util.Log;
11 import android.util.NtpTrustedTime;
12 
13 import com.android.internal.annotations.GuardedBy;
14 import com.android.internal.annotations.VisibleForTesting;
15 
16 import java.util.Date;
17 
18 /**
19  * Handles inject NTP time to GNSS.
20  *
21  * <p>The client is responsible to call {@link #onNetworkAvailable()} when network is available
22  * for retrieving NTP Time.
23  */
24 class NtpTimeHelper {
25 
26     private static final String TAG = "NtpTimeHelper";
27     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
28 
29     // states for injecting ntp
30     private static final int STATE_PENDING_NETWORK = 0;
31     private static final int STATE_RETRIEVING_AND_INJECTING = 1;
32     private static final int STATE_IDLE = 2;
33 
34     // how often to request NTP time, in milliseconds
35     // current setting 24 hours
36     @VisibleForTesting
37     static final long NTP_INTERVAL = 24 * 60 * 60 * 1000;
38 
39     // how long to wait if we have a network error in NTP
40     // the initial value of the exponential backoff
41     // current setting - 5 minutes
42     @VisibleForTesting
43     static final long RETRY_INTERVAL = 5 * 60 * 1000;
44     // how long to wait if we have a network error in NTP
45     // the max value of the exponential backoff
46     // current setting - 4 hours
47     private static final long MAX_RETRY_INTERVAL = 4 * 60 * 60 * 1000;
48 
49     private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
50     private static final String WAKELOCK_KEY = "NtpTimeHelper";
51 
52     private final ExponentialBackOff mNtpBackOff = new ExponentialBackOff(RETRY_INTERVAL,
53             MAX_RETRY_INTERVAL);
54 
55     private final ConnectivityManager mConnMgr;
56     private final NtpTrustedTime mNtpTime;
57     private final WakeLock mWakeLock;
58     private final Handler mHandler;
59 
60     @GuardedBy("this")
61     private final InjectNtpTimeCallback mCallback;
62 
63     // flags to trigger NTP when network becomes available
64     // initialized to STATE_PENDING_NETWORK so we do NTP when the network comes up after booting
65     @GuardedBy("this")
66     private int mInjectNtpTimeState = STATE_PENDING_NETWORK;
67 
68     // set to true if the GPS engine requested on-demand NTP time requests
69     @GuardedBy("this")
70     private boolean mOnDemandTimeInjection;
71 
72     interface InjectNtpTimeCallback {
injectTime(long time, long timeReference, int uncertainty)73         void injectTime(long time, long timeReference, int uncertainty);
74     }
75 
76     @VisibleForTesting
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback, NtpTrustedTime ntpTime)77     NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback,
78             NtpTrustedTime ntpTime) {
79         mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
80         mCallback = callback;
81         mNtpTime = ntpTime;
82         mHandler = new Handler(looper);
83         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
84         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
85     }
86 
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback)87     NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback) {
88         this(context, looper, callback, NtpTrustedTime.getInstance(context));
89     }
90 
enablePeriodicTimeInjection()91     synchronized void enablePeriodicTimeInjection() {
92         mOnDemandTimeInjection = true;
93     }
94 
onNetworkAvailable()95     synchronized void onNetworkAvailable() {
96         if (mInjectNtpTimeState == STATE_PENDING_NETWORK) {
97             retrieveAndInjectNtpTime();
98         }
99     }
100 
101     /**
102      * @return {@code true} if there is a network available for outgoing connections,
103      * {@code false} otherwise.
104      */
isNetworkConnected()105     private boolean isNetworkConnected() {
106         NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
107         return activeNetworkInfo != null && activeNetworkInfo.isConnected();
108     }
109 
retrieveAndInjectNtpTime()110     synchronized void retrieveAndInjectNtpTime() {
111         if (mInjectNtpTimeState == STATE_RETRIEVING_AND_INJECTING) {
112             // already downloading data
113             return;
114         }
115         if (!isNetworkConnected()) {
116             // try again when network is up
117             mInjectNtpTimeState = STATE_PENDING_NETWORK;
118             return;
119         }
120         mInjectNtpTimeState = STATE_RETRIEVING_AND_INJECTING;
121 
122         // hold wake lock while task runs
123         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
124         new Thread(this::blockingGetNtpTimeAndInject).start();
125     }
126 
127     /** {@link NtpTrustedTime#forceRefresh} is a blocking network operation. */
blockingGetNtpTimeAndInject()128     private void blockingGetNtpTimeAndInject() {
129         long delay;
130 
131         // force refresh NTP cache when outdated
132         boolean refreshSuccess = true;
133         NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
134         if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
135             // Blocking network operation.
136             refreshSuccess = mNtpTime.forceRefresh();
137         }
138 
139         synchronized (this) {
140             mInjectNtpTimeState = STATE_IDLE;
141 
142             // only update when NTP time is fresh
143             // If refreshSuccess is false, cacheAge does not drop down.
144             ntpResult = mNtpTime.getCachedTimeResult();
145             if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
146                 long time = ntpResult.getTimeMillis();
147                 long timeReference = ntpResult.getElapsedRealtimeMillis();
148                 long certainty = ntpResult.getCertaintyMillis();
149 
150                 if (DEBUG) {
151                     long now = System.currentTimeMillis();
152                     Log.d(TAG, "NTP server returned: "
153                             + time + " (" + new Date(time) + ")"
154                             + " ntpResult: " + ntpResult
155                             + " system time offset: " + (time - now));
156                 }
157 
158                 // Ok to cast to int, as can't rollover in practice
159                 mHandler.post(() -> mCallback.injectTime(time, timeReference, (int) certainty));
160 
161                 delay = NTP_INTERVAL;
162                 mNtpBackOff.reset();
163             } else {
164                 Log.e(TAG, "requestTime failed");
165                 delay = mNtpBackOff.nextBackoffMillis();
166             }
167 
168             if (DEBUG) {
169                 Log.d(TAG, String.format(
170                         "onDemandTimeInjection=%s, refreshSuccess=%s, delay=%s",
171                         mOnDemandTimeInjection,
172                         refreshSuccess,
173                         delay));
174             }
175             // TODO(b/73893222): reconcile Capabilities bit 'on demand' name vs. de facto periodic
176             // injection.
177             if (mOnDemandTimeInjection || !refreshSuccess) {
178                 /* Schedule next NTP injection.
179                  * Since this is delayed, the wake lock is released right away, and will be held
180                  * again when the delayed task runs.
181                  */
182                 mHandler.postDelayed(this::retrieveAndInjectNtpTime, delay);
183             }
184         }
185         try {
186             // release wake lock held by task
187             mWakeLock.release();
188         } catch (Exception e) {
189             // This happens when the WakeLock is already released.
190         }
191     }
192 }
193