Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee4718b4 authored by Yu-Han Yang's avatar Yu-Han Yang Committed by android-build-merger
Browse files

Merge "Add NtpTimeHelper" into pi-dev

am: 93a10eb1

Change-Id: I06ec09f1976e91e5efcb00ca5062be67e20b35b1
parents 1378c6e3 93a10eb1
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
package com.android.server.location;

/**
 * A simple implementation of exponential backoff.
 */
class ExponentialBackOff {
    private static final int MULTIPLIER = 2;
    private final long mInitIntervalMillis;
    private final long mMaxIntervalMillis;
    private long mCurrentIntervalMillis;

    ExponentialBackOff(long initIntervalMillis, long maxIntervalMillis) {
        mInitIntervalMillis = initIntervalMillis;
        mMaxIntervalMillis = maxIntervalMillis;

        mCurrentIntervalMillis = mInitIntervalMillis / MULTIPLIER;
    }

    long nextBackoffMillis() {
        if (mCurrentIntervalMillis > mMaxIntervalMillis) {
            return mMaxIntervalMillis;
        }

        mCurrentIntervalMillis *= MULTIPLIER;
        return mCurrentIntervalMillis;
    }

    void reset() {
        mCurrentIntervalMillis = mInitIntervalMillis / MULTIPLIER;
    }
}
+22 −129
Original line number Diff line number Diff line
@@ -76,14 +76,14 @@ import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Log;
import android.util.NtpTrustedTime;
import com.android.internal.app.IAppOpsService;

import com.android.internal.app.IBatteryStats;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -93,7 +93,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -107,7 +106,7 @@ import libcore.io.IoUtils;
 *
 * {@hide}
 */
public class GnssLocationProvider implements LocationProviderInterface {
public class GnssLocationProvider implements LocationProviderInterface, InjectNtpTimeCallback {

    private static final String TAG = "GnssLocationProvider";

@@ -208,7 +207,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
    private static final int UPDATE_LOCATION = 7;  // Handle external location from network listener
    private static final int ADD_LISTENER = 8;
    private static final int REMOVE_LISTENER = 9;
    private static final int INJECT_NTP_TIME_FINISHED = 10;
    private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11;
    private static final int SUBSCRIPTION_OR_SIM_CHANGED = 12;
    private static final int INITIALIZE_HANDLER = 13;
@@ -329,9 +327,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
    // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane.
    private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000;

    // how often to request NTP time, in milliseconds
    // current setting 24 hours
    private static final long NTP_INTERVAL = 24 * 60 * 60 * 1000;
    // how long to wait if we have a network error in NTP or XTRA downloading
    // the initial value of the exponential backoff
    // current setting - 5 minutes
@@ -344,8 +339,8 @@ public class GnssLocationProvider implements LocationProviderInterface {
    // Timeout when holding wakelocks for downloading XTRA data.
    private static final long DOWNLOAD_XTRA_DATA_TIMEOUT_MS = 60 * 1000;

    private BackOff mNtpBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL);
    private BackOff mXtraBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL);
    private final ExponentialBackOff mXtraBackOff = new ExponentialBackOff(RETRY_INTERVAL,
            MAX_RETRY_INTERVAL);

    // true if we are enabled, protected by this
    private boolean mEnabled;
@@ -357,12 +352,8 @@ public class GnssLocationProvider implements LocationProviderInterface {

    // flags to trigger NTP or XTRA data download when network becomes available
    // initialized to true so we do NTP and XTRA when the network comes up after booting
    private int mInjectNtpTimePending = STATE_PENDING_NETWORK;
    private int mDownloadXtraDataPending = STATE_PENDING_NETWORK;

    // set to true if the GPS engine requested on-demand NTP time requests
    private boolean mOnDemandTimeInjection;

    // true if GPS is navigating
    private boolean mNavigating;

@@ -417,7 +408,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
    private boolean mSuplEsEnabled = false;

    private final Context mContext;
    private final NtpTrustedTime mNtpTime;
    private final ILocationManager mILocationManager;
    private final LocationExtras mLocationExtras = new LocationExtras();
    private final GnssStatusListenerHelper mListenerHelper;
@@ -425,6 +415,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
    private final GnssNavigationMessageProvider mGnssNavigationMessageProvider;
    private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener();
    private final LocationChangeListener mFusedLocationListener = new FusedLocationListener();
    private final NtpTimeHelper mNtpTimeHelper;

    // Handler for processing events
    private Handler mHandler;
@@ -517,9 +508,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
            new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    if (mInjectNtpTimePending == STATE_PENDING_NETWORK) {
                        requestUtcTime();
                    }
                    mNtpTimeHelper.onNetworkAvailable();
                    if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) {
                        if (mSupportsXtra) {
                            // Download only if supported, (prevents an unneccesary on-boot
@@ -762,7 +751,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
    public GnssLocationProvider(Context context, ILocationManager ilocationManager,
            Looper looper) {
        mContext = context;
        mNtpTime = NtpTrustedTime.getInstance(context);
        mILocationManager = ilocationManager;

        // Create a wake lock
@@ -880,6 +868,8 @@ public class GnssLocationProvider implements LocationProviderInterface {
            }
        };
        mGnssMetrics = new GnssMetrics(mBatteryStats);

        mNtpTimeHelper = new NtpTimeHelper(mContext, Looper.myLooper(), this);
    }

    /**
@@ -895,6 +885,15 @@ public class GnssLocationProvider implements LocationProviderInterface {
        return PROPERTIES;
    }


    /**
     * Implements {@link InjectNtpTimeCallback#injectTime}
     */
    @Override
    public void injectTime(long time, long timeReference, int uncertainty) {
        native_inject_time(time, timeReference, uncertainty);
    }

    private void handleUpdateNetworkState(Network network) {
        // retrieve NetworkInfo for this UID
        NetworkInfo info = mConnMgr.getNetworkInfo(network);
@@ -1015,78 +1014,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
        }
    }
    
    private void handleInjectNtpTime() {
        if (mInjectNtpTimePending == STATE_DOWNLOADING) {
            // already downloading data
            return;
        }
        if (!isDataNetworkConnected()) {
            // try again when network is up
            mInjectNtpTimePending = STATE_PENDING_NETWORK;
            return;
        }
        mInjectNtpTimePending = STATE_DOWNLOADING;

        // hold wake lock while task runs
        mWakeLock.acquire();
        Log.i(TAG, "WakeLock acquired by handleInjectNtpTime()");
        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                long delay;

                // force refresh NTP cache when outdated
                boolean refreshSuccess = true;
                if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
                    refreshSuccess = mNtpTime.forceRefresh();
                }

                // only update when NTP time is fresh
                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));
                    }

                    native_inject_time(time, timeReference, (int) certainty);
                    delay = NTP_INTERVAL;
                    mNtpBackOff.reset();
                } else {
                    Log.e(TAG, "requestTime failed");
                    delay = mNtpBackOff.nextBackoffMillis();
                }

                sendMessage(INJECT_NTP_TIME_FINISHED, 0, null);

                if (DEBUG) {
                    String message = String.format(
                            "onDemandTimeInjection=%s, refreshSuccess=%s, delay=%s",
                            mOnDemandTimeInjection,
                            refreshSuccess,
                            delay);
                    Log.d(TAG, message);
                }
                if (mOnDemandTimeInjection || !refreshSuccess) {
                    // send delayed message for next NTP injection
                    // since this is delayed and not urgent we do not hold a wake lock here
                    mHandler.sendEmptyMessageDelayed(INJECT_NTP_TIME, delay);
                }

                // release wake lock held by task
                mWakeLock.release();
                Log.i(TAG, "WakeLock released by handleInjectNtpTime()");
            }
        });
    }

    private void handleRequestLocation(boolean independentFromGnss) {
        if (isRequestLocationRateLimited()) {
            if (DEBUG) {
@@ -2006,7 +1933,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
                mEngineCapabilities = capabilities;

                if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
                    mOnDemandTimeInjection = true;
                    mNtpTimeHelper.enablePeriodicTimeInjection();
                    requestUtcTime();
                }

@@ -2467,7 +2394,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
                    handleReleaseSuplConnection(msg.arg1);
                    break;
                case INJECT_NTP_TIME:
                    handleInjectNtpTime();
                    mNtpTimeHelper.retrieveAndInjectNtpTime();
                    break;
                case REQUEST_LOCATION:
                    handleRequestLocation((boolean) msg.obj);
@@ -2475,9 +2402,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
                case DOWNLOAD_XTRA_DATA:
                    handleDownloadXtraData();
                    break;
                case INJECT_NTP_TIME_FINISHED:
                    mInjectNtpTimePending = STATE_IDLE;
                    break;
                case DOWNLOAD_XTRA_DATA_FINISHED:
                    mDownloadXtraDataPending = STATE_IDLE;
                    break;
@@ -2808,8 +2732,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
                return "REQUEST_LOCATION";
            case DOWNLOAD_XTRA_DATA:
                return "DOWNLOAD_XTRA_DATA";
            case INJECT_NTP_TIME_FINISHED:
                return "INJECT_NTP_TIME_FINISHED";
            case DOWNLOAD_XTRA_DATA_FINISHED:
                return "DOWNLOAD_XTRA_DATA_FINISHED";
            case UPDATE_LOCATION:
@@ -2856,36 +2778,6 @@ public class GnssLocationProvider implements LocationProviderInterface {
        pw.append(s);
    }

    /**
     * A simple implementation of exponential backoff.
     */
    private static final class BackOff {
        private static final int MULTIPLIER = 2;
        private final long mInitIntervalMillis;
        private final long mMaxIntervalMillis;
        private long mCurrentIntervalMillis;

        public BackOff(long initIntervalMillis, long maxIntervalMillis) {
            mInitIntervalMillis = initIntervalMillis;
            mMaxIntervalMillis = maxIntervalMillis;

            mCurrentIntervalMillis = mInitIntervalMillis / MULTIPLIER;
        }

        public long nextBackoffMillis() {
            if (mCurrentIntervalMillis > mMaxIntervalMillis) {
                return mMaxIntervalMillis;
            }

            mCurrentIntervalMillis *= MULTIPLIER;
            return mCurrentIntervalMillis;
        }

        public void reset() {
            mCurrentIntervalMillis = mInitIntervalMillis / MULTIPLIER;
        }
    }

    // preallocated to avoid memory allocation in reportNmea()
    private byte[] mNmeaBuffer = new byte[120];

@@ -3022,3 +2914,4 @@ public class GnssLocationProvider implements LocationProviderInterface {
    private static native void native_cleanup_batching();

}
+191 −0
Original line number Diff line number Diff line
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.
        }
    }
}
+100 −0
Original line number Diff line number Diff line
package com.android.server.location;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;

import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.util.NtpTrustedTime;

import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowSystemClock;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Unit tests for {@link NtpTimeHelper}.
 */
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        sdk = 27
)
@SystemLoaderPackages({"com.android.server.location"})
@Presubmit
public class NtpTimeHelperTest {

    private static final long MOCK_NTP_TIME = 1519930775453L;
    @Mock
    private NtpTrustedTime mMockNtpTrustedTime;
    private NtpTimeHelper mNtpTimeHelper;
    private CountDownLatch mCountDownLatch;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mCountDownLatch = new CountDownLatch(1);
        InjectNtpTimeCallback callback =
                (time, timeReference, uncertainty) -> {
                    assertThat(time).isEqualTo(MOCK_NTP_TIME);
                    mCountDownLatch.countDown();
                };
        mNtpTimeHelper = new NtpTimeHelper(RuntimeEnvironment.application,
                Looper.myLooper(),
                callback, mMockNtpTrustedTime);
    }

    @Test
    public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
        doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();

        mNtpTimeHelper.retrieveAndInjectNtpTime();

        waitForTasksToBePostedOnHandlerAndRunThem();
        assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue();
    }

    @Test
    public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
            throws InterruptedException {
        doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
        doReturn(false).when(mMockNtpTrustedTime).forceRefresh();

        mNtpTimeHelper.retrieveAndInjectNtpTime();
        waitForTasksToBePostedOnHandlerAndRunThem();
        assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();

        doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
        doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
        ShadowSystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);

        waitForTasksToBePostedOnHandlerAndRunThem();
        assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue();
    }

    /**
     * Since a thread is created in {@link NtpTimeHelper#retrieveAndInjectNtpTime} and the task to
     * be verified is posted in the thread, we have to wait for the task to be posted and then it
     * can be run.
     */
    private void waitForTasksToBePostedOnHandlerAndRunThem() throws InterruptedException {
        mCountDownLatch.await(1, TimeUnit.SECONDS);
        ShadowLooper.runUiThreadTasks();
    }
}