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