• 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     private final InjectNtpTimeCallback mCallback;
77 
78     // flags to trigger NTP when network becomes available
79     // initialized to STATE_PENDING_NETWORK so we do NTP when the network comes up after booting
80     @GuardedBy("this")
81     private int mInjectNtpTimeState = STATE_PENDING_NETWORK;
82 
83     // set to true if the GPS engine requested on-demand NTP time requests
84     @GuardedBy("this")
85     private boolean mOnDemandTimeInjection;
86 
87     interface InjectNtpTimeCallback {
injectTime(long time, long timeReference, int uncertainty)88         void injectTime(long time, long timeReference, int uncertainty);
89     }
90 
91     @VisibleForTesting
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback, NtpTrustedTime ntpTime)92     NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback,
93             NtpTrustedTime ntpTime) {
94         mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
95         mCallback = callback;
96         mNtpTime = ntpTime;
97         mHandler = new Handler(looper);
98         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
99         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
100     }
101 
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback)102     NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback) {
103         this(context, looper, callback, NtpTrustedTime.getInstance(context));
104     }
105 
enablePeriodicTimeInjection()106     synchronized void enablePeriodicTimeInjection() {
107         mOnDemandTimeInjection = true;
108     }
109 
onNetworkAvailable()110     synchronized void onNetworkAvailable() {
111         if (mInjectNtpTimeState == STATE_PENDING_NETWORK) {
112             retrieveAndInjectNtpTime();
113         }
114     }
115 
116     /**
117      * @return {@code true} if there is a network available for outgoing connections,
118      * {@code false} otherwise.
119      */
isNetworkConnected()120     private boolean isNetworkConnected() {
121         NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
122         return activeNetworkInfo != null && activeNetworkInfo.isConnected();
123     }
124 
retrieveAndInjectNtpTime()125     synchronized void retrieveAndInjectNtpTime() {
126         if (mInjectNtpTimeState == STATE_RETRIEVING_AND_INJECTING) {
127             // already downloading data
128             return;
129         }
130         if (!isNetworkConnected()) {
131             // try to inject the cached NTP time
132             injectCachedNtpTime();
133             // try again when network is up
134             mInjectNtpTimeState = STATE_PENDING_NETWORK;
135             return;
136         }
137         mInjectNtpTimeState = STATE_RETRIEVING_AND_INJECTING;
138 
139         // hold wake lock while task runs
140         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
141         new Thread(this::blockingGetNtpTimeAndInject).start();
142     }
143 
144     /** {@link NtpTrustedTime#forceRefresh} is a blocking network operation. */
blockingGetNtpTimeAndInject()145     private void blockingGetNtpTimeAndInject() {
146         long delay;
147 
148         // force refresh NTP cache when outdated
149         boolean refreshSuccess = true;
150         NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
151         if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
152             // Blocking network operation.
153             refreshSuccess = mNtpTime.forceRefresh();
154         }
155 
156         synchronized (this) {
157             mInjectNtpTimeState = STATE_IDLE;
158 
159             // only update when NTP time is fresh
160             // If refreshSuccess is false, cacheAge does not drop down.
161             if (injectCachedNtpTime()) {
162                 delay = NTP_INTERVAL;
163                 mNtpBackOff.reset();
164             } else {
165                 Log.e(TAG, "requestTime failed");
166                 delay = mNtpBackOff.nextBackoffMillis();
167             }
168 
169             if (DEBUG) {
170                 Log.d(TAG, String.format(
171                         "onDemandTimeInjection=%s, refreshSuccess=%s, delay=%s",
172                         mOnDemandTimeInjection,
173                         refreshSuccess,
174                         delay));
175             }
176             // TODO(b/73893222): reconcile Capabilities bit 'on demand' name vs. de facto periodic
177             // injection.
178             if (mOnDemandTimeInjection || !refreshSuccess) {
179                 /* Schedule next NTP injection.
180                  * Since this is delayed, the wake lock is released right away, and will be held
181                  * again when the delayed task runs.
182                  */
183                 mHandler.postDelayed(this::retrieveAndInjectNtpTime, delay);
184             }
185         }
186         // release wake lock held by task
187         mWakeLock.release();
188     }
189 
190     /** Returns true if successfully inject cached NTP time. */
injectCachedNtpTime()191     private synchronized boolean injectCachedNtpTime() {
192         NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
193         if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
194             return false;
195         }
196 
197         long time = ntpResult.getTimeMillis();
198         long timeReference = ntpResult.getElapsedRealtimeMillis();
199         long certainty = ntpResult.getCertaintyMillis();
200 
201         if (DEBUG) {
202             long now = System.currentTimeMillis();
203             Log.d(TAG, "NTP server returned: " + time + " (" + new Date(time) + ")"
204                     + " ntpResult: " + ntpResult + " system time offset: " + (time - now));
205         }
206 
207         // Ok to cast to int, as can't rollover in practice
208         mHandler.post(() -> mCallback.injectTime(time, timeReference, (int) certainty));
209         return true;
210     }
211 }
212