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

Commit f566eabe authored by Neil Fuller's avatar Neil Fuller Committed by Gerrit Code Review
Browse files

Merge "Revert "Revert "Implement country-dependent detection logic"""

parents 9d112e8e 9dbc0acb
Loading
Loading
Loading
Loading
+39 −8
Original line number Diff line number Diff line
@@ -202,6 +202,7 @@ public final class NitzStateMachineImpl implements NitzStateMachine {
            } else {
                OffsetResult lookupResult =
                        mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, isoCountryCode);
                if (lookupResult != null) {
                    if (DBG) {
                        Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: using"
                                + " lookupByNitzCountry(nitzData, isoCountryCode),"
@@ -209,7 +210,35 @@ public final class NitzStateMachineImpl implements NitzStateMachine {
                                + " isoCountryCode=" + isoCountryCode
                                + " lookupResult=" + lookupResult);
                    }
                zoneId = lookupResult != null ? lookupResult.getTimeZone().getID() : null;
                    zoneId = lookupResult.getTimeZone().getID();
                } else {
                    // The country + offset provided no match, so see if the country by itself
                    // would be enough.
                    CountryResult countryResult = mTimeZoneLookupHelper.lookupByCountry(
                            isoCountryCode, nitzData.getCurrentTimeInMillis());
                    if (DBG) {
                        Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: fallback to"
                                + " lookupByCountry(isoCountryCode, whenMillis),"
                                + " nitzData=" + nitzData
                                + " isoCountryCode=" + isoCountryCode
                                + " countryResult=" + countryResult);
                    }
                    if (countryResult != null) {
                        // If the country has a single zone, or it has multiple zones but the
                        // default is "boosted" (i.e. it is considered a good result in most cases)
                        // then use it.
                        if (countryResult.quality == CountryResult.QUALITY_SINGLE_ZONE
                                || countryResult.quality == CountryResult.QUALITY_DEFAULT_BOOSTED) {
                            zoneId = countryResult.zoneId;
                        } else {
                            // Quality is not high enough.
                            zoneId = null;
                        }
                    } else {
                        // Country not recognized.
                        zoneId = null;
                    }
                }
            }

            String logMsg = "updateTimeZoneFromCountryAndNitz:"
@@ -490,7 +519,9 @@ public final class NitzStateMachineImpl implements NitzStateMachine {
    private void updateTimeZoneFromNetworkCountryCode(String iso) {
        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
                iso, mDeviceState.currentTimeMillis());
        if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
        if (lookupResult != null
                && (lookupResult.quality == CountryResult.QUALITY_SINGLE_ZONE
                        || lookupResult.quality == CountryResult.QUALITY_DEFAULT_BOOSTED)) {
            String logMsg = "updateTimeZoneFromNetworkCountryCode: tz result found"
                    + " iso=" + iso
                    + " lookupResult=" + lookupResult;
+77 −79
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.internal.telephony;

import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET;

import android.annotation.IntDef;
import android.icu.util.TimeZone;
import android.text.TextUtils;

@@ -23,6 +27,8 @@ import libcore.timezone.CountryTimeZones;
import libcore.timezone.CountryTimeZones.TimeZoneMapping;
import libcore.timezone.TimeZoneFinder;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;

@@ -94,32 +100,35 @@ public class TimeZoneLookupHelper {
     */
    public static final class CountryResult {

        @IntDef({ QUALITY_SINGLE_ZONE, QUALITY_DEFAULT_BOOSTED, QUALITY_MULTIPLE_ZONES_SAME_OFFSET,
                QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Quality {}

        public static final int QUALITY_SINGLE_ZONE = 1;
        public static final int QUALITY_DEFAULT_BOOSTED = 2;
        public static final int QUALITY_MULTIPLE_ZONES_SAME_OFFSET = 3;
        public static final int QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS = 4;

        /** A time zone to use for the country. */
        public final String zoneId;

        /**
         * True if the country has multiple effective zones to choose from at {@link #whenMillis}.
         */
        public final boolean multipleZonesInCountry;

        /**
         * True if all the effective time zones in the country have the same offset at
         * {@link #whenMillis}.
         * The quality of the match.
         */
        public final boolean allZonesHaveSameOffset;
        @Quality
        public final int quality;

        /**
         * The time associated with {@link #allZonesHaveSameOffset} and
         * {@link #multipleZonesInCountry}.
         * Freeform information about why the value of {@link #quality} was chosen. Not used for
         * {@link #equals(Object)}.
         */
        public final long whenMillis;
        private final String mDebugInfo;

        public CountryResult(String zoneId, boolean multipleZonesInCountry,
                boolean allZonesHaveSameOffset, long whenMillis) {
        public CountryResult(String zoneId, @Quality int quality, String debugInfo) {
            this.zoneId = zoneId;
            this.multipleZonesInCountry = multipleZonesInCountry;
            this.allZonesHaveSameOffset = allZonesHaveSameOffset;
            this.whenMillis = whenMillis;
            this.quality = quality;
            mDebugInfo = debugInfo;
        }

        @Override
@@ -130,37 +139,22 @@ public class TimeZoneLookupHelper {
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            CountryResult that = (CountryResult) o;

            if (multipleZonesInCountry != that.multipleZonesInCountry) {
                return false;
            }
            if (allZonesHaveSameOffset != that.allZonesHaveSameOffset) {
                return false;
            }
            if (whenMillis != that.whenMillis) {
                return false;
            }
            return zoneId.equals(that.zoneId);
            return quality == that.quality
                    && zoneId.equals(that.zoneId);
        }

        @Override
        public int hashCode() {
            int result = zoneId.hashCode();
            result = 31 * result + (multipleZonesInCountry ? 1 : 0);
            result = 31 * result + (allZonesHaveSameOffset ? 1 : 0);
            result = 31 * result + (int) (whenMillis ^ (whenMillis >>> 32));
            return result;
            return Objects.hash(zoneId, quality);
        }

        @Override
        public String toString() {
            return "CountryResult{"
                    + "zoneId='" + zoneId + '\''
                    + ", multipleZonesInCountry=" + multipleZonesInCountry
                    + ", allZonesHaveSameOffset=" + allZonesHaveSameOffset
                    + ", whenMillis=" + whenMillis
                    + ", quality=" + quality
                    + ", mDebugInfo=" + mDebugInfo
                    + '}';
        }
    }
@@ -226,42 +220,48 @@ public class TimeZoneLookupHelper {
            // Unknown country code.
            return null;
        }
        if (countryTimeZones.getDefaultTimeZoneId() == null) {
        TimeZone countryDefaultZone = countryTimeZones.getDefaultTimeZone();
        if (countryDefaultZone == null) {
            // This is not expected: the country default should have been validated before.
            return null;
        }

        String debugInfo;
        int matchQuality;
        if (countryTimeZones.getDefaultTimeZoneBoost()) {
            matchQuality = CountryResult.QUALITY_DEFAULT_BOOSTED;
            debugInfo = "Country default is boosted";
        } else {
            List<TimeZoneMapping> effectiveTimeZoneMappings =
                    countryTimeZones.getEffectiveTimeZoneMappingsAt(whenMillis);
        boolean multipleZonesInCountry = effectiveTimeZoneMappings.size() > 1;
        boolean defaultOkForCountryTimeZoneDetection = isDefaultOkForCountryTimeZoneDetection(
                countryTimeZones.getDefaultTimeZone(), effectiveTimeZoneMappings, whenMillis);
        return new CountryResult(
                countryTimeZones.getDefaultTimeZoneId(), multipleZonesInCountry,
                defaultOkForCountryTimeZoneDetection, whenMillis);
    }

    /**
     * Returns {@code true} if the default time zone for the country is either the only zone used or
     * if it has the same offsets as all other zones used by the country <em>at the specified time
     * </em> making the default equivalent to all other zones used by the country <em>at that time
     * </em>.
     */
    private static boolean isDefaultOkForCountryTimeZoneDetection(
            TimeZone countryDefault, List<TimeZoneMapping> timeZoneMappings, long whenMillis) {
        if (timeZoneMappings.isEmpty()) {
            // Should never happen unless there's been an error loading the data.
            return false;
        } else if (timeZoneMappings.size() == 1) {
            if (effectiveTimeZoneMappings.isEmpty()) {
                // This should never happen unless there's been an error loading the data.
                // Treat it the same as a low quality answer.
                matchQuality = QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
                debugInfo = "No effective time zones found at whenMillis=" + whenMillis;
            } else if (effectiveTimeZoneMappings.size() == 1) {
                // The default is the only zone so it's a good candidate.
            return true;
                matchQuality = CountryResult.QUALITY_SINGLE_ZONE;
                debugInfo = "One effective time zone found at whenMillis=" + whenMillis;
            } else {
            if (countryDefault == null) {
                return false;
                boolean countryUsesDifferentOffsets = countryUsesDifferentOffsets(
                        whenMillis, effectiveTimeZoneMappings, countryDefaultZone);
                matchQuality = countryUsesDifferentOffsets
                        ? QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS
                        : QUALITY_MULTIPLE_ZONES_SAME_OFFSET;
                debugInfo = "countryUsesDifferentOffsets=" + countryUsesDifferentOffsets + " at"
                        + " whenMillis=" + whenMillis;
            }
        }
        return new CountryResult(countryDefaultZone.getID(), matchQuality, debugInfo);
    }

            String countryDefaultId = countryDefault.getID();
            int countryDefaultOffset = countryDefault.getOffset(whenMillis);
            for (TimeZoneMapping timeZoneMapping : timeZoneMappings) {
    private static boolean countryUsesDifferentOffsets(
            long whenMillis, List<TimeZoneMapping> effectiveTimeZoneMappings,
            TimeZone countryDefaultZone) {
        String countryDefaultId = countryDefaultZone.getID();
        int countryDefaultOffset = countryDefaultZone.getOffset(whenMillis);
        for (TimeZoneMapping timeZoneMapping : effectiveTimeZoneMappings) {
            if (timeZoneMapping.timeZoneId.equals(countryDefaultId)) {
                continue;
            }
@@ -273,13 +273,11 @@ public class TimeZoneLookupHelper {

            int candidateOffset = timeZone.getOffset(whenMillis);
            if (countryDefaultOffset != candidateOffset) {
                    // Multiple different offsets means the default should not be used.
                    return false;
                }
            }
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a time zone ID for the country if possible. For counties that use a single time zone
+136 −11
Original line number Diff line number Diff line
@@ -19,7 +19,10 @@ package com.android.internal.telephony;
import static com.android.internal.telephony.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
import static com.android.internal.telephony.NitzStateMachineTestSupport.ARBITRARY_TIME_ZONE_ID;
import static com.android.internal.telephony.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_OTHER_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO2;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.createTimeSignalFromNitzSignal;

@@ -74,7 +77,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_uniqueUsZone_timeZoneEnabled_countryThenNitz() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -120,9 +123,131 @@ public class NitzStateMachineImplTest extends TelephonyTest {
        assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
    }

    @Test
    public void test_countryDefaultBoost_timeZoneEnabled_countryThenNitz() throws Exception {
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
                .initializeTimeZoneSetting("Europe/London");

        // Demonstrate the defaultTimeZoneBoost behavior: we can get a zone only from the
        // countryIsoCode.
        {
            Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            script.countryReceived(scenario.getNetworkCountryIsoCode())
                    // Even though there are multiple zones in the country, countryIsoCode is enough
                    // to guess the time zone because the default zone is boosted.
                    .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());

            // Confirm what happens when NITZ doesn't conflict with the country-only result.
            script.nitzReceived(nitzSignal)
                    // Country + NITZ is enough for both time + time zone detection.
                    .verifyTimeSuggestedAndZoneSetAndReset(
                            scenario.createTimeSignal(mFakeDeviceState.elapsedRealtime()),
                            scenario.getTimeZoneId());

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }

        // A valid NITZ signal for the non-default zone should still be correctly detected.
        {
            Scenario scenario = NEW_ZEALAND_OTHER_SCENARIO;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            script.nitzReceived(nitzSignal)
                    // Time won't be set because the UTC signal will be the same.
                    .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }

        // Demonstrate what happens with a bogus NITZ for NZ: because the default zone is boosted
        // then we should return to the country default zone.
        {
            // A scenario that has a different offset than NZ.
            Scenario scenario = CZECHIA_SCENARIO;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            String expectedTimeZoneId = NEW_ZEALAND_DEFAULT_SCENARIO.getTimeZoneId();
            script.nitzReceived(nitzSignal)
                    // Time won't be set because the UTC signal will be the same.
                    .verifyOnlyTimeZoneWasSetAndReset(expectedTimeZoneId);

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }
    }

    @Test
    public void test_noCountryDefaultBoost_timeZoneEnabled_countryThenNitz() throws Exception {
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
                .initializeTimeZoneSetting("Europe/London");

        // Demonstrate the behavior without default country boost for a country with multiple zones:
        // we cannot get a zone only from the countryIsoCode.
        {
            Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            script.countryReceived(scenario.getNetworkCountryIsoCode())
                    // The country should no be enough to guess time zone.
                    .verifyNothingWasSetAndReset();

            // The NITZ signal + country iso code will be enough.
            script.nitzReceived(nitzSignal)
                    // Country + NITZ is enough for both time + time zone detection.
                    .verifyTimeSuggestedAndZoneSetAndReset(
                            scenario.createTimeSignal(mFakeDeviceState.elapsedRealtime()),
                            scenario.getTimeZoneId());

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }

        // A valid NITZ signal for a different zone should also be correctly detected.
        {
            Scenario scenario = UNIQUE_US_ZONE_SCENARIO2;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            script.nitzReceived(nitzSignal)
                    // Time won't be set because the UTC signal will be the same.
                    .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }

        // Demonstrate what happens with a bogus NITZ for US: because the default zone is not
        // boosted we should not change anything.
        {
            // A scenario that has a different offset than US.
            Scenario scenario = CZECHIA_SCENARIO;
            TimestampedValue<NitzData> nitzSignal =
                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
            script.nitzReceived(nitzSignal)
                    // Time won't be set because the UTC signal will be the same.
                    .verifyNothingWasSetAndReset();

            // Check NitzStateMachine state.
            assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
            assertEquals(nitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
        }
    }

    @Test
    public void test_uniqueUsZone_timeZoneDisabled_countryThenNitz() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(false)
@@ -168,7 +293,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_uniqueUsZone_timeZoneEnabled_nitzThenCountry() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -368,7 +493,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_bogusUniqueUsNitzSignal_nitzReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -408,7 +533,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_bogusUsUniqueNitzSignal_countryReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -447,7 +572,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -480,7 +605,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_emptyCountryStringUsTime_countryReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -510,7 +635,7 @@ public class NitzStateMachineImplTest extends TelephonyTest {

    @Test
    public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
        Script script = new Script()
                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
                .initializeTimeZoneDetectionEnabled(true)
@@ -615,8 +740,8 @@ public class NitzStateMachineImplTest extends TelephonyTest {
        script.incrementTime(timeStepMillis);

        // Simulate the movement to the destination.
        scenario.changeCountry(UNIQUE_US_ZONE_SCENARIO.getTimeZoneId(),
                UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
        scenario.changeCountry(UNIQUE_US_ZONE_SCENARIO1.getTimeZoneId(),
                UNIQUE_US_ZONE_SCENARIO1.getNetworkCountryIsoCode());

        // Simulate the device receiving NITZ signals again after the flight. Now the
        // NitzStateMachineImpl is opinionated again.
+27 −4

File changed.

Preview size limit exceeded, changes collapsed.

+80 −26

File changed.

Preview size limit exceeded, changes collapsed.

Loading