Java程序  |  192行  |  6.95 KB

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