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

Commit 0068bae2 authored by Almaz Mingaleev's avatar Almaz Mingaleev
Browse files

Add lower bound checks for non-manual time suggestions.

Before this change, automatic time detection would not validate the incoming UTC time suggested. When the device time is set to a bad value it can cause problems with connectivity and fail enterprise policy checks.

After this change, automatic time detection (currently NITZ or NTP) will disregard times before a lower bound. The lower bound value is initially set from a date derived from the build timestamp. This works on the principle that a valid time cannot be before the system image was built. There is no upper bound currently as we haven't seen invalid dates in the future, and it's harder to come up with a timestamp that we know will always be invalid.

This change may impact automated or manual tests that use dates in the distant past: these dates will no longer be accepted. Tests will have to be updated to use dates in the future.

Bug: 172229867
Test: atest
frameworks/base/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java

Change-Id: I844ae06c3145e0fac30bd5455ec4f00d01b06ac8
parent 08466f61
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -20,12 +20,15 @@ import android.annotation.NonNull;
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;

import java.time.Instant;
import java.util.Objects;

/**
@@ -37,6 +40,13 @@ public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrat

    private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;

    /**
     * Time in the past. If automatic time suggestion is before this point, it's
     * incorrect for sure.
     */
    private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
            Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));

    /**
     * If a newly calculated system clock time and the current system clock time differs by this or
     * more the system clock will actually be updated. Used to prevent the system clock being set
@@ -78,6 +88,11 @@ public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrat
        }
    }

    @Override
    public Instant autoTimeLowerBound() {
        return TIME_LOWER_BOUND;
    }

    @Override
    public void acquireWakeLock() {
        if (mWakeLock.isHeld()) {
+38 −10
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.server.timezonedetector.ReferenceWithHistory;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;

/**
 * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
@@ -143,6 +144,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
        /** Returns true if automatic time detection is enabled. */
        boolean isAutoTimeDetectionEnabled();

        /**
         * Returns a lower bound for valid automatic times. It is guaranteed to be in the past,
         * i.e. it is unrelated to the current system clock time.
         * It holds no other meaning; it could be related to when the device system image was built,
         * or could be updated by a mainline module.
         */
        @NonNull
        Instant autoTimeLowerBound();

        /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
        void acquireWakeLock();

@@ -177,7 +187,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {

    @Override
    public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion) {
        if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
        if (!validateAutoSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
            return;
        }

@@ -211,9 +221,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
            return;
        }

        // Perform validation / input filtering and record the validated suggestion against the
        // slotIndex.
        if (!validateAndStoreTelephonySuggestion(timeSuggestion)) {
        if (!validateAutoSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
            return;
        }

        // Perform input filtering and record the validated suggestion against the slotIndex.
        if (!storeTelephonySuggestion(timeSuggestion)) {
            return;
        }

@@ -272,14 +285,9 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
    }

    @GuardedBy("this")
    private boolean validateAndStoreTelephonySuggestion(
    private boolean storeTelephonySuggestion(
            @NonNull TelephonyTimeSuggestion suggestion) {
        TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
        if (!validateSuggestionTime(newUtcTime, suggestion)) {
            // There's probably nothing useful we can do: elsewhere we assume that reference
            // times are in the past so just stop here.
            return false;
        }

        int slotIndex = suggestion.getSlotIndex();
        TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
@@ -330,6 +338,26 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
        return true;
    }

    private boolean validateAutoSuggestionTime(
            @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion)  {
        return validateSuggestionTime(newUtcTime, suggestion)
                && validateSuggestionAgainstLowerBound(newUtcTime, suggestion);
    }

    private boolean validateSuggestionAgainstLowerBound(
            @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
        Instant lowerBound = mCallback.autoTimeLowerBound();

        // Suggestion is definitely wrong if it comes before lower time bound.
        if (lowerBound.isAfter(Instant.ofEpochMilli(newUtcTime.getValue()))) {
            Slog.w(LOG_TAG, "Suggestion points to time before lower bound, skipping it. "
                    + "suggestion=" + suggestion + ", lower bound=" + lowerBound);
            return false;
        }

        return true;
    }

    @GuardedBy("this")
    private void doAutoTimeDetection(@NonNull String detectionReason) {
        if (!mCallback.isAutoTimeDetectionEnabled()) {
+132 −57
Original line number Diff line number Diff line
@@ -25,9 +25,6 @@ import static org.junit.Assert.fail;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
import android.icu.util.TimeZone;
import android.os.TimestampedValue;

import androidx.test.runner.AndroidJUnit4;
@@ -37,20 +34,25 @@ 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;

@RunWith(AndroidJUnit4.class)
public class TimeDetectorStrategyImplTest {

    private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
    private static final Instant TIME_LOWER_BOUND = createUtcTime(2009, 1, 1, 12, 0, 0);

    private static final TimestampedValue<Instant> ARBITRARY_CLOCK_INITIALIZATION_INFO =
            new TimestampedValue<>(
                    123456789L /* realtimeClockMillis */,
                    createUtcTime(2008, 5, 23, 12, 0, 0));
                    createUtcTime(2010, 5, 23, 12, 0, 0));

    /**
     * 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 long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
    private static final Instant ARBITRARY_TEST_TIME = createUtcTime(2018, 1, 1, 12, 0, 0);

    private static final int ARBITRARY_SLOT_INDEX = 123456;

@@ -67,10 +69,10 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(true);

        int slotIndex = ARBITRARY_SLOT_INDEX;
        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        Instant testTime = ARBITRARY_TEST_TIME;

        TelephonyTimeSuggestion timeSuggestion =
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
        mScript.simulateTimePassing()
                .simulateTelephonyTimeSuggestion(timeSuggestion);

@@ -106,9 +108,9 @@ public class TimeDetectorStrategyImplTest {
        // Send the first time signal. It should be used.
        {
            TelephonyTimeSuggestion timeSuggestion1 =
                    mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
                    mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);

            // Increment the the device clocks to simulate the passage of time.
            // Increment the device clocks to simulate the passage of time.
            mScript.simulateTimePassing(clockIncrementMillis);

            long expectedSystemClockMillis1 =
@@ -157,13 +159,13 @@ public class TimeDetectorStrategyImplTest {
        // uses the lowest slotIndex when multiple telephony suggestions are available.
        int slotIndex1 = ARBITRARY_SLOT_INDEX;
        int slotIndex2 = ARBITRARY_SLOT_INDEX + 1;
        long slotIndex1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        long slotIndex2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
        Instant slotIndex1Time = ARBITRARY_TEST_TIME;
        Instant slotIndex2Time = ARBITRARY_TEST_TIME.plus(Duration.ofDays(1));

        // Make a suggestion with slotIndex2.
        {
            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
            mScript.simulateTimePassing();

            long expectedSystemClockMillis =
@@ -180,7 +182,7 @@ public class TimeDetectorStrategyImplTest {
        // Now make a different suggestion with slotIndex1.
        {
            TelephonyTimeSuggestion slotIndex1TimeSuggestion =
                    mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1TimeMillis);
                    mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1Time);
            mScript.simulateTimePassing();

            long expectedSystemClockMillis =
@@ -198,7 +200,7 @@ public class TimeDetectorStrategyImplTest {
        // slotIndex1 suggestion will still "win".
        {
            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
            mScript.simulateTimePassing();

            mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
@@ -213,7 +215,7 @@ public class TimeDetectorStrategyImplTest {
        // is in an older "bucket".
        {
            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
            mScript.simulateTimePassing();

            long expectedSystemClockMillis =
@@ -232,7 +234,7 @@ public class TimeDetectorStrategyImplTest {

        int slotIndex = ARBITRARY_SLOT_INDEX;
        TelephonyTimeSuggestion timeSuggestion =
                mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
                mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
        mScript.simulateTimePassing()
                .simulateTelephonyTimeSuggestion(timeSuggestion)
                .verifySystemClockWasNotSetAndResetCallTracking()
@@ -246,11 +248,11 @@ public class TimeDetectorStrategyImplTest {
                .pokeThresholds(systemClockUpdateThreshold)
                .pokeAutoTimeDetectionEnabled(true);

        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        Instant testTime = ARBITRARY_TEST_TIME;
        int slotIndex = ARBITRARY_SLOT_INDEX;

        TelephonyTimeSuggestion timeSuggestion1 =
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();

        // Initialize the strategy / device with a time set from a telephony suggestion.
@@ -299,6 +301,23 @@ public class TimeDetectorStrategyImplTest {
                .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;
@@ -308,9 +327,9 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(false);

        int slotIndex = ARBITRARY_SLOT_INDEX;
        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        Instant testTime = ARBITRARY_TEST_TIME;
        TelephonyTimeSuggestion timeSuggestion1 =
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();

        // Simulate time passing.
@@ -366,9 +385,9 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(true);

        int slotIndex = ARBITRARY_SLOT_INDEX;
        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        Instant testTime = ARBITRARY_TEST_TIME;
        TelephonyTimeSuggestion telephonySuggestion =
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);

        mScript.simulateTimePassing();

@@ -397,7 +416,7 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(false);

        ManualTimeSuggestion timeSuggestion =
                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);

        mScript.simulateTimePassing();

@@ -416,9 +435,9 @@ public class TimeDetectorStrategyImplTest {
        int slotIndex = ARBITRARY_SLOT_INDEX;

        // Simulate a telephony suggestion.
        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
        Instant testTime = ARBITRARY_TEST_TIME;
        TelephonyTimeSuggestion telephonyTimeSuggestion =
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);

        // Simulate the passage of time.
        mScript.simulateTimePassing();
@@ -441,9 +460,9 @@ public class TimeDetectorStrategyImplTest {
        mScript.simulateTimePassing();

        // Simulate a manual suggestion 1 day different from the auto suggestion.
        long manualTimeMillis = testTimeMillis + Duration.ofDays(1).toMillis();
        Instant manualTime = testTime.plus(Duration.ofDays(1));
        ManualTimeSuggestion manualTimeSuggestion =
                mScript.generateManualTimeSuggestion(manualTimeMillis);
                mScript.generateManualTimeSuggestion(manualTime);
        mScript.simulateTimePassing();

        long expectedManualClockMillis =
@@ -478,20 +497,33 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(true);

        ManualTimeSuggestion timeSuggestion =
                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);

        mScript.simulateTimePassing()
                .simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */)
                .verifySystemClockWasNotSetAndResetCallTracking();
    }

    @Test
    public void suggestManualTime_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)
                .pokeAutoTimeDetectionEnabled(true);

        NetworkTimeSuggestion timeSuggestion =
                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);

        mScript.simulateTimePassing();

@@ -507,7 +539,7 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(false);

        NetworkTimeSuggestion timeSuggestion =
                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);

        mScript.simulateTimePassing()
                .simulateNetworkTimeSuggestion(timeSuggestion)
@@ -520,16 +552,16 @@ public class TimeDetectorStrategyImplTest {
                .pokeAutoTimeDetectionEnabled(true);

        // Three obviously different times that could not be mistaken for each other.
        long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS;
        long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis();
        long telephonyTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis();
        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(networkTimeMillis1);
                mScript.generateNetworkTimeSuggestion(networkTime1);
        mScript.simulateTimePassing(smallTimeIncrementMillis)
                .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
                .verifySystemClockWasSetAndResetCallTracking(
@@ -548,7 +580,7 @@ public class TimeDetectorStrategyImplTest {
        // Now a telephony suggestion is made. Telephony suggestions are prioritized over network
        // suggestions so it should "win".
        TelephonyTimeSuggestion telephonyTimeSuggestion =
                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeMillis);
                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
        mScript.simulateTimePassing(smallTimeIncrementMillis)
                .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
                .verifySystemClockWasSetAndResetCallTracking(
@@ -568,7 +600,7 @@ public class TimeDetectorStrategyImplTest {
        // 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(networkTimeMillis2);
                mScript.generateNetworkTimeSuggestion(networkTime2);
        mScript.simulateTimePassing(smallTimeIncrementMillis)
                .simulateNetworkTimeSuggestion(networkTimeSuggestion2)
                .verifySystemClockWasNotSetAndResetCallTracking();
@@ -612,6 +644,20 @@ public class TimeDetectorStrategyImplTest {
        assertNull(mScript.peekBestTelephonySuggestion());
    }

    @Test
    public void networkTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                .pokeAutoTimeDetectionEnabled(true);

        Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
        NetworkTimeSuggestion timeSuggestion = mScript
                .generateNetworkTimeSuggestion(suggestedTime);

        mScript.simulateNetworkTimeSuggestion(timeSuggestion)
                .verifySystemClockWasNotSetAndResetCallTracking()
                .assertLatestNetworkSuggestion(null);
    }

    /**
     * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
     * like the real thing should, it also asserts preconditions.
@@ -636,6 +682,11 @@ public class TimeDetectorStrategyImplTest {
            return mAutoTimeDetectionEnabled;
        }

        @Override
        public Instant autoTimeLowerBound() {
            return TIME_LOWER_BOUND;
        }

        @Override
        public void acquireWakeLock() {
            if (mWakeLockAcquired) {
@@ -703,7 +754,10 @@ public class TimeDetectorStrategyImplTest {
        }

        void verifySystemClockNotSet() {
            assertFalse(mSystemClockWasSet);
            assertFalse(
                    String.format("System clock was manipulated and set to %s(=%s)",
                            Instant.ofEpochMilli(mSystemClockMillis), mSystemClockMillis),
                    mSystemClockWasSet);
        }

        void verifySystemClockWasSet(long expectedSystemClockMillis) {
@@ -739,9 +793,9 @@ public class TimeDetectorStrategyImplTest {
            return this;
        }

        Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
        Script pokeFakeClocks(TimestampedValue<Instant> timeInfo) {
            mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
            return this;
        }

@@ -765,7 +819,13 @@ public class TimeDetectorStrategyImplTest {

        Script simulateManualTimeSuggestion(
                ManualTimeSuggestion timeSuggestion, boolean expectedResult) {
            assertEquals(expectedResult, mTimeDetectorStrategy.suggestManualTime(timeSuggestion));
            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;
        }

@@ -808,7 +868,10 @@ public class TimeDetectorStrategyImplTest {
         * White box test info: Asserts the latest suggestion for the slotIndex is as expected.
         */
        Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) {
            assertEquals(expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
            assertEquals(
                    "Expected to see " + expected + " at slotIndex=" + slotIndex + ", but got "
                        + mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex),
                    expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
            return this;
        }

@@ -840,9 +903,11 @@ public class TimeDetectorStrategyImplTest {
         * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
         * reference time.
         */
        ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) {
        ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
            TimestampedValue<Long> utcTime =
                    new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
                    new TimestampedValue<>(
                            mFakeCallback.peekElapsedRealtimeMillis(),
                            suggestedTime.toEpochMilli());
            return new ManualTimeSuggestion(utcTime);
        }

@@ -850,21 +915,33 @@ public class TimeDetectorStrategyImplTest {
         * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
         * the reference time.
         */
        TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, Long timeMillis) {
            TimestampedValue<Long> time = null;
            if (timeMillis != null) {
                time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
            }
        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(long timeMillis) {
        NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
            TimestampedValue<Long> utcTime =
                    new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
                    new TimestampedValue<>(
                            mFakeCallback.peekElapsedRealtimeMillis(),
                            suggestedTime.toEpochMilli());
            return new NetworkTimeSuggestion(utcTime);
        }

@@ -884,11 +961,9 @@ public class TimeDetectorStrategyImplTest {
                .build();
    }

    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
            int second) {
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
        cal.clear();
        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
        return cal.getTimeInMillis();
    private static Instant createUtcTime(int year, int monthInYear, int day, int hourOfDay,
            int minute, int second) {
        return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second)
                .toInstant(ZoneOffset.UTC);
    }
}