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