blob: 296b500eaceea0bf8938ec9cc0b6c475b1173542 [file] [log] [blame]
package com.android.server.location;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.util.NtpTrustedTime;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Date;
/**
* Handles inject NTP time to GNSS.
*
* <p>The client is responsible to call {@link #onNetworkAvailable()} when network is available
* for retrieving NTP Time.
*/
class NtpTimeHelper {
private static final String TAG = "NtpTimeHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// states for injecting ntp
private static final int STATE_PENDING_NETWORK = 0;
private static final int STATE_RETRIEVING_AND_INJECTING = 1;
private static final int STATE_IDLE = 2;
// how often to request NTP time, in milliseconds
// current setting 24 hours
@VisibleForTesting
static final long NTP_INTERVAL = 24 * 60 * 60 * 1000;
// how long to wait if we have a network error in NTP
// the initial value of the exponential backoff
// current setting - 5 minutes
@VisibleForTesting
static final long RETRY_INTERVAL = 5 * 60 * 1000;
// how long to wait if we have a network error in NTP
// the max value of the exponential backoff
// current setting - 4 hours
private static final long MAX_RETRY_INTERVAL = 4 * 60 * 60 * 1000;
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
private static final String WAKELOCK_KEY = "NtpTimeHelper";
private final ExponentialBackOff mNtpBackOff = new ExponentialBackOff(RETRY_INTERVAL,
MAX_RETRY_INTERVAL);
private final ConnectivityManager mConnMgr;
private final NtpTrustedTime mNtpTime;
private final WakeLock mWakeLock;
private final Handler mHandler;
@GuardedBy("this")
private final InjectNtpTimeCallback mCallback;
// flags to trigger NTP when network becomes available
// initialized to STATE_PENDING_NETWORK so we do NTP when the network comes up after booting
@GuardedBy("this")
private int mInjectNtpTimeState = STATE_PENDING_NETWORK;
// set to true if the GPS engine requested on-demand NTP time requests
@GuardedBy("this")
private boolean mOnDemandTimeInjection;
interface InjectNtpTimeCallback {
void injectTime(long time, long timeReference, int uncertainty);
}
@VisibleForTesting
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback,
NtpTrustedTime ntpTime) {
mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mCallback = callback;
mNtpTime = ntpTime;
mHandler = new Handler(looper);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
}
NtpTimeHelper(Context context, Looper looper, InjectNtpTimeCallback callback) {
this(context, looper, callback, NtpTrustedTime.getInstance(context));
}
synchronized void enablePeriodicTimeInjection() {
mOnDemandTimeInjection = true;
}
synchronized void onNetworkAvailable() {
if (mInjectNtpTimeState == STATE_PENDING_NETWORK) {
retrieveAndInjectNtpTime();
}
}
/**
* @return {@code true} if there is a network available for outgoing connections,
* {@code false} otherwise.
*/
private boolean isNetworkConnected() {
NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
synchronized void retrieveAndInjectNtpTime() {
if (mInjectNtpTimeState == STATE_RETRIEVING_AND_INJECTING) {
// already downloading data
return;
}
if (!isNetworkConnected()) {
// try again when network is up
mInjectNtpTimeState = STATE_PENDING_NETWORK;
return;
}
mInjectNtpTimeState = STATE_RETRIEVING_AND_INJECTING;
// hold wake lock while task runs
mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
new Thread(this::blockingGetNtpTimeAndInject).start();
}
/** {@link NtpTrustedTime#forceRefresh} is a blocking network operation. */
private void blockingGetNtpTimeAndInject() {
long delay;
// force refresh NTP cache when outdated
boolean refreshSuccess = true;
if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
// Blocking network operation.
refreshSuccess = mNtpTime.forceRefresh();
}
synchronized (this) {
mInjectNtpTimeState = STATE_IDLE;
// only update when NTP time is fresh
// If refreshSuccess is false, cacheAge does not drop down.
if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
long time = mNtpTime.getCachedNtpTime();
long timeReference = mNtpTime.getCachedNtpTimeReference();
long certainty = mNtpTime.getCacheCertainty();
if (DEBUG) {
long now = System.currentTimeMillis();
Log.d(TAG, "NTP server returned: "
+ time + " (" + new Date(time)
+ ") reference: " + timeReference
+ " certainty: " + certainty
+ " system time offset: " + (time - now));
}
// Ok to cast to int, as can't rollover in practice
mHandler.post(() -> mCallback.injectTime(time, timeReference, (int) certainty));
delay = NTP_INTERVAL;
mNtpBackOff.reset();
} else {
Log.e(TAG, "requestTime failed");
delay = mNtpBackOff.nextBackoffMillis();
}
if (DEBUG) {
Log.d(TAG, String.format(
"onDemandTimeInjection=%s, refreshSuccess=%s, delay=%s",
mOnDemandTimeInjection,
refreshSuccess,
delay));
}
// TODO(b/73893222): reconcile Capabilities bit 'on demand' name vs. de facto periodic
// injection.
if (mOnDemandTimeInjection || !refreshSuccess) {
/* Schedule next NTP injection.
* Since this is delayed, the wake lock is released right away, and will be held
* again when the delayed task runs.
*/
mHandler.postDelayed(this::retrieveAndInjectNtpTime, delay);
}
}
try {
// release wake lock held by task
mWakeLock.release();
} catch (Exception e) {
// This happens when the WakeLock is already released.
}
}
}