content: 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.
          }
      }
  }
  