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