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

Commit e90eaa72 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Remove time compensation for bad NITZ offset info"

parents db70f408 4e9dc146
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;