Loading services/core/java/com/android/server/location/ExponentialBackOff.java 0 → 100644 +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; } } services/core/java/com/android/server/location/GnssLocationProvider.java +22 −129 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -880,6 +868,8 @@ public class GnssLocationProvider implements LocationProviderInterface { } }; mGnssMetrics = new GnssMetrics(mBatteryStats); mNtpTimeHelper = new NtpTimeHelper(mContext, Looper.myLooper(), this); } /** Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -2006,7 +1933,7 @@ public class GnssLocationProvider implements LocationProviderInterface { mEngineCapabilities = capabilities; if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { mOnDemandTimeInjection = true; mNtpTimeHelper.enablePeriodicTimeInjection(); requestUtcTime(); } Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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: Loading Loading @@ -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]; Loading Loading @@ -3022,3 +2914,4 @@ public class GnssLocationProvider implements LocationProviderInterface { private static native void native_cleanup_batching(); } services/core/java/com/android/server/location/NtpTimeHelper.java 0 → 100644 +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. } } } services/robotests/src/com/android/server/location/NtpTimeHelperTest.java 0 → 100644 +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(); } } Loading
services/core/java/com/android/server/location/ExponentialBackOff.java 0 → 100644 +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; } }
services/core/java/com/android/server/location/GnssLocationProvider.java +22 −129 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -880,6 +868,8 @@ public class GnssLocationProvider implements LocationProviderInterface { } }; mGnssMetrics = new GnssMetrics(mBatteryStats); mNtpTimeHelper = new NtpTimeHelper(mContext, Looper.myLooper(), this); } /** Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -2006,7 +1933,7 @@ public class GnssLocationProvider implements LocationProviderInterface { mEngineCapabilities = capabilities; if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { mOnDemandTimeInjection = true; mNtpTimeHelper.enablePeriodicTimeInjection(); requestUtcTime(); } Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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: Loading Loading @@ -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]; Loading Loading @@ -3022,3 +2914,4 @@ public class GnssLocationProvider implements LocationProviderInterface { private static native void native_cleanup_batching(); }
services/core/java/com/android/server/location/NtpTimeHelper.java 0 → 100644 +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. } } }
services/robotests/src/com/android/server/location/NtpTimeHelperTest.java 0 → 100644 +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(); } }