diff options
| author | 2018-04-05 00:51:32 +0000 | |
|---|---|---|
| committer | 2018-04-05 00:51:32 +0000 | |
| commit | 93a10eb1d2535f2ab9c3edfa10bb31e439a825e9 (patch) | |
| tree | 20d4b2cd86d8acbcdcb7afdb615d1df378b98d8e | |
| parent | f6486683fa62a6fd81862a4f440ea40a34a9ff85 (diff) | |
| parent | a1862b587f05b6ba36eda81a67748baaf66afc48 (diff) | |
Merge "Add NtpTimeHelper" into pi-dev
4 files changed, 345 insertions, 129 deletions
diff --git a/services/core/java/com/android/server/location/ExponentialBackOff.java b/services/core/java/com/android/server/location/ExponentialBackOff.java new file mode 100644 index 000000000000..8c77b2176b74 --- /dev/null +++ b/services/core/java/com/android/server/location/ExponentialBackOff.java @@ -0,0 +1,32 @@ +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; + } +} + diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 5955c9c3dd0a..5ba738093c6d 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -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); @@ -1014,79 +1013,7 @@ public class GnssLocationProvider implements LocationProviderInterface { Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus); } } - - 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(); } + diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java new file mode 100644 index 000000000000..296b500eacee --- /dev/null +++ b/services/core/java/com/android/server/location/NtpTimeHelper.java @@ -0,0 +1,191 @@ +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. + } + } +} diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java new file mode 100644 index 000000000000..a68b5795235b --- /dev/null +++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java @@ -0,0 +1,100 @@ +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(); + } +} + |