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