blob: 2248ddb9e69ae0f0e9ce704020756985e4a08867 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.timedetector;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;
@RunWith(AndroidJUnit4.class)
public class TimeDetectorStrategyImplTest {
private static final Instant TIME_LOWER_BOUND = createUnixEpochTime(2009, 1, 1, 12, 0, 0);
private static final TimestampedValue<Instant> ARBITRARY_CLOCK_INITIALIZATION_INFO =
new TimestampedValue<>(
123456789L /* realtimeClockMillis */,
createUnixEpochTime(2010, 5, 23, 12, 0, 0));
// This is the traditional ordering for time detection on Android.
private static final @Origin int [] PROVIDERS_PRIORITY = { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
/**
* An arbitrary time, very different from the {@link #ARBITRARY_CLOCK_INITIALIZATION_INFO}
* time. Can be used as the basis for time suggestions.
*/
private static final Instant ARBITRARY_TEST_TIME = createUnixEpochTime(2018, 1, 1, 12, 0, 0);
private static final int ARBITRARY_SLOT_INDEX = 123456;
private Script mScript;
@Before
public void setUp() {
mScript = new Script();
}
@Test
public void testSuggestTelephonyTime_autoTimeEnabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
Instant testTime = ARBITRARY_TEST_TIME;
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
mScript.simulateTimePassing()
.simulateTelephonyTimeSuggestion(timeSuggestion);
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
}
@Test
public void testSuggestTelephonyTime_emptySuggestionIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, null);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, null);
}
@Test
public void testSuggestTelephonyTime_systemClockThreshold() {
final int systemClockUpdateThresholdMillis = 1000;
final int clockIncrementMillis = 100;
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeThresholds(systemClockUpdateThresholdMillis)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
// Send the first time signal. It should be used.
{
TelephonyTimeSuggestion timeSuggestion1 =
mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
// Increment the device clocks to simulate the passage of time.
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 =
mScript.calculateTimeInMillisForNow(timeSuggestion1.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
}
// Now send another time signal, but one that is too similar to the last one and should be
// stored, but not used to set the system clock.
{
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion(
slotIndex, mScript.peekSystemClockMillis() + underThresholdMillis);
mScript.simulateTimePassing(clockIncrementMillis)
.simulateTelephonyTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
}
// Now send another time signal, but one that is on the threshold and so should be used.
{
TelephonyTimeSuggestion timeSuggestion3 = mScript.generateTelephonyTimeSuggestion(
slotIndex,
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis3 =
mScript.calculateTimeInMillisForNow(timeSuggestion3.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(timeSuggestion3)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion3);
}
}
@Test
public void testSuggestTelephonyTime_multipleSlotIndexsAndBucketing() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
// There are 2 slotIndexes in this test. slotIndex1 and slotIndex2 have different opinions
// about the current time. slotIndex1 < slotIndex2 (which is important because the strategy
// uses the lowest slotIndex when multiple telephony suggestions are available.
int slotIndex1 = ARBITRARY_SLOT_INDEX;
int slotIndex2 = ARBITRARY_SLOT_INDEX + 1;
Instant slotIndex1Time = ARBITRARY_TEST_TIME;
Instant slotIndex2Time = ARBITRARY_TEST_TIME.plus(Duration.ofDays(1));
// Make a suggestion with slotIndex2.
{
TelephonyTimeSuggestion slotIndex2TimeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
mScript.simulateTimePassing();
long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(
slotIndex2TimeSuggestion.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestTelephonySuggestion(slotIndex1, null)
.assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
}
mScript.simulateTimePassing();
// Now make a different suggestion with slotIndex1.
{
TelephonyTimeSuggestion slotIndex1TimeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1Time);
mScript.simulateTimePassing();
long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(
slotIndex1TimeSuggestion.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestTelephonySuggestion(slotIndex1, slotIndex1TimeSuggestion);
}
mScript.simulateTimePassing();
// Make another suggestion with slotIndex2. It should be stored but not used because the
// slotIndex1 suggestion will still "win".
{
TelephonyTimeSuggestion slotIndex2TimeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
mScript.simulateTimePassing();
mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
}
// Let enough time pass that slotIndex1's suggestion should now be too old.
mScript.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS);
// Make another suggestion with slotIndex2. It should be used because the slotIndex1
// is in an older "bucket".
{
TelephonyTimeSuggestion slotIndex2TimeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
mScript.simulateTimePassing();
long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(
slotIndex2TimeSuggestion.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
}
}
@Test
public void testSuggestTelephonyTime_autoTimeDisabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false);
int slotIndex = ARBITRARY_SLOT_INDEX;
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
mScript.simulateTimePassing()
.simulateTelephonyTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
}
@Test
public void testSuggestTelephonyTime_invalidNitzReferenceTimesIgnored() {
final int systemClockUpdateThreshold = 2000;
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeThresholds(systemClockUpdateThreshold)
.pokeAutoTimeDetectionEnabled(true);
Instant testTime = ARBITRARY_TEST_TIME;
int slotIndex = ARBITRARY_SLOT_INDEX;
TelephonyTimeSuggestion timeSuggestion1 =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
TimestampedValue<Long> unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
// Initialize the strategy / device with a time set from a telephony suggestion.
mScript.simulateTimePassing();
long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(unixEpochTime1);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// The Unix epoch time increment should be larger than the system clock update threshold so
// we know it shouldn't be ignored for other reasons.
long validUnixEpochTimeMillis = unixEpochTime1.getValue()
+ (2 * systemClockUpdateThreshold);
// Now supply a new signal that has an obviously bogus reference time : older than the last
// one.
long referenceTimeBeforeLastSignalMillis = unixEpochTime1.getReferenceTimeMillis() - 1;
TimestampedValue<Long> unixEpochTime2 = new TimestampedValue<>(
referenceTimeBeforeLastSignalMillis, validUnixEpochTimeMillis);
TelephonyTimeSuggestion timeSuggestion2 =
createTelephonyTimeSuggestion(slotIndex, unixEpochTime2);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// Now supply a new signal that has an obviously bogus reference time : substantially in the
// future.
long referenceTimeInFutureMillis =
unixEpochTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
TimestampedValue<Long> unixEpochTime3 = new TimestampedValue<>(
referenceTimeInFutureMillis, validUnixEpochTimeMillis);
TelephonyTimeSuggestion timeSuggestion3 =
createTelephonyTimeSuggestion(slotIndex, unixEpochTime3);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion3)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// Just to prove validUnixEpochTimeMillis is valid.
long validReferenceTimeMillis = unixEpochTime1.getReferenceTimeMillis() + 100;
TimestampedValue<Long> unixEpochTime4 = new TimestampedValue<>(
validReferenceTimeMillis, validUnixEpochTimeMillis);
long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(unixEpochTime4);
TelephonyTimeSuggestion timeSuggestion4 =
createTelephonyTimeSuggestion(slotIndex, unixEpochTime4);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion4)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion4);
}
@Test
public void telephonyTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(
slotIndex, suggestedTime);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, null);
}
@Test
public void testSuggestTelephonyTime_timeDetectionToggled() {
final int clockIncrementMillis = 100;
final int systemClockUpdateThreshold = 2000;
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeThresholds(systemClockUpdateThreshold)
.pokeAutoTimeDetectionEnabled(false);
int slotIndex = ARBITRARY_SLOT_INDEX;
Instant testTime = ARBITRARY_TEST_TIME;
TelephonyTimeSuggestion timeSuggestion1 =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
TimestampedValue<Long> unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
// Simulate time passing.
mScript.simulateTimePassing(clockIncrementMillis);
// Simulate the time signal being received. It should not be used because auto time
// detection is off but it should be recorded.
mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(unixEpochTime1);
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// Turn off auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
// Receive another valid time signal.
// It should be on the threshold and accounting for the clock increments.
TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion(
slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis2 =
mScript.calculateTimeInMillisForNow(timeSuggestion2.getUnixEpochTime());
// The new time, though valid, should not be set in the system clock because auto time is
// disabled.
mScript.simulateTelephonyTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
.assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
}
@Test
public void testSuggestTelephonyTime_maxSuggestionAge() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
Instant testTime = ARBITRARY_TEST_TIME;
TelephonyTimeSuggestion telephonySuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
mScript.simulateTimePassing();
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(telephonySuggestion.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(telephonySuggestion)
.verifySystemClockWasSetAndResetCallTracking(
expectedSystemClockMillis /* expectedNetworkBroadcast */)
.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
// Look inside and check what the strategy considers the current best telephony suggestion.
assertEquals(telephonySuggestion, mScript.peekBestTelephonySuggestion());
// Simulate time passing, long enough that telephonySuggestion is now too old.
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS);
// Look inside and check what the strategy considers the current best telephony suggestion.
// It should still be the, it's just no longer used.
assertNull(mScript.peekBestTelephonySuggestion());
mScript.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
}
@Test
public void testSuggestManualTime_autoTimeDisabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false);
ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing();
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
public void testSuggestManualTime_retainsAutoSignal() {
// Configure the start state.
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
int slotIndex = ARBITRARY_SLOT_INDEX;
// Simulate a telephony suggestion.
Instant testTime = ARBITRARY_TEST_TIME;
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
// Simulate the passage of time.
mScript.simulateTimePassing();
long expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime());
mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
// Simulate the passage of time.
mScript.simulateTimePassing();
// Switch to manual.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
// Simulate the passage of time.
mScript.simulateTimePassing();
// Simulate a manual suggestion 1 day different from the auto suggestion.
Instant manualTime = testTime.plus(Duration.ofDays(1));
ManualTimeSuggestion manualTimeSuggestion =
mScript.generateManualTimeSuggestion(manualTime);
mScript.simulateTimePassing();
long expectedManualClockMillis =
mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUnixEpochTime());
mScript.simulateManualTimeSuggestion(manualTimeSuggestion, true /* expectedResult */)
.verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
.assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
// Simulate the passage of time.
mScript.simulateTimePassing();
// Switch back to auto.
mScript.simulateAutoTimeDetectionToggle();
expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime());
mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
// Switch back to manual - nothing should happen to the clock.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
}
@Test
public void manualTimeSuggestion_isIgnored_whenAutoTimeEnabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true);
ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing()
.simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void manualTimeSuggestion_ignoresTimeLowerBound() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false);
Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(suggestedTime);
mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */)
.verifySystemClockWasSetAndResetCallTracking(suggestedTime.toEpochMilli());
}
@Test
public void testSuggestNetworkTime_autoTimeEnabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_NETWORK)
.pokeAutoTimeDetectionEnabled(true);
NetworkTimeSuggestion timeSuggestion =
mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing();
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
public void testSuggestNetworkTime_autoTimeDisabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_NETWORK)
.pokeAutoTimeDetectionEnabled(false);
NetworkTimeSuggestion timeSuggestion =
mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing()
.simulateNetworkTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void networkTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_NETWORK)
.pokeAutoTimeDetectionEnabled(true);
Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
NetworkTimeSuggestion timeSuggestion = mScript
.generateNetworkTimeSuggestion(suggestedTime);
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestNetworkSuggestion(null);
}
@Test
public void testSuggestGnssTime_autoTimeEnabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_GNSS)
.pokeAutoTimeDetectionEnabled(true);
GnssTimeSuggestion timeSuggestion =
mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing();
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
mScript.simulateGnssTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
public void testSuggestGnssTime_autoTimeDisabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_GNSS)
.pokeAutoTimeDetectionEnabled(false);
GnssTimeSuggestion timeSuggestion =
mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing()
.simulateGnssTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void testSuggestExternalTime_autoTimeEnabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_EXTERNAL)
.pokeAutoTimeDetectionEnabled(true);
ExternalTimeSuggestion timeSuggestion =
mScript.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing();
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
mScript.simulateExternalTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
public void testSuggestExternalTime_autoTimeDisabled() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_EXTERNAL)
.pokeAutoTimeDetectionEnabled(false);
ExternalTimeSuggestion timeSuggestion =
mScript.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateTimePassing()
.simulateExternalTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void externalTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoOriginPriorities(ORIGIN_EXTERNAL)
.pokeAutoTimeDetectionEnabled(true);
Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
ExternalTimeSuggestion timeSuggestion = mScript
.generateExternalTimeSuggestion(suggestedTime);
mScript.simulateExternalTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestExternalSuggestion(null);
}
@Test
public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK);
// Three obviously different times that could not be mistaken for each other.
Instant networkTime1 = ARBITRARY_TEST_TIME;
Instant networkTime2 = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
// A small increment used to simulate the passage of time, but not enough to interfere with
// macro-level time changes associated with suggestion age.
final long smallTimeIncrementMillis = 101;
// A network suggestion is made. It should be used because there is no telephony suggestion.
NetworkTimeSuggestion networkTimeSuggestion1 =
mScript.generateNetworkTimeSuggestion(networkTime1);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
networkTimeSuggestion1.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null)
.assertLatestNetworkSuggestion(networkTimeSuggestion1);
assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
assertNull("No telephony suggestions were made:", mScript.peekBestTelephonySuggestion());
// Simulate a little time passing.
mScript.simulateTimePassing(smallTimeIncrementMillis)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now a telephony suggestion is made. Telephony suggestions are prioritized over network
// suggestions so it should "win".
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
telephonyTimeSuggestion.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion1);
assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use".
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now another network suggestion is made. Telephony suggestions are prioritized over
// network suggestions so the latest telephony suggestion should still "win".
NetworkTimeSuggestion networkTimeSuggestion2 =
mScript.generateNetworkTimeSuggestion(networkTime2);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion2);
assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to
// be used but networkTimeSuggestion2 is not.
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2);
// NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
// suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle
// to re-run the detection logic. This may change in future but until then we rely on a
// steady stream of suggestions to re-evaluate.
mScript.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion2);
assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
assertNull(
"Telephony suggestion should be expired:",
mScript.peekBestTelephonySuggestion());
// Toggle auto-time off and on to force the detection logic to run.
mScript.simulateAutoTimeDetectionToggle()
.simulateTimePassing(smallTimeIncrementMillis)
.simulateAutoTimeDetectionToggle();
// Verify the latest network time now wins.
mScript.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion2);
assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
assertNull(
"Telephony suggestion should still be expired:",
mScript.peekBestTelephonySuggestion());
}
@Test
public void highPrioritySuggestionsBeatLowerPrioritySuggestions_networkGnssOrigins() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_NETWORK, ORIGIN_GNSS);
// Three obviously different times that could not be mistaken for each other.
Instant gnssTime1 = ARBITRARY_TEST_TIME;
Instant gnssTime2 = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
Instant networkTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
// A small increment used to simulate the passage of time, but not enough to interfere with
// macro-level time changes associated with suggestion age.
final long smallTimeIncrementMillis = 101;
// A gnss suggestion is made. It should be used because there is no network suggestion.
GnssTimeSuggestion gnssTimeSuggestion1 =
mScript.generateGnssTimeSuggestion(gnssTime1);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateGnssTimeSuggestion(gnssTimeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
gnssTimeSuggestion1.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(null)
.assertLatestGnssSuggestion(gnssTimeSuggestion1);
assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion());
assertNull("No network suggestions were made:", mScript.peekLatestValidNetworkSuggestion());
// Simulate a little time passing.
mScript.simulateTimePassing(smallTimeIncrementMillis)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now a network suggestion is made. Network suggestions are prioritized over gnss
// suggestions so it should "win".
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
networkTimeSuggestion.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion1);
assertEquals(gnssTimeSuggestion1, mScript.peekLatestValidGnssSuggestion());
assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use".
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now another gnss suggestion is made. Network suggestions are prioritized over
// gnss suggestions so the latest network suggestion should still "win".
GnssTimeSuggestion gnssTimeSuggestion2 =
mScript.generateGnssTimeSuggestion(gnssTime2);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateGnssTimeSuggestion(gnssTimeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion2);
assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to
// be used but networkTimeSuggestion2 is not.
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2);
// NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
// suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle
// to re-run the detection logic. This may change in future but until then we rely on a
// steady stream of suggestions to re-evaluate.
mScript.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion2);
assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
assertNull(
"Network suggestion should be expired:",
mScript.peekLatestValidNetworkSuggestion());
// Toggle auto-time off and on to force the detection logic to run.
mScript.simulateAutoTimeDetectionToggle()
.simulateTimePassing(smallTimeIncrementMillis)
.simulateAutoTimeDetectionToggle();
// Verify the latest gnss time now wins.
mScript.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(gnssTimeSuggestion2.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion2);
assertEquals(gnssTimeSuggestion2, mScript.peekLatestValidGnssSuggestion());
assertNull(
"Network suggestion should still be expired:",
mScript.peekLatestValidNetworkSuggestion());
}
@Test
public void highPrioritySuggestionsBeatLowerPrioritySuggestions_networkExternalOrigins() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_NETWORK, ORIGIN_EXTERNAL);
// Three obviously different times that could not be mistaken for each other.
Instant externalTime1 = ARBITRARY_TEST_TIME;
Instant externalTime2 = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
Instant networkTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
// A small increment used to simulate the passage of time, but not enough to interfere with
// macro-level time changes associated with suggestion age.
final long smallTimeIncrementMillis = 101;
// A external suggestion is made. It should be used because there is no network suggestion.
ExternalTimeSuggestion externalTimeSuggestion1 =
mScript.generateExternalTimeSuggestion(externalTime1);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateExternalTimeSuggestion(externalTimeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
externalTimeSuggestion1.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(null)
.assertLatestExternalSuggestion(externalTimeSuggestion1);
assertEquals(externalTimeSuggestion1, mScript.peekLatestValidExternalSuggestion());
assertNull("No network suggestions were made:", mScript.peekLatestValidNetworkSuggestion());
// Simulate a little time passing.
mScript.simulateTimePassing(smallTimeIncrementMillis)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now a network suggestion is made. Network suggestions are prioritized over external
// suggestions so it should "win".
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(
networkTimeSuggestion.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion1);
assertEquals(externalTimeSuggestion1, mScript.peekLatestValidExternalSuggestion());
assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use".
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now another external suggestion is made. Network suggestions are prioritized over
// external suggestions so the latest network suggestion should still "win".
ExternalTimeSuggestion externalTimeSuggestion2 =
mScript.generateExternalTimeSuggestion(externalTime2);
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateExternalTimeSuggestion(externalTimeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion2);
assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion());
assertEquals(networkTimeSuggestion, mScript.peekLatestValidNetworkSuggestion());
// Simulate some significant time passing: half the time allowed before a time signal
// becomes "too old to use". This should mean that networkTimeSuggestion is now too old to
// be used but externalTimeSuggestion2 is not.
mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS / 2);
// NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
// suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle
// to re-run the detection logic. This may change in future but until then we rely on a
// steady stream of suggestions to re-evaluate.
mScript.verifySystemClockWasNotSetAndResetCallTracking();
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion2);
assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion());
assertNull(
"Network suggestion should be expired:",
mScript.peekLatestValidNetworkSuggestion());
// Toggle auto-time off and on to force the detection logic to run.
mScript.simulateAutoTimeDetectionToggle()
.simulateTimePassing(smallTimeIncrementMillis)
.simulateAutoTimeDetectionToggle();
// Verify the latest external time now wins.
mScript.verifySystemClockWasSetAndResetCallTracking(
mScript.calculateTimeInMillisForNow(externalTimeSuggestion2.getUnixEpochTime()));
// Check internal state.
mScript.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion2);
assertEquals(externalTimeSuggestion2, mScript.peekLatestValidExternalSuggestion());
assertNull(
"Network suggestion should still be expired:",
mScript.peekLatestValidNetworkSuggestion());
}
@Test
public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_lowerPriorityComesFirst() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK, ORIGIN_EXTERNAL,
ORIGIN_GNSS);
Instant networkTime = ARBITRARY_TEST_TIME;
Instant externalTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(15));
Instant gnssTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
ExternalTimeSuggestion externalTimeSuggestion =
mScript.generateExternalTimeSuggestion(externalTime);
GnssTimeSuggestion gnssTimeSuggestion =
mScript.generateGnssTimeSuggestion(gnssTime);
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
mScript.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.simulateExternalTimeSuggestion(externalTimeSuggestion)
.simulateGnssTimeSuggestion(gnssTimeSuggestion)
.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion)
.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
}
@Test
public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_higherPriorityComesFirst() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK,
ORIGIN_EXTERNAL, ORIGIN_GNSS);
Instant networkTime = ARBITRARY_TEST_TIME;
Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
Instant externalTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(50));
Instant gnssTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
NetworkTimeSuggestion networkTimeSuggestion =
mScript.generateNetworkTimeSuggestion(networkTime);
TelephonyTimeSuggestion telephonyTimeSuggestion =
mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
GnssTimeSuggestion gnssTimeSuggestion =
mScript.generateGnssTimeSuggestion(gnssTime);
ExternalTimeSuggestion externalTimeSuggestion =
mScript.generateExternalTimeSuggestion(externalTime);
mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
.simulateNetworkTimeSuggestion(networkTimeSuggestion)
.simulateGnssTimeSuggestion(gnssTimeSuggestion)
.simulateExternalTimeSuggestion(externalTimeSuggestion)
.assertLatestNetworkSuggestion(networkTimeSuggestion)
.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
.assertLatestGnssSuggestion(gnssTimeSuggestion)
.assertLatestExternalSuggestion(externalTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
}
@Test
public void whenHighestPrioritySuggestionIsNotAvailable_fallbacksToNext() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK);
NetworkTimeSuggestion timeSuggestion =
mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
}
@Test
public void whenHigherPrioritySuggestionsAreNotAvailable_fallbacksToNext() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY, ORIGIN_NETWORK,
ORIGIN_EXTERNAL, ORIGIN_GNSS);
GnssTimeSuggestion timeSuggestion =
mScript.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateGnssTimeSuggestion(timeSuggestion)
.assertLatestGnssSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
}
@Test
public void suggestionsFromTelephonyOriginNotInPriorityList_areIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_NETWORK);
int slotIndex = ARBITRARY_SLOT_INDEX;
Instant testTime = ARBITRARY_TEST_TIME;
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void suggestionsFromNetworkOriginNotInPriorityList_areIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
NetworkTimeSuggestion timeSuggestion = mScript.generateNetworkTimeSuggestion(
ARBITRARY_TEST_TIME);
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
.assertLatestNetworkSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void suggestionsFromGnssOriginNotInPriorityList_areIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
GnssTimeSuggestion timeSuggestion = mScript.generateGnssTimeSuggestion(
ARBITRARY_TEST_TIME);
mScript.simulateGnssTimeSuggestion(timeSuggestion)
.assertLatestGnssSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void suggestionsFromExternalOriginNotInPriorityList_areIgnored() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
ExternalTimeSuggestion timeSuggestion = mScript.generateExternalTimeSuggestion(
ARBITRARY_TEST_TIME);
mScript.simulateExternalTimeSuggestion(timeSuggestion)
.assertLatestExternalSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void autoOriginPrioritiesList_doesNotAffectManualSuggestion() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY);
ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */)
.verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
}
@Test
public void manualY2038SuggestionsAreRejectedOnAffectedDevices() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
.pokeDeviceHasY2038Issues(true);
Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(y2038IssueTime);
mScript.simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void telephonyY2038SuggestionsAreRejectedOnAffectedDevices() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
.pokeDeviceHasY2038Issues(true);
final int slotIndex = 0;
Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void telephonyY2038SuggestionsAreNotRejectedOnUnaffectedDevices() {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(true)
.pokeAutoOriginPriorities(ORIGIN_TELEPHONY)
.pokeDeviceHasY2038Issues(false);
final int slotIndex = 0;
Instant y2038IssueTime = Instant.ofEpochMilli((1L + Integer.MAX_VALUE) * 1000L);
TelephonyTimeSuggestion timeSuggestion =
mScript.generateTelephonyTimeSuggestion(slotIndex, y2038IssueTime);
mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(y2038IssueTime.toEpochMilli());
}
/**
* A fake implementation of {@link TimeDetectorStrategyImpl.Environment}. Besides tracking
* changes and behaving like the real thing should, it also asserts preconditions.
*/
private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment {
private boolean mAutoTimeDetectionEnabled;
private boolean mWakeLockAcquired;
private long mElapsedRealtimeMillis;
private long mSystemClockMillis;
private int mSystemClockUpdateThresholdMillis = 2000;
private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY;
private ConfigurationChangeListener mConfigChangeListener;
private boolean mDeviceHas2038Issues = false;
// Tracking operations.
private boolean mSystemClockWasSet;
@Override
public void setConfigChangeListener(ConfigurationChangeListener listener) {
mConfigChangeListener = Objects.requireNonNull(listener);
}
@Override
public int systemClockUpdateThresholdMillis() {
return mSystemClockUpdateThresholdMillis;
}
@Override
public boolean isAutoTimeDetectionEnabled() {
return mAutoTimeDetectionEnabled;
}
@Override
public Instant autoTimeLowerBound() {
return TIME_LOWER_BOUND;
}
@Override
public int[] autoOriginPriorities() {
return mAutoOriginPriorities;
}
@Override
public ConfigurationInternal configurationInternal(int userId) {
throw new UnsupportedOperationException();
}
@Override
public void acquireWakeLock() {
if (mWakeLockAcquired) {
fail("Wake lock already acquired");
}
mWakeLockAcquired = true;
}
@Override
public long elapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
@Override
public long systemClockMillis() {
return mSystemClockMillis;
}
@Override
public void setSystemClock(long newTimeMillis) {
assertWakeLockAcquired();
mSystemClockWasSet = true;
mSystemClockMillis = newTimeMillis;
}
@Override
public void releaseWakeLock() {
assertWakeLockAcquired();
mWakeLockAcquired = false;
}
public void setDeviceHas2038Issues(boolean hasIssues) {
mDeviceHas2038Issues = hasIssues;
}
@Override
public boolean deviceHasY2038Issue() {
return mDeviceHas2038Issues;
}
// Methods below are for managing the fake's behavior.
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
mSystemClockUpdateThresholdMillis = thresholdMillis;
}
void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
void pokeSystemClockMillis(long systemClockMillis) {
mSystemClockMillis = systemClockMillis;
}
void pokeAutoTimeDetectionEnabled(boolean enabled) {
mAutoTimeDetectionEnabled = enabled;
}
void pokeAutoOriginPriorities(@Origin int[] autoOriginPriorities) {
mAutoOriginPriorities = autoOriginPriorities;
}
long peekElapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
long peekSystemClockMillis() {
return mSystemClockMillis;
}
void simulateTimePassing(long incrementMillis) {
mElapsedRealtimeMillis += incrementMillis;
mSystemClockMillis += incrementMillis;
}
void simulateAutoTimeZoneDetectionToggle() {
mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
mConfigChangeListener.onChange();
}
void verifySystemClockNotSet() {
assertFalse(
String.format("System clock was manipulated and set to %s(=%s)",
Instant.ofEpochMilli(mSystemClockMillis), mSystemClockMillis),
mSystemClockWasSet);
}
void verifySystemClockWasSet(long expectedSystemClockMillis) {
assertTrue(mSystemClockWasSet);
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
void resetCallTracking() {
mSystemClockWasSet = false;
}
private void assertWakeLockAcquired() {
assertTrue("The operation must be performed only after acquiring the wakelock",
mWakeLockAcquired);
}
}
/**
* A fluent helper class for tests.
*/
private class Script {
private final FakeEnvironment mFakeEnvironment;
private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
Script() {
mFakeEnvironment = new FakeEnvironment();
mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
}
Script pokeAutoTimeDetectionEnabled(boolean enabled) {
mFakeEnvironment.pokeAutoTimeDetectionEnabled(enabled);
return this;
}
Script pokeFakeClocks(TimestampedValue<Instant> timeInfo) {
mFakeEnvironment.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
mFakeEnvironment.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
return this;
}
Script pokeThresholds(int systemClockUpdateThreshold) {
mFakeEnvironment.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
return this;
}
Script pokeAutoOriginPriorities(@Origin int... autoOriginPriorities) {
mFakeEnvironment.pokeAutoOriginPriorities(autoOriginPriorities);
return this;
}
Script pokeDeviceHasY2038Issues(boolean hasIssues) {
mFakeEnvironment.setDeviceHas2038Issues(hasIssues);
return this;
}
long peekElapsedRealtimeMillis() {
return mFakeEnvironment.peekElapsedRealtimeMillis();
}
long peekSystemClockMillis() {
return mFakeEnvironment.peekSystemClockMillis();
}
Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) {
mTimeDetectorStrategy.suggestTelephonyTime(timeSuggestion);
return this;
}
Script simulateManualTimeSuggestion(
ManualTimeSuggestion timeSuggestion, boolean expectedResult) {
String errorMessage = expectedResult
? "Manual time suggestion was ignored, but expected to be accepted."
: "Manual time suggestion was accepted, but expected to be ignored.";
assertEquals(
errorMessage,
expectedResult,
mTimeDetectorStrategy.suggestManualTime(timeSuggestion));
return this;
}
Script simulateNetworkTimeSuggestion(NetworkTimeSuggestion timeSuggestion) {
mTimeDetectorStrategy.suggestNetworkTime(timeSuggestion);
return this;
}
Script simulateGnssTimeSuggestion(GnssTimeSuggestion timeSuggestion) {
mTimeDetectorStrategy.suggestGnssTime(timeSuggestion);
return this;
}
Script simulateExternalTimeSuggestion(ExternalTimeSuggestion timeSuggestion) {
mTimeDetectorStrategy.suggestExternalTime(timeSuggestion);
return this;
}
Script simulateAutoTimeDetectionToggle() {
mFakeEnvironment.simulateAutoTimeZoneDetectionToggle();
return this;
}
Script simulateTimePassing(long clockIncrementMillis) {
mFakeEnvironment.simulateTimePassing(clockIncrementMillis);
return this;
}
/**
* Simulates time passing by an arbitrary (but relatively small) amount.
*/
Script simulateTimePassing() {
return simulateTimePassing(999);
}
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeEnvironment.verifySystemClockNotSet();
mFakeEnvironment.resetCallTracking();
return this;
}
Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
mFakeEnvironment.verifySystemClockWasSet(expectedSystemClockMillis);
mFakeEnvironment.resetCallTracking();
return this;
}
/**
* White box test info: Asserts the latest suggestion for the slotIndex is as expected.
*/
Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) {
assertEquals(
"Expected to see " + expected + " at slotIndex=" + slotIndex + ", but got "
+ mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex),
expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
return this;
}
/**
* White box test info: Asserts the latest network suggestion is as expected.
*/
Script assertLatestNetworkSuggestion(NetworkTimeSuggestion expected) {
assertEquals(expected, mTimeDetectorStrategy.getLatestNetworkSuggestion());
return this;
}
/**
* White box test info: Asserts the latest gnss suggestion is as expected.
*/
Script assertLatestGnssSuggestion(GnssTimeSuggestion expected) {
assertEquals(expected, mTimeDetectorStrategy.getLatestGnssSuggestion());
return this;
}
/**
* White box test info: Asserts the latest external suggestion is as expected.
*/
Script assertLatestExternalSuggestion(ExternalTimeSuggestion expected) {
assertEquals(expected, mTimeDetectorStrategy.getLatestExternalSuggestion());
return this;
}
/**
* White box test info: Returns the telephony suggestion that would be used, if any, given
* the current elapsed real time clock and regardless of origin prioritization.
*/
TelephonyTimeSuggestion peekBestTelephonySuggestion() {
return mTimeDetectorStrategy.findBestTelephonySuggestionForTests();
}
/**
* White box test info: Returns the network suggestion that would be used, if any, given the
* current elapsed real time clock and regardless of origin prioritization.
*/
NetworkTimeSuggestion peekLatestValidNetworkSuggestion() {
return mTimeDetectorStrategy.findLatestValidNetworkSuggestionForTests();
}
/**
* White box test info: Returns the gnss suggestion that would be used, if any, given the
* current elapsed real time clock and regardless of origin prioritization.
*/
GnssTimeSuggestion peekLatestValidGnssSuggestion() {
return mTimeDetectorStrategy.findLatestValidGnssSuggestionForTests();
}
/**
* White box test info: Returns the external suggestion that would be used, if any, given
* the current elapsed real time clock and regardless of origin prioritization.
*/
ExternalTimeSuggestion peekLatestValidExternalSuggestion() {
return mTimeDetectorStrategy.findLatestValidExternalSuggestionForTests();
}
/**
* Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> unixEpochTime =
new TimestampedValue<>(
mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new ManualTimeSuggestion(unixEpochTime);
}
/**
* Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
* the reference time.
*/
TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, long timeMillis) {
TimestampedValue<Long> time =
new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
return createTelephonyTimeSuggestion(slotIndex, time);
}
/**
* Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
* the reference time.
*/
TelephonyTimeSuggestion generateTelephonyTimeSuggestion(
int slotIndex, Instant suggestedTime) {
if (suggestedTime == null) {
return createTelephonyTimeSuggestion(slotIndex, null);
}
return generateTelephonyTimeSuggestion(slotIndex, suggestedTime.toEpochMilli());
}
/**
* Generates a NetworkTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> unixEpochTime =
new TimestampedValue<>(
mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new NetworkTimeSuggestion(unixEpochTime);
}
/**
* Generates a GnssTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
GnssTimeSuggestion generateGnssTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> unixEpochTime =
new TimestampedValue<>(
mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new GnssTimeSuggestion(unixEpochTime);
}
/**
* Generates a ExternalTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
ExternalTimeSuggestion generateExternalTimeSuggestion(Instant suggestedTime) {
return new ExternalTimeSuggestion(mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
}
/**
* Calculates what the supplied time would be when adjusted for the movement of the fake
* elapsed realtime clock.
*/
long calculateTimeInMillisForNow(TimestampedValue<Long> unixEpochTime) {
return TimeDetectorStrategy.getTimeAt(unixEpochTime, peekElapsedRealtimeMillis());
}
}
private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,
TimestampedValue<Long> unixEpochTime) {
return new TelephonyTimeSuggestion.Builder(slotIndex)
.setUnixEpochTime(unixEpochTime)
.build();
}
private static Instant createUnixEpochTime(int year, int monthInYear, int day, int hourOfDay,
int minute, int second) {
return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second)
.toInstant(ZoneOffset.UTC);
}
}