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         if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
134             // Blocking network operation.
135             refreshSuccess = mNtpTime.forceRefresh();
136         }
137 
138         synchronized (this) {
139             mInjectNtpTimeState = STATE_IDLE;
140 
141             // only update when NTP time is fresh
142             // If refreshSuccess is false, cacheAge does not drop down.
143             if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
144                 long time = mNtpTime.getCachedNtpTime();
145                 long timeReference = mNtpTime.getCachedNtpTimeReference();
146                 long certainty = mNtpTime.getCacheCertainty();
147 
148                 if (DEBUG) {
149                     long now = System.currentTimeMillis();
150                     Log.d(TAG, "NTP server returned: "
151                             + time + " (" + new Date(time)
152                             + ") reference: " + timeReference
153                             + " certainty: " + certainty
154                             + " system time offset: " + (time - now));
155                 }
156 
157                 // Ok to cast to int, as can't rollover in practice
158                 mHandler.post(() -> mCallback.injectTime(time, timeReference, (int) certainty));
159 
160                 delay = NTP_INTERVAL;
161                 mNtpBackOff.reset();
162             } else {
163                 Log.e(TAG, "requestTime failed");
164                 delay = mNtpBackOff.nextBackoffMillis();
165             }
166 
167             if (DEBUG) {
168                 Log.d(TAG, String.format(
169                         "onDemandTimeInjection=%s, refreshSuccess=%s, delay=%s",
170                         mOnDemandTimeInjection,
171                         refreshSuccess,
172                         delay));
173             }
174             // TODO(b/73893222): reconcile Capabilities bit 'on demand' name vs. de facto periodic
175             // injection.
176             if (mOnDemandTimeInjection || !refreshSuccess) {
177                 /* Schedule next NTP injection.
178                  * Since this is delayed, the wake lock is released right away, and will be held
179                  * again when the delayed task runs.
180                  */
181                 mHandler.postDelayed(this::retrieveAndInjectNtpTime, delay);
182             }
183         }
184         try {
185             // release wake lock held by task
186             mWakeLock.release();
187         } catch (Exception e) {
188             // This happens when the WakeLock is already released.
189         }
190     }
191 }
192