diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java index b0d301e42634d..e9104ba8f5d37 100644 --- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java @@ -358,9 +358,30 @@ public class NetworkTimeUpdateService extends Binder { @NonNull private final LocalLog mLocalDebugLog = new LocalLog(30, false /* useLocalTimestamps */); + /** + * The usual interval between refresh attempts. Always used after a successful request. + * + *

The value also determines whether a network time result is considered fresh. + * Refreshes only take place from this class when the latest time result is considered too + * old. + */ private final int mNormalPollingIntervalMillis; + + /** + * A shortened interval between refresh attempts used after a failure to refresh. + * Always shorter than {@link #mNormalPollingIntervalMillis} and only used when {@link + * #mTryAgainTimesMax} != 0. + */ private final int mShortPollingIntervalMillis; + + /** + * The number of times {@link #mShortPollingIntervalMillis} can be used after successive + * failures before switching back to using {@link #mNormalPollingIntervalMillis} once before + * repeating. When this value is negative, the refresh algorithm will continue to use {@link + * #mShortPollingIntervalMillis} until a successful refresh. + */ private final int mTryAgainTimesMax; + private final NtpTrustedTime mNtpTrustedTime; /** @@ -378,6 +399,11 @@ public class NetworkTimeUpdateService extends Binder { int normalPollingIntervalMillis, int shortPollingIntervalMillis, int tryAgainTimesMax, @NonNull NtpTrustedTime ntpTrustedTime) { mElapsedRealtimeMillisSupplier = Objects.requireNonNull(elapsedRealtimeMillisSupplier); + if (shortPollingIntervalMillis > normalPollingIntervalMillis) { + throw new IllegalArgumentException(String.format( + "shortPollingIntervalMillis (%s) > normalPollingIntervalMillis (%s)", + shortPollingIntervalMillis, normalPollingIntervalMillis)); + } mNormalPollingIntervalMillis = normalPollingIntervalMillis; mShortPollingIntervalMillis = shortPollingIntervalMillis; mTryAgainTimesMax = tryAgainTimesMax; @@ -387,81 +413,121 @@ public class NetworkTimeUpdateService extends Binder { @Override public boolean forceRefreshForTests( @NonNull Network network, @NonNull RefreshCallbacks refreshCallbacks) { - boolean success = mNtpTrustedTime.forceRefresh(network); - logToDebugAndDumpsys("forceRefreshForTests: success=" + success); + boolean refreshSuccessful = mNtpTrustedTime.forceRefresh(network); + logToDebugAndDumpsys("forceRefreshForTests: refreshSuccessful=" + refreshSuccessful); - if (success) { + if (refreshSuccessful) { makeNetworkTimeSuggestion(mNtpTrustedTime.getCachedTimeResult(), "EngineImpl.forceRefreshForTests()", refreshCallbacks); } - return success; + return refreshSuccessful; } @Override public void refreshIfRequiredAndReschedule( @NonNull Network network, @NonNull String reason, @NonNull RefreshCallbacks refreshCallbacks) { - long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get(); - - final int maxNetworkTimeAgeMillis = mNormalPollingIntervalMillis; - // Force an NTP fix when outdated + // Attempt to refresh the network time if there is no latest time result, or if the + // latest time result is considered too old. NtpTrustedTime.TimeResult initialTimeResult = mNtpTrustedTime.getCachedTimeResult(); - if (calculateTimeResultAgeMillis(initialTimeResult, currentElapsedRealtimeMillis) - >= maxNetworkTimeAgeMillis) { - if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh using network=" + network); - boolean successful = mNtpTrustedTime.forceRefresh(network); - if (successful) { - synchronized (this) { - mTryAgainCounter = 0; - } - } else { - String logMsg = "forceRefresh() returned false:" - + " initialTimeResult=" + initialTimeResult - + ", currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis; - logToDebugAndDumpsys(logMsg); - } + boolean shouldAttemptRefresh; + synchronized (this) { + long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get(); + + // calculateTimeResultAgeMillis() safely handles a null initialTimeResult. + long timeResultAgeMillis = calculateTimeResultAgeMillis( + initialTimeResult, currentElapsedRealtimeMillis); + shouldAttemptRefresh = timeResultAgeMillis >= mNormalPollingIntervalMillis; + } + + boolean refreshSuccessful = false; + if (shouldAttemptRefresh) { + // This is a blocking call. Deliberately invoked without holding the "this" monitor + // to avoid blocking logic that wants to use the "this" monitor. + refreshSuccessful = mNtpTrustedTime.forceRefresh(network); } synchronized (this) { - long nextPollDelayMillis; - NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult(); - if (calculateTimeResultAgeMillis(latestTimeResult, currentElapsedRealtimeMillis) - < maxNetworkTimeAgeMillis) { - // Obtained fresh fix; schedule next normal update - nextPollDelayMillis = mNormalPollingIntervalMillis - - latestTimeResult.getAgeMillis(currentElapsedRealtimeMillis); - - makeNetworkTimeSuggestion(latestTimeResult, reason, refreshCallbacks); - } else { - // No fresh fix; schedule retry - mTryAgainCounter++; - if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) { - nextPollDelayMillis = mShortPollingIntervalMillis; - } else { - // Try much later + // Manage mTryAgainCounter. + if (shouldAttemptRefresh) { + if (refreshSuccessful) { + // Reset failure tracking. mTryAgainCounter = 0; - - nextPollDelayMillis = mNormalPollingIntervalMillis; + } else { + if (mTryAgainTimesMax < 0) { + // When mTryAgainTimesMax is negative there's no enforced maximum and + // short intervals should be used until a successful refresh. Setting + // mTryAgainCounter to 1 is sufficient for the interval calculations + // below. There's no need to increment. + mTryAgainCounter = 1; + } else { + mTryAgainCounter++; + if (mTryAgainCounter > mTryAgainTimesMax) { + mTryAgainCounter = 0; + } + } } } + + // currentElapsedRealtimeMillis is used to evaluate ages and refresh scheduling + // below. Capturing this after a possible successful refresh ensures that latest + // time result ages will be >= 0. + long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get(); + + // This section of code deliberately doesn't assume it is the only component using + // mNtpTrustedTime to obtain NTP times: another component in the same process could + // be gathering NTP signals (which then won't have been suggested to the time + // detector). + // TODO(b/222295093): Make this class the sole owner of mNtpTrustedTime and + // simplify / reduce duplicate suggestions. + NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult(); + long latestTimeResultAgeMillis = calculateTimeResultAgeMillis( + latestTimeResult, currentElapsedRealtimeMillis); + + // Suggest the latest time result to the time detector if it is fresh regardless of + // whether refresh happened above. + if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) { + // We assume the time detector service will detect duplicate suggestions and not + // do more work than it has to, so no need to avoid making duplicate + // suggestions. + makeNetworkTimeSuggestion(latestTimeResult, reason, refreshCallbacks); + } + + // (Re)schedule the next refresh based on the latest state. + // Determine which refresh delay to use by using the current value of + // mTryAgainCounter. + long refreshDelayMillis = mTryAgainCounter > 0 + ? mShortPollingIntervalMillis : mNormalPollingIntervalMillis; + // Adjust the next refresh time for the age of the latest time result. + if (latestTimeResultAgeMillis < refreshDelayMillis) { + refreshDelayMillis -= latestTimeResultAgeMillis; + } + long nextRefreshElapsedRealtimeMillis = - currentElapsedRealtimeMillis + nextPollDelayMillis; + currentElapsedRealtimeMillis + refreshDelayMillis; refreshCallbacks.scheduleNextRefresh(nextRefreshElapsedRealtimeMillis); logToDebugAndDumpsys("refreshIfRequiredAndReschedule:" + " network=" + network + ", reason=" + reason - + ", currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis + ", initialTimeResult=" + initialTimeResult + + ", shouldAttemptRefresh=" + shouldAttemptRefresh + + ", refreshSuccessful=" + refreshSuccessful + + ", currentElapsedRealtimeMillis=" + + formatElapsedRealtimeMillis(currentElapsedRealtimeMillis) + ", latestTimeResult=" + latestTimeResult + ", mTryAgainCounter=" + mTryAgainCounter - + ", nextPollDelayMillis=" + nextPollDelayMillis + + ", refreshDelayMillis=" + refreshDelayMillis + ", nextRefreshElapsedRealtimeMillis=" - + Duration.ofMillis(nextRefreshElapsedRealtimeMillis) - + " (" + nextRefreshElapsedRealtimeMillis + ")"); + + formatElapsedRealtimeMillis(nextRefreshElapsedRealtimeMillis)); } } + private static String formatElapsedRealtimeMillis( + @ElapsedRealtimeLong long elapsedRealtimeMillis) { + return Duration.ofMillis(elapsedRealtimeMillis) + " (" + elapsedRealtimeMillis + ")"; + } + private static long calculateTimeResultAgeMillis( @Nullable TimeResult timeResult, @ElapsedRealtimeLong long currentElapsedRealtimeMillis) { diff --git a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java index 08d08b65288a2..44c80f3f24dc9 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java @@ -74,10 +74,13 @@ public class NetworkTimeUpdateServiceTest { // Simulated NTP client behavior: No cached time value available initially, then a // successful refresh. NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() - 1); + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); // Trigger the engine's logic. engine.refreshIfRequiredAndReschedule(mDummyNetwork, "Test", mockCallback); @@ -86,10 +89,9 @@ public class NetworkTimeUpdateServiceTest { verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); // Check everything happened that was supposed to. - long expectedDelayMillis = calculateRefreshDelayMillisForTimeResult( - timeResult, normalPollingIntervalMillis); + long expectedDelayMillis = normalPollingIntervalMillis; verify(mockCallback).scheduleNextRefresh( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + timeResult.getElapsedRealtimeMillis() + expectedDelayMillis); NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult); verify(mockCallback).submitSuggestion(expectedSuggestion); @@ -108,6 +110,9 @@ public class NetworkTimeUpdateServiceTest { mMockNtpTrustedTime); for (int i = 0; i < tryAgainTimesMax + 1; i++) { + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + // Simulated NTP client behavior: No cached time value available and failure to refresh. when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false); @@ -140,7 +145,6 @@ public class NetworkTimeUpdateServiceTest { mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); int normalPollingIntervalMillis = 7777777; - int maxTimeResultAgeMillis = normalPollingIntervalMillis; int shortPollingIntervalMillis = 3333; int tryAgainTimesMax = 5; NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( @@ -149,7 +153,7 @@ public class NetworkTimeUpdateServiceTest { mMockNtpTrustedTime); NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() - 1); + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult); { @@ -158,6 +162,9 @@ public class NetworkTimeUpdateServiceTest { when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); // Trigger the engine's logic. @@ -167,17 +174,16 @@ public class NetworkTimeUpdateServiceTest { // initially. verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); - long expectedDelayMillis = calculateRefreshDelayMillisForTimeResult( - timeResult, normalPollingIntervalMillis); + long expectedDelayMillis = normalPollingIntervalMillis; verify(mockCallback).scheduleNextRefresh( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + timeResult.getElapsedRealtimeMillis() + expectedDelayMillis); verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion); reset(mMockNtpTrustedTime); } // Increment the current time by enough so that an attempt to refresh the time should be // made every time refreshIfRequiredAndReschedule() is called. - mFakeElapsedRealtimeClock.incrementMillis(maxTimeResultAgeMillis); + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis); // Test multiple follow-up calls. for (int i = 0; i < tryAgainTimesMax + 1; i++) { @@ -208,30 +214,37 @@ public class NetworkTimeUpdateServiceTest { verify(mockCallback, never()).submitSuggestion(any()); reset(mMockNtpTrustedTime); + + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); } } @Test - public void engineImpl_refreshIfRequiredAndReschedule_successFailSuccess() { + public void engineImpl_refreshIfRequiredAndReschedule_successThenFail_tryAgainTimesZero() { mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); int normalPollingIntervalMillis = 7777777; - int maxTimeResultAgeMillis = normalPollingIntervalMillis; int shortPollingIntervalMillis = 3333; - int tryAgainTimesMax = 5; + int tryAgainTimesMax = 0; NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( mFakeElapsedRealtimeClock, normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax, mMockNtpTrustedTime); - NtpTrustedTime.TimeResult timeResult1 = createNtpTimeResult( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() - 1); + NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); + NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult); + { // Simulated NTP client behavior: No cached time value available initially, with a // successful refresh. - when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult1); + when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); // Trigger the engine's logic. @@ -241,10 +254,159 @@ public class NetworkTimeUpdateServiceTest { // initially. verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); - long expectedDelayMillis = calculateRefreshDelayMillisForTimeResult( - timeResult1, normalPollingIntervalMillis); + long expectedDelayMillis = normalPollingIntervalMillis; + verify(mockCallback).scheduleNextRefresh( + timeResult.getElapsedRealtimeMillis() + expectedDelayMillis); + verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion); + reset(mMockNtpTrustedTime); + } + + // Increment the current time by enough so that an attempt to refresh the time should be + // made every time refreshIfRequiredAndReschedule() is called. + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis); + + // Test multiple follow-up calls. + for (int i = 0; i < 3; i++) { + // Simulated NTP client behavior: (Too old) cached time value available, unsuccessful + // refresh. + when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult); + when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false); + + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); + + // Trigger the engine's logic. + engine.refreshIfRequiredAndReschedule(mDummyNetwork, "Test", mockCallback); + + // Expect a refresh attempt each time as the cached network time is too old. + verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); + + // Check the scheduling. tryAgainTimesMax == 0, so the algorithm should start with + + long expectedDelayMillis = normalPollingIntervalMillis; verify(mockCallback).scheduleNextRefresh( mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + + // No valid time, no suggestion. + verify(mockCallback, never()).submitSuggestion(any()); + + reset(mMockNtpTrustedTime); + + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + } + } + + @Test + public void engineImpl_refreshIfRequiredAndReschedule_successThenFail_tryAgainTimesNegative() { + mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); + + int normalPollingIntervalMillis = 7777777; + int shortPollingIntervalMillis = 3333; + int tryAgainTimesMax = -1; + NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( + mFakeElapsedRealtimeClock, + normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax, + mMockNtpTrustedTime); + + NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); + NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult); + + { + // Simulated NTP client behavior: No cached time value available initially, with a + // successful refresh. + when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult); + when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); + + // Trigger the engine's logic. + engine.refreshIfRequiredAndReschedule(mDummyNetwork, "Test", mockCallback); + + // Expect the refresh attempt to have been made: there is no cached network time + // initially. + verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); + + long expectedDelayMillis = normalPollingIntervalMillis; + verify(mockCallback).scheduleNextRefresh( + timeResult.getElapsedRealtimeMillis() + expectedDelayMillis); + verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion); + reset(mMockNtpTrustedTime); + } + + // Increment the current time by enough so that an attempt to refresh the time should be + // made every time refreshIfRequiredAndReschedule() is called. + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis); + + // Test multiple follow-up calls. + for (int i = 0; i < 3; i++) { + // Simulated NTP client behavior: (Too old) cached time value available, unsuccessful + // refresh. + when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult); + when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false); + + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); + + // Trigger the engine's logic. + engine.refreshIfRequiredAndReschedule(mDummyNetwork, "Test", mockCallback); + + // Expect a refresh attempt each time as the cached network time is too old. + verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); + + // Check the scheduling. tryAgainTimesMax == -1, so it should always be + // shortPollingIntervalMillis. + long expectedDelayMillis = shortPollingIntervalMillis; + verify(mockCallback).scheduleNextRefresh( + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + + // No valid time, no suggestion. + verify(mockCallback, never()).submitSuggestion(any()); + + reset(mMockNtpTrustedTime); + + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + } + } + + @Test + public void engineImpl_refreshIfRequiredAndReschedule_successFailSuccess() { + mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); + + int normalPollingIntervalMillis = 7777777; + int shortPollingIntervalMillis = 3333; + int tryAgainTimesMax = 5; + NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( + mFakeElapsedRealtimeClock, + normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax, + mMockNtpTrustedTime); + + NtpTrustedTime.TimeResult timeResult1 = createNtpTimeResult( + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); + { + // Simulated NTP client behavior: No cached time value available initially, with a + // successful refresh. + when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult1); + when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); + + // Trigger the engine's logic. + engine.refreshIfRequiredAndReschedule(mDummyNetwork, "Test", mockCallback); + + // Expect the refresh attempt to have been made: there is no cached network time + // initially. + verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); + + long expectedDelayMillis = normalPollingIntervalMillis; + verify(mockCallback).scheduleNextRefresh( + timeResult1.getElapsedRealtimeMillis() + expectedDelayMillis); NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1); verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion); reset(mMockNtpTrustedTime); @@ -253,7 +415,7 @@ public class NetworkTimeUpdateServiceTest { // Increment the current time by enough so that the cached time result is too old and an // attempt to refresh the time should be made every time refreshIfRequiredAndReschedule() is // called. - mFakeElapsedRealtimeClock.incrementMillis(maxTimeResultAgeMillis); + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis); { // Simulated NTP client behavior: (Old) cached time value available initially, with an @@ -278,8 +440,11 @@ public class NetworkTimeUpdateServiceTest { reset(mMockNtpTrustedTime); } + // Increment time enough to avoid the minimum refresh interval protection. + mFakeElapsedRealtimeClock.incrementMillis(shortPollingIntervalMillis); + NtpTrustedTime.TimeResult timeResult2 = createNtpTimeResult( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() - 1); + mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); { // Simulated NTP client behavior: (Old) cached time value available initially, with a @@ -287,6 +452,9 @@ public class NetworkTimeUpdateServiceTest { when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult1, timeResult2); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true); + // Simulate the passage of time for realism. + mFakeElapsedRealtimeClock.incrementMillis(5000); + RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); // Trigger the engine's logic. @@ -295,10 +463,9 @@ public class NetworkTimeUpdateServiceTest { // Expect the refresh attempt to have been made: the timeResult is too old. verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork); - long expectedDelayMillis = calculateRefreshDelayMillisForTimeResult( - timeResult2, normalPollingIntervalMillis); + long expectedDelayMillis = normalPollingIntervalMillis; verify(mockCallback).scheduleNextRefresh( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + timeResult2.getElapsedRealtimeMillis() + expectedDelayMillis); NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2); verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion); reset(mMockNtpTrustedTime); @@ -311,11 +478,10 @@ public class NetworkTimeUpdateServiceTest { * A suggestion will still be made. */ @Test - public void engineImpl_refreshIfRequiredAndReschedule_noRefreshIfLatestIsNotTooOld() { + public void engineImpl_refreshIfRequiredAndReschedule_noRefreshIfLatestIsFresh() { mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); int normalPollingIntervalMillis = 7777777; - int maxTimeResultAgeMillis = normalPollingIntervalMillis; int shortPollingIntervalMillis = 3333; int tryAgainTimesMax = 5; NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( @@ -323,12 +489,12 @@ public class NetworkTimeUpdateServiceTest { normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax, mMockNtpTrustedTime); - // Simulated NTP client behavior: A cached time value is available, increment the clock, but - // not enough to consider the cached value too old. + // Simulated NTP client behavior: A cached time value is available. NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult); - mFakeElapsedRealtimeClock.incrementMillis(maxTimeResultAgeMillis - 1); + // Increment the clock, but not enough to consider the cached value too old. + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis - 1); RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); // Trigger the engine's logic. @@ -339,10 +505,9 @@ public class NetworkTimeUpdateServiceTest { // The next wake-up should be rescheduled for when the cached time value will become too // old. - long expectedDelayMillis = calculateRefreshDelayMillisForTimeResult(timeResult, - normalPollingIntervalMillis); + long expectedDelayMillis = normalPollingIntervalMillis; verify(mockCallback).scheduleNextRefresh( - mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis); + timeResult.getElapsedRealtimeMillis() + expectedDelayMillis); // Suggestions must be made every time if the cached time value is not too old in case it // was refreshed by a different component. @@ -352,15 +517,13 @@ public class NetworkTimeUpdateServiceTest { /** * Confirms that if a refreshIfRequiredAndReschedule() call is made, e.g. for reasons besides - * scheduled alerts, and the latest time is not too old, then an NTP refresh won't be attempted. - * A suggestion will still be made. + * scheduled alerts, and the latest time is too old, then an NTP refresh will be attempted. */ @Test public void engineImpl_refreshIfRequiredAndReschedule_failureHandlingAfterLatestIsTooOld() { mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS); int normalPollingIntervalMillis = 7777777; - int maxTimeResultAgeMillis = normalPollingIntervalMillis; int shortPollingIntervalMillis = 3333; int tryAgainTimesMax = 5; NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl( @@ -373,7 +536,7 @@ public class NetworkTimeUpdateServiceTest { NtpTrustedTime.TimeResult timeResult = createNtpTimeResult( mFakeElapsedRealtimeClock.getElapsedRealtimeMillis()); when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult); - mFakeElapsedRealtimeClock.incrementMillis(maxTimeResultAgeMillis); + mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis); when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false); RefreshCallbacks mockCallback = mock(RefreshCallbacks.class); @@ -392,13 +555,6 @@ public class NetworkTimeUpdateServiceTest { verify(mockCallback, never()).submitSuggestion(any()); } - private long calculateRefreshDelayMillisForTimeResult(NtpTrustedTime.TimeResult timeResult, - int normalPollingIntervalMillis) { - long currentElapsedRealtimeMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis(); - long timeResultAgeMillis = timeResult.getAgeMillis(currentElapsedRealtimeMillis); - return normalPollingIntervalMillis - timeResultAgeMillis; - } - private static NetworkTimeSuggestion createExpectedSuggestion( NtpTrustedTime.TimeResult timeResult) { UnixEpochTime unixEpochTime = new UnixEpochTime(