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

Commit 4e9dc146 authored by Neil Fuller's avatar Neil Fuller
Browse files

Remove time compensation for bad NITZ offset info

Before this commit:

When an NITZ signal has been received but at the time
the country was unknown, when a country code becomes
known.....

...if
1) The device has had a time zone explicitly
set (i.e. this isn't the first boot)
2) The most recently received NITZ signal had:
a) Either an empty "isDst" field or a "false"
b) A zero offset from UTC
3) The country is known not to use UTC...

...the device would assume that the NITZ offset information
it is receiving is incorrect in very specific way: It assumed
that the NITZ time was in local time (not UTC as per NITZ spec)
AND the time zone the device was currently using was correct.

It would then attempt to set the device system clock
by reversing out the assumed time zone adjustment from the
UTC time.

If the country code became know before NITZ was received the time
would not be corrected in this way.

The adjustment of the system clock results in verifiable issues for
users: The assumption that the time signal is in local time is
incorrect and leads to incorrect system clocks on devices.

The usual cause of bad NITZ signals appears to be that the local
offset information has been lost somehow or that the network has
made a mistake about the offset. In these cases the user ends up
with a bad system clock that is offset from UTC by the inverse
of the offset for their zone.

After this commit:

If an NITZ signal is received before or after the country code
is known and the offset information looks bogus it will not
be used to set the time zone.

This change has the side effect of making the code more consistent
and independent of the order the information was received.

Note: Devices that receive NITZ information containing bad
offset information will still set the system clock using the UTC
information from the NITZ signal.

This change is removing untested behavior so there are no existing
test changes. Existing tests run as before but more have been added.

Historical info:

The logic being remove was copied over to NitzStateMachine from
ServiceStateTracker with a tweak to adjust the NITZ signal time, not
the current system clock since it seemed more logical - the original
logic would potentially keep adjusting the device system clock making
it more and more incorrect.

The time adjustment behavior was originally introduced to
frameworks/opt/telephony ServiceStateTracker in commit 1a87ab3d.

Before that, the logic was present in very early
versions of Android. See commit df7bbfd7c in frameworks/base for
some adjustments and commit 9066cfe (the first git commit) and
com/android/internal/telephony/gsm/ServiceStateTracker.java.
(See usages of GMT_COUNTRY_CODES).

Test: atest FrameworksTelephonyTests:com.android.internal.telephony.NitzStateMachineTest
Test: atest FrameworksTelephonyTests:com.android.internal.telephony.ServiceStateTrackerTest
Bug: 69593701
Bug: 78217059
Change-Id: Idb6220629d07f3bdba9a1a16ad07a92485e0ba99
parent b941c937
Loading
Loading
Loading
Loading
+42 −75
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.TimeUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
@@ -36,7 +35,6 @@ import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.TimeZone;

/**
 * {@hide}
@@ -241,67 +239,17 @@ public class NitzStateMachine {
                            + " not setting zone");
                }
            } else { // mLatestNitzSignal != null
                if (nitzOffsetMightBeBogus(mLatestNitzSignal.mValue)
                        && isTimeZoneSettingInitialized
                        && !countryUsesUtc(isoCountryCode, mLatestNitzSignal)) {

                    // This case means that (1) the device received an NITZ signal that could be
                    // bogus due to having a zero offset from UTC, (2) the device has had a time
                    // zone set explicitly and (3) the iso tells us the country is NOT one that uses
                    // a zero offset. This is interpreted as being NITZ incorrectly reporting a
                    // local time and not a UTC time. The zone is left as the current device's zone
                    // setting, and the system clock may be adjusted by taking the NITZ time and
                    // assuming the current zone setting is correct.

                    TimeZone zone = TimeZone.getDefault();
                if (isNitzSignalOffsetInfoBogus(mLatestNitzSignal, isoCountryCode)) {
                    String logMsg = "handleNetworkCountryCodeSet: Stored NITZ looks bogus, "
                            + " isoCountryCode=" + isoCountryCode
                            + " mLatestNitzSignal=" + mLatestNitzSignal;
                    if (DBG) {
                        Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: NITZ looks bogus, maybe using"
                                + " current default zone to adjust the system clock,"
                                + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
                                + " mLatestNitzSignal=" + mLatestNitzSignal
                                + " zone=" + zone);
                        Rlog.d(LOG_TAG, logMsg);
                    }
                    zoneId = zone.getID();

                    if (mNeedCountryCodeForNitz) {
                        NitzData nitzData = mLatestNitzSignal.mValue;
                        try {
                            // Acquire the wakelock as we're reading the elapsed realtime clock
                            // here.
                            mWakeLock.acquire();
                    mTimeZoneLog.log(logMsg);

                            // Use the time that came with the NITZ offset that we think is bogus:
                            // we just interpret it as local time.
                            long ctm = nitzData.getCurrentTimeInMillis();
                            long delayAdjustedCtm = ctm + (mTimeServiceHelper.elapsedRealtime()
                                    - mLatestNitzSignal.mElapsedRealtime);
                            long tzOffset = zone.getOffset(delayAdjustedCtm);
                            if (DBG) {
                                Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
                                        + " tzOffset=" + tzOffset
                                        + " delayAdjustedCtm="
                                        + TimeUtils.logTimeOfDay(delayAdjustedCtm));
                            }
                            if (mTimeServiceHelper.isTimeDetectionEnabled()) {
                                long timeZoneAdjustedCtm = delayAdjustedCtm - tzOffset;
                                String msg = "handleNetworkCountryCodeSet: setting time"
                                        + " timeZoneAdjustedCtm="
                                        + TimeUtils.logTimeOfDay(timeZoneAdjustedCtm);
                                setAndBroadcastNetworkSetTime(msg, timeZoneAdjustedCtm);
                            } else {
                                // Adjust the saved NITZ time to account for tzOffset.
                                mSavedNitzTime = new TimeStampedValue<>(
                                        mSavedNitzTime.mValue - tzOffset,
                                        mSavedNitzTime.mElapsedRealtime);
                                if (DBG) {
                                    Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
                                            + "adjusting time mSavedNitzTime=" + mSavedNitzTime);
                                }
                            }
                        } finally {
                            mWakeLock.release();
                        }
                    }
                    // No zone can be determined.
                    zoneId = null;
                } else {
                    NitzData nitzData = mLatestNitzSignal.mValue;
                    OffsetResult lookupResult =
@@ -372,14 +320,6 @@ public class NitzStateMachine {
        mNitzTimeZoneDetectionSuccessful = false;
    }

    /**
     * Returns {@code true} if the NITZ data looks like it might be incomplete or bogus, i.e. it has
     * a zero offset from UTC with either no DST information available or a zero DST offset.
     */
    private static boolean nitzOffsetMightBeBogus(NitzData nitzData) {
        return nitzData.getLocalOffsetMillis() == 0 && !nitzData.isDst();
    }

    /**
     * Handle a new NITZ signal being received.
     */
@@ -391,6 +331,22 @@ public class NitzStateMachine {
        handleTimeFromNitz(nitzSignal);
    }

    /**
     * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
     */
    private boolean isNitzSignalOffsetInfoBogus(
            TimeStampedValue<NitzData> nitzSignal, String isoCountryCode) {

        if (TextUtils.isEmpty(isoCountryCode)) {
            // We cannot say for sure.
            return false;
        }

        NitzData newNitzData = nitzSignal.mValue;
        boolean zeroOffsetNitz = newNitzData.getLocalOffsetMillis() == 0 && !newNitzData.isDst();
        return zeroOffsetNitz && !countryUsesUtc(isoCountryCode, nitzSignal);
    }

    private void handleTimeZoneFromNitz(TimeStampedValue<NitzData> nitzSignal) {
        try {
            NitzData newNitzData = nitzSignal.mValue;
@@ -406,12 +362,7 @@ public class NitzStateMachine {
                    // We don't have a country code so we won't try to look up the time zone.
                    zoneId = null;
                    needCountryCode = true;
                } else if (!TextUtils.isEmpty(isoCountryCode)) {
                    OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
                            newNitzData, isoCountryCode);
                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
                    needCountryCode = false;
                } else {
                } else if (TextUtils.isEmpty(isoCountryCode)) {
                    // We have a country code but it's empty. This is most likely because we're on a
                    // test network that's using a bogus MCC (eg, "001"), so get a TimeZone
                    // based only on the NITZ parameters.
@@ -422,6 +373,22 @@ public class NitzStateMachine {
                    }
                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
                    needCountryCode = false;
                } else if (isNitzSignalOffsetInfoBogus(nitzSignal, isoCountryCode)) {
                    String logMsg = "handleNitzReceived: Received NITZ looks bogus, "
                            + " isoCountryCode=" + isoCountryCode
                            + " nitzSignal=" + nitzSignal;
                    if (DBG) {
                        Rlog.d(LOG_TAG, logMsg);
                    }
                    mTimeZoneLog.log(logMsg);

                    zoneId = null;
                    needCountryCode = false;
                } else {
                    OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
                            newNitzData, isoCountryCode);
                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
                    needCountryCode = false;
                }
            }

+274 −22
Original line number Diff line number Diff line
@@ -45,6 +45,37 @@ import org.mockito.Mock;

public class NitzStateMachineTest extends TelephonyTest {

    // A country with a single zone : the zone can be guessed from the country.
    // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
    private static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
            .setInitialDeviceRealtimeMillis(123456789L)
            .setTimeZone("Europe/London")
            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
            .setCountryIso("gb")
            .build();

    // A country that has multiple zones, but there is only one matching time zone at the time :
    // the zone cannot be guessed from the country alone, but can be guessed from the country +
    // NITZ. The US never uses UTC so it can be used for testing bogus NITZ signal handling.
    private static final Scenario UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
            .setInitialDeviceRealtimeMillis(123456789L)
            .setTimeZone("America/Los_Angeles")
            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
            .setCountryIso("us")
            .build();

    // A country with a single zone: the zone can be guessed from the country alone. CZ never uses
    // UTC so it can be used for testing bogus NITZ signal handling.
    private static final Scenario CZECHIA_SCENARIO = new Scenario.Builder()
            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
            .setInitialDeviceRealtimeMillis(123456789L)
            .setTimeZone("Europe/Prague")
            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
            .setCountryIso("cz")
            .build();

    @Mock
    private NitzStateMachine.DeviceState mDeviceState;

@@ -75,17 +106,6 @@ public class NitzStateMachineTest extends TelephonyTest {
        super.tearDown();
    }

    // A country that has multiple zones, but there is only one matching time zone at the time :
    // the zone cannot be guessed from the country alone, but can be guessed from the country +
    // NITZ.
    private static final Scenario UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
            .setInitialDeviceRealtimeMillis(123456789L)
            .setTimeZone("America/Los_Angeles")
            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
            .setCountryIso("us")
            .build();

    @Test
    public void test_uniqueUsZone_Assumptions() {
        // Check we'll get the expected behavior from TimeZoneLookupHelper.
@@ -109,15 +129,6 @@ public class NitzStateMachineTest extends TelephonyTest {
        assertEquals(expectedLookupResult, actualLookupResult);
    }

    // A country with a single zone : the zone can be guessed from the country.
    private static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
            .setInitialDeviceRealtimeMillis(123456789L)
            .setTimeZone("Europe/London")
            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
            .setCountryIso("gb")
            .build();

    @Test
    public void test_unitedKingdom_Assumptions() {
        // Check we'll get the expected behavior from TimeZoneLookupHelper.
@@ -416,6 +427,246 @@ public class NitzStateMachineTest extends TelephonyTest {
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_validCzNitzSignal_nitzReceivedFirst() throws Exception {
        Scenario scenario = CZECHIA_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Simulate receiving an NITZ signal.
        script.nitzReceived(goodNitzSignal)
                // The NITZ alone isn't enough to detect a time zone.
                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(goodNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The NITZ country is enough to detect the time zone, but the NITZ + country is
                // also sufficient so we expect the time zone to be set twice.
                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2);

        // Check NitzStateMachine state.
        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(goodNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_validCzNitzSignal_countryReceivedFirst() throws Exception {
        Scenario scenario = CZECHIA_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The NITZ country is enough to detect the time zone.
                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertNull(mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());

        // Simulate receiving an NITZ signal.
        script.nitzReceived(goodNitzSignal)
                // The time will be set from the NITZ signal.
                // The combination of NITZ + country will cause the time zone to be set.
                .verifyTimeAndZoneSetAndReset(
                        scenario.getActualTimeMillis(), scenario.getTimeZoneId());

        // Check NitzStateMachine state.
        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(goodNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_bogusCzNitzSignal_nitzReceivedFirst() throws Exception {
        Scenario scenario = CZECHIA_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Create a corrupted NITZ signal, where the offset information has been lost.
        NitzData bogusNitzData = NitzData.createForTests(
                0 /* UTC! */, null /* dstOffsetMillis */,
                goodNitzSignal.mValue.getCurrentTimeInMillis(), null /* emulatorHostTimeZone */);
        TimeStampedValue<NitzData> badNitzSignal = new TimeStampedValue<>(
                bogusNitzData, goodNitzSignal.mElapsedRealtime);

        // Simulate receiving an NITZ signal.
        script.nitzReceived(badNitzSignal)
                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
                // information to work out its bogus.
                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The country is enough to detect the time zone for CZ. If the NITZ signal
                // wasn't obviously bogus we'd try to set it twice.
                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_bogusCzNitzSignal_countryReceivedFirst() throws Exception {
        Scenario scenario = CZECHIA_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Create a corrupted NITZ signal, where the offset information has been lost.
        NitzData bogusNitzData = NitzData.createForTests(
                0 /* UTC! */, null /* dstOffsetMillis */,
                goodNitzSignal.mValue.getCurrentTimeInMillis(), null /* emulatorHostTimeZone */);
        TimeStampedValue<NitzData> badNitzSignal = new TimeStampedValue<>(
                bogusNitzData, goodNitzSignal.mElapsedRealtime);

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The country is enough to detect the time zone for CZ.
                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertNull(mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());

        // Simulate receiving an NITZ signal.
        script.nitzReceived(badNitzSignal)
                // The NITZ should be detected as bogus so only the time will be set.
                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_bogusUniqueUsNitzSignal_nitzReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Create a corrupted NITZ signal, where the offset information has been lost.
        NitzData bogusNitzData = NitzData.createForTests(
                0 /* UTC! */, null /* dstOffsetMillis */,
                goodNitzSignal.mValue.getCurrentTimeInMillis(), null /* emulatorHostTimeZone */);
        TimeStampedValue<NitzData> badNitzSignal = new TimeStampedValue<>(
                bogusNitzData, goodNitzSignal.mElapsedRealtime);

        // Simulate receiving an NITZ signal.
        script.nitzReceived(badNitzSignal)
                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
                // information to work out its bogus.
                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The country isn't enough to detect the time zone for US so we will leave the time
                // zone unset.
                .verifyNothingWasSetAndReset();

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());
    }

    @Test
    public void test_bogusUsUniqueNitzSignal_countryReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Device device = new DeviceBuilder()
                .setClocksFromScenario(scenario)
                .setTimeDetectionEnabled(true)
                .setTimeZoneDetectionEnabled(true)
                .setTimeZoneSettingInitialized(true)
                .initialize();
        Script script = new Script(device);

        TimeStampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();

        // Create a corrupted NITZ signal, where the offset information has been lost.
        NitzData bogusNitzData = NitzData.createForTests(
                0 /* UTC! */, null /* dstOffsetMillis */,
                goodNitzSignal.mValue.getCurrentTimeInMillis(), null /* emulatorHostTimeZone */);
        TimeStampedValue<NitzData> badNitzSignal = new TimeStampedValue<>(
                bogusNitzData, goodNitzSignal.mElapsedRealtime);

        // Simulate the country code becoming known.
        script.countryReceived(scenario.getNetworkCountryIsoCode())
                // The country isn't enough to detect the time zone for US so we will leave the time
                // zone unset.
                .verifyNothingWasSetAndReset();

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertNull(mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());

        // Simulate receiving an NITZ signal.
        script.nitzReceived(badNitzSignal)
                // The NITZ should be detected as bogus so only the time will be set.
                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());

        // Check NitzStateMachine state.
        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
        assertEquals(badNitzSignal.mValue, mNitzStateMachine.getCachedNitzData());
        assertNull(mNitzStateMachine.getSavedTimeZoneId());
    }

    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
            int second) {
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
@@ -647,8 +898,9 @@ public class NitzStateMachineTest extends TelephonyTest {
                int[] offsets = new int[2];
                mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
                int zoneOffsetMillis = offsets[0] + offsets[1];
                NitzData nitzData = NitzData
                        .createForTests(zoneOffsetMillis, offsets[1], mActualTimeMillis, null);
                NitzData nitzData = NitzData.createForTests(
                        zoneOffsetMillis, offsets[1], mActualTimeMillis,
                        null /* emulatorHostTimeZone */);
                mNitzSignal = new TimeStampedValue<>(nitzData, mInitialDeviceRealtimeMillis);
            }
            return mNitzSignal;