Loading src/java/com/android/internal/telephony/NitzStateMachine.java +175 −161 File changed.Preview size limit exceeded, changes collapsed. Show changes src/java/com/android/internal/telephony/TelephonyComponentFactory.java +0 −7 Original line number Diff line number Diff line Loading @@ -71,13 +71,6 @@ public class TelephonyComponentFactory { return new NitzStateMachine(phone); } /** * Returns a new {@link TimeServiceHelper} instance. */ public TimeServiceHelper makeTimeServiceHelper(Context context) { return new TimeServiceHelper(context); } public SimActivationTracker makeSimActivationTracker(Phone phone) { return new SimActivationTracker(phone); } Loading src/java/com/android/internal/telephony/TimeServiceHelper.java +14 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,20 @@ public class TimeServiceHelper { }); } /** * Returns the same value as {@link System#currentTimeMillis()}. */ public long currentTimeMillis() { return System.currentTimeMillis(); } /** * Returns the same value as {@link SystemClock#elapsedRealtime()}. */ public long elapsedRealtime() { return SystemClock.elapsedRealtime(); } /** * Returns true if the device has an explicit time zone set. */ Loading src/java/com/android/internal/telephony/TimeZoneLookupHelper.java +200 −75 Original line number Diff line number Diff line Loading @@ -16,12 +16,11 @@ package com.android.internal.telephony; import android.util.TimeUtils; import android.text.TextUtils; import libcore.util.CountryTimeZones; import libcore.util.TimeZoneFinder; import java.util.Arrays; import java.util.Date; import java.util.TimeZone; Loading @@ -30,72 +29,150 @@ import java.util.TimeZone; */ // Non-final to allow mocking. public class TimeZoneLookupHelper { private static final int MS_PER_HOUR = 60 * 60 * 1000; /** * List of ISO codes for countries that can have an offset of * GMT+0 when not in daylight savings time. This ignores some * small places such as the Canary Islands (Spain) and * Danmarkshavn (Denmark). The list must be sorted by code. * The result of looking up a time zone using offset information (and possibly more). */ private static final String[] GMT_COUNTRY_CODES = { "bf", // Burkina Faso "ci", // Cote d'Ivoire "eh", // Western Sahara "fo", // Faroe Islands, Denmark "gb", // United Kingdom of Great Britain and Northern Ireland "gh", // Ghana "gm", // Gambia "gn", // Guinea "gw", // Guinea Bissau "ie", // Ireland "lr", // Liberia "is", // Iceland "ma", // Morocco "ml", // Mali "mr", // Mauritania "pt", // Portugal "sl", // Sierra Leone "sn", // Senegal "st", // Sao Tome and Principe "tg", // Togo }; public static final class OffsetResult { public TimeZoneLookupHelper() {} /** A zone that matches the supplied criteria. See also {@link #isOnlyMatch}. */ public final String zoneId; /** True if there is only one matching time zone for the supplied criteria. */ public final boolean isOnlyMatch; public OffsetResult(String zoneId, boolean isOnlyMatch) { this.zoneId = zoneId; this.isOnlyMatch = isOnlyMatch; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } OffsetResult result = (OffsetResult) o; if (isOnlyMatch != result.isOnlyMatch) { return false; } return zoneId.equals(result.zoneId); } @Override public int hashCode() { int result = zoneId.hashCode(); result = 31 * result + (isOnlyMatch ? 1 : 0); return result; } @Override public String toString() { return "Result{" + "zoneId='" + zoneId + '\'' + ", isOnlyMatch=" + isOnlyMatch + '}'; } } /** * Finds a time zone ID that fits the supplied NITZ and country information. * * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates * will be returned. If the current device default zone matches it will be returned in * preference to other candidates. This method can return {@code null} if no matching time * zones are found. * The result of looking up a time zone using country information. */ public static final class CountryResult { /** A time zone for the country. */ public final String zoneId; /** * True if all the time zones in the country have the same offset at {@link #whenMillis}. */ public String guessZoneIdByNitzCountry(NitzData nitzData, String isoCountryCode) { return guessZoneIdByInstantOffsetDstCountry( nitzData.getCurrentTimeInMillis(), nitzData.getLocalOffsetMillis(), nitzData.isDst(), isoCountryCode); public final boolean allZonesHaveSameOffset; /** The time associated with {@link #allZonesHaveSameOffset}. */ public final long whenMillis; public CountryResult(String zoneId, boolean allZonesHaveSameOffset, long whenMillis) { this.zoneId = zoneId; this.allZonesHaveSameOffset = allZonesHaveSameOffset; this.whenMillis = whenMillis; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CountryResult that = (CountryResult) o; if (allZonesHaveSameOffset != that.allZonesHaveSameOffset) { return false; } if (whenMillis != that.whenMillis) { return false; } return zoneId.equals(that.zoneId); } @Override public int hashCode() { int result = zoneId.hashCode(); result = 31 * result + (allZonesHaveSameOffset ? 1 : 0); result = 31 * result + (int) (whenMillis ^ (whenMillis >>> 32)); return result; } @Override public String toString() { return "CountryResult{" + "zoneId='" + zoneId + '\'' + ", allZonesHaveSameOffset=" + allZonesHaveSameOffset + ", whenMillis=" + whenMillis + '}'; } } private static final int MS_PER_HOUR = 60 * 60 * 1000; /** The last CountryTimeZones object retrieved. */ private CountryTimeZones mLastCountryTimeZones; public TimeZoneLookupHelper() {} /** * Finds a time zone ID that fits the supplied time / offset and country information. * Looks for a time zone for the supplied NITZ and country information. * * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates * will be returned. If the current device default zone matches it will be returned in * preference to other candidates. This method can return {@code null} if no matching time * zones are found. * will be returned in the result. If the current device default zone matches it will be * returned in preference to other candidates. This method can return {@code null} if no * matching time zones are found. */ public String guessZoneIdByInstantOffsetDstCountry( long timeMillis, int utcOffsetMillis, boolean isDst, String isoCountryCode) { TimeZone timeZone = TimeUtils.getTimeZone(utcOffsetMillis, isDst, timeMillis, isoCountryCode); return timeZone == null ? null : timeZone.getID(); public OffsetResult lookupByNitzCountry(NitzData nitzData, String isoCountryCode) { CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); if (countryTimeZones == null) { return null; } android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault(); CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias( nitzData.getLocalOffsetMillis(), nitzData.isDst(), nitzData.getCurrentTimeInMillis(), bias); if (offsetResult == null) { return null; } return new OffsetResult(offsetResult.mTimeZone.getID(), offsetResult.mOneMatch); } /** * Finds a time zone ID using only information present in the supplied {@link NitzData} object. * Looks for a time zone using only information present in the supplied {@link NitzData} object. * * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given * time this process is error prone; an arbitrary match is returned when there are multiple Loading @@ -103,9 +180,8 @@ public class TimeZoneLookupHelper { * information provided by NITZ is incorrect. This method can return {@code null} if no matching * time zones are found. */ public String guessZoneIdByNitz(NitzData nitzData) { TimeZone zone = guessZoneByNitzStatic(nitzData); return zone == null ? null : zone.getID(); public OffsetResult lookupByNitz(NitzData nitzData) { return lookupByNitzStatic(nitzData); } /** Loading @@ -115,59 +191,108 @@ public class TimeZoneLookupHelper { * according to the device's current system clock time). If this is not the case then * {@code null} can be returned. */ public String guessZoneIdByCountry(String isoCountryCode, long whenMillis) { CountryTimeZones countryTimeZones = TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode); public CountryResult lookupByCountry(String isoCountryCode, long whenMillis) { CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); if (countryTimeZones == null) { // Unknown country code. return null; } if (countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis)) { return countryTimeZones.getDefaultTimeZoneId(); } if (countryTimeZones.getDefaultTimeZoneId() == null) { return null; } /** Static method for use by {@link ServiceStateTracker}. */ return new CountryResult( countryTimeZones.getDefaultTimeZoneId(), countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis), whenMillis); } /** * Finds a time zone using only information present in the supplied {@link NitzData} object. * This is a static method for use by {@link ServiceStateTracker}. * * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given * time this process is error prone; an arbitrary match is returned when there are multiple * candidates. The algorithm can also return a non-exact match by assuming that the DST * information provided by NITZ is incorrect. This method can return {@code null} if no matching * time zones are found. */ static TimeZone guessZoneByNitzStatic(NitzData nitzData) { OffsetResult result = lookupByNitzStatic(nitzData); return result != null ? TimeZone.getTimeZone(result.zoneId) : null; } private static OffsetResult lookupByNitzStatic(NitzData nitzData) { int utcOffsetMillis = nitzData.getLocalOffsetMillis(); boolean isDst = nitzData.isDst(); long timeMillis = nitzData.getCurrentTimeInMillis(); TimeZone guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (guess == null) { OffsetResult match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (match == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); } return guess; return match; } private static TimeZone guessByInstantOffsetDst(long timeMillis, int utcOffsetMillis, private static OffsetResult lookupByInstantOffsetDst(long timeMillis, int utcOffsetMillis, boolean isDst) { int rawOffset = utcOffsetMillis; if (isDst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; TimeZone match = null; Date d = new Date(timeMillis); boolean isOnlyMatch = true; for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) { guess = tz; if (match == null) { match = tz; } else { isOnlyMatch = false; break; } } } return guess; if (match == null) { return null; } return new OffsetResult(match.getID(), isOnlyMatch); } /** * Returns {@code true} if the supplied (lower-case) ISO country code is for a country known to * use a raw offset of zero from UTC. * use a raw offset of zero from UTC at the time specified. */ public boolean countryUsesUtc(String isoCountryCode) { return Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) >= 0; public boolean countryUsesUtc(String isoCountryCode, long whenMillis) { if (TextUtils.isEmpty(isoCountryCode)) { return false; } CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); return countryTimeZones != null && countryTimeZones.hasUtcZone(whenMillis); } private CountryTimeZones getCountryTimeZones(String isoCountryCode) { // A single entry cache of the last CountryTimeZones object retrieved since there should // be strong consistency across calls. synchronized (this) { if (mLastCountryTimeZones != null) { if (mLastCountryTimeZones.isForCountryCode(isoCountryCode)) { return mLastCountryTimeZones; } } // Perform the lookup. It's very unlikely to return null, but we won't cache null. CountryTimeZones countryTimeZones = TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode); if (countryTimeZones != null) { mLastCountryTimeZones = countryTimeZones; } return countryTimeZones; } } } tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTest.java +38 −34 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult; import com.android.internal.telephony.util.TimeStampedValue; import org.junit.After; Loading @@ -51,6 +52,9 @@ public class NitzStateMachineTest extends TelephonyTest { @Mock private TimeZoneLookupHelper mTimeZoneLookupHelper; @Mock private TimeServiceHelper mTimeServiceHelper; private NitzStateMachine mNitzStateMachine; @Before Loading @@ -62,8 +66,8 @@ public class NitzStateMachineTest extends TelephonyTest { when(mDeviceState.getIgnoreNitz()).thenReturn(false); when(mDeviceState.getNitzUpdateDiffMillis()).thenReturn(2000); when(mDeviceState.getNitzUpdateSpacingMillis()).thenReturn(1000 * 60 * 10); when(mDeviceState.elapsedRealtime()).thenReturn(123456789L); when(mDeviceState.currentTimeMillis()).thenReturn(987654321L); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(123456789L); when(mTimeServiceHelper.currentTimeMillis()).thenReturn(987654321L); mNitzStateMachine = new NitzStateMachine( mPhone, mTimeServiceHelper, mDeviceState, mTimeZoneLookupHelper); Loading @@ -78,8 +82,6 @@ public class NitzStateMachineTest extends TelephonyTest { verify(mDeviceState, atLeast(0)).getIgnoreNitz(); verify(mDeviceState, atLeast(0)).getNitzUpdateDiffMillis(); verify(mDeviceState, atLeast(0)).getNitzUpdateSpacingMillis(); verify(mDeviceState, atLeast(0)).elapsedRealtime(); verify(mDeviceState, atLeast(0)).currentTimeMillis(); verify(mDeviceState, atLeast(0)).getNetworkCountryIsoForPhone(); verifyNoMoreInteractions(mDeviceState); Loading @@ -90,6 +92,8 @@ public class NitzStateMachineTest extends TelephonyTest { verify(mTimeServiceHelper, atLeast(0)).isTimeDetectionEnabled(); verify(mTimeServiceHelper, atLeast(0)).isTimeZoneDetectionEnabled(); verify(mTimeServiceHelper, atLeast(0)).isTimeZoneSettingInitialized(); verify(mTimeServiceHelper, atLeast(0)).elapsedRealtime(); verify(mTimeServiceHelper, atLeast(0)).currentTimeMillis(); verifyNoMoreInteractions(mTimeServiceHelper); super.tearDown(); Loading @@ -110,9 +114,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -122,15 +126,15 @@ public class NitzStateMachineTest extends TelephonyTest { // Check resulting state and side effects. long expectedAdjustedCurrentTimeMillis = testNitzSignal.getAdjustedCurrentTimeMillis(mDeviceState.elapsedRealtime()); testNitzSignal.getAdjustedCurrentTimeMillis(mTimeServiceHelper.elapsedRealtime()); verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); verifyTimeServiceTimeWasSet(expectedAdjustedCurrentTimeMillis); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -149,9 +153,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -161,14 +165,14 @@ public class NitzStateMachineTest extends TelephonyTest { // Check resulting state and side effects. long expectedAdjustedCurrentTimeMillis = testNitzSignal.getAdjustedCurrentTimeMillis(mDeviceState.elapsedRealtime()); testNitzSignal.getAdjustedCurrentTimeMillis(mTimeServiceHelper.elapsedRealtime()); verifyTimeServiceTimeZoneWasNotSet(); verifyTimeServiceTimeWasSet(expectedAdjustedCurrentTimeMillis); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -186,9 +190,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -197,12 +201,12 @@ public class NitzStateMachineTest extends TelephonyTest { mNitzStateMachine.handleNitzReceived(testNitzSignal.asTimeStampedValue()); // Check resulting state and side effects. verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); verifyTimeServiceTimeWasNotSet(); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -221,9 +225,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -237,7 +241,7 @@ public class NitzStateMachineTest extends TelephonyTest { assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading Loading @@ -271,22 +275,22 @@ public class NitzStateMachineTest extends TelephonyTest { // // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the country being known. when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(US_ISO_CODE); mNitzStateMachine.handleNetworkCountryCodeSet(true); // Check resulting state and side effects. verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); // TODO(nfuller): The following line should probably be assertTrue but the logic under test // is probably buggy. Fix. assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } private void verifyTimeServiceTimeZoneWasNotSet() { Loading @@ -308,10 +312,10 @@ public class NitzStateMachineTest extends TelephonyTest { } private void incrementSimulatedDeviceClock(int incMillis) { long currentElapsedRealtime = mDeviceState.elapsedRealtime(); when(mDeviceState.elapsedRealtime()).thenReturn(currentElapsedRealtime + incMillis); long currentTimeMillis = mDeviceState.currentTimeMillis(); when(mDeviceState.elapsedRealtime()).thenReturn(currentTimeMillis + incMillis); long currentElapsedRealtime = mTimeServiceHelper.elapsedRealtime(); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentElapsedRealtime + incMillis); long currentTimeMillis = mTimeServiceHelper.currentTimeMillis(); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentTimeMillis + incMillis); } private static long createTime(TimeZone timeZone, int year, int monthOfYear, int dayOfMonth, Loading @@ -327,7 +331,7 @@ public class NitzStateMachineTest extends TelephonyTest { * receivedRealtimeMillis from the current {@code mDeviceState.elapsedRealtime()}. */ private TestNitzSignal createTestNitzSignal() { long receivedRealtimeMillis = mDeviceState.elapsedRealtime(); long receivedRealtimeMillis = mTimeServiceHelper.elapsedRealtime(); // Create an arbitrary time. long timeMillis = createTime(TimeZone.getTimeZone("UTC"), 2017, 1, 2, 12, 45, 25); // Create arbitrary NITZ data. Loading Loading
src/java/com/android/internal/telephony/NitzStateMachine.java +175 −161 File changed.Preview size limit exceeded, changes collapsed. Show changes
src/java/com/android/internal/telephony/TelephonyComponentFactory.java +0 −7 Original line number Diff line number Diff line Loading @@ -71,13 +71,6 @@ public class TelephonyComponentFactory { return new NitzStateMachine(phone); } /** * Returns a new {@link TimeServiceHelper} instance. */ public TimeServiceHelper makeTimeServiceHelper(Context context) { return new TimeServiceHelper(context); } public SimActivationTracker makeSimActivationTracker(Phone phone) { return new SimActivationTracker(phone); } Loading
src/java/com/android/internal/telephony/TimeServiceHelper.java +14 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,20 @@ public class TimeServiceHelper { }); } /** * Returns the same value as {@link System#currentTimeMillis()}. */ public long currentTimeMillis() { return System.currentTimeMillis(); } /** * Returns the same value as {@link SystemClock#elapsedRealtime()}. */ public long elapsedRealtime() { return SystemClock.elapsedRealtime(); } /** * Returns true if the device has an explicit time zone set. */ Loading
src/java/com/android/internal/telephony/TimeZoneLookupHelper.java +200 −75 Original line number Diff line number Diff line Loading @@ -16,12 +16,11 @@ package com.android.internal.telephony; import android.util.TimeUtils; import android.text.TextUtils; import libcore.util.CountryTimeZones; import libcore.util.TimeZoneFinder; import java.util.Arrays; import java.util.Date; import java.util.TimeZone; Loading @@ -30,72 +29,150 @@ import java.util.TimeZone; */ // Non-final to allow mocking. public class TimeZoneLookupHelper { private static final int MS_PER_HOUR = 60 * 60 * 1000; /** * List of ISO codes for countries that can have an offset of * GMT+0 when not in daylight savings time. This ignores some * small places such as the Canary Islands (Spain) and * Danmarkshavn (Denmark). The list must be sorted by code. * The result of looking up a time zone using offset information (and possibly more). */ private static final String[] GMT_COUNTRY_CODES = { "bf", // Burkina Faso "ci", // Cote d'Ivoire "eh", // Western Sahara "fo", // Faroe Islands, Denmark "gb", // United Kingdom of Great Britain and Northern Ireland "gh", // Ghana "gm", // Gambia "gn", // Guinea "gw", // Guinea Bissau "ie", // Ireland "lr", // Liberia "is", // Iceland "ma", // Morocco "ml", // Mali "mr", // Mauritania "pt", // Portugal "sl", // Sierra Leone "sn", // Senegal "st", // Sao Tome and Principe "tg", // Togo }; public static final class OffsetResult { public TimeZoneLookupHelper() {} /** A zone that matches the supplied criteria. See also {@link #isOnlyMatch}. */ public final String zoneId; /** True if there is only one matching time zone for the supplied criteria. */ public final boolean isOnlyMatch; public OffsetResult(String zoneId, boolean isOnlyMatch) { this.zoneId = zoneId; this.isOnlyMatch = isOnlyMatch; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } OffsetResult result = (OffsetResult) o; if (isOnlyMatch != result.isOnlyMatch) { return false; } return zoneId.equals(result.zoneId); } @Override public int hashCode() { int result = zoneId.hashCode(); result = 31 * result + (isOnlyMatch ? 1 : 0); return result; } @Override public String toString() { return "Result{" + "zoneId='" + zoneId + '\'' + ", isOnlyMatch=" + isOnlyMatch + '}'; } } /** * Finds a time zone ID that fits the supplied NITZ and country information. * * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates * will be returned. If the current device default zone matches it will be returned in * preference to other candidates. This method can return {@code null} if no matching time * zones are found. * The result of looking up a time zone using country information. */ public static final class CountryResult { /** A time zone for the country. */ public final String zoneId; /** * True if all the time zones in the country have the same offset at {@link #whenMillis}. */ public String guessZoneIdByNitzCountry(NitzData nitzData, String isoCountryCode) { return guessZoneIdByInstantOffsetDstCountry( nitzData.getCurrentTimeInMillis(), nitzData.getLocalOffsetMillis(), nitzData.isDst(), isoCountryCode); public final boolean allZonesHaveSameOffset; /** The time associated with {@link #allZonesHaveSameOffset}. */ public final long whenMillis; public CountryResult(String zoneId, boolean allZonesHaveSameOffset, long whenMillis) { this.zoneId = zoneId; this.allZonesHaveSameOffset = allZonesHaveSameOffset; this.whenMillis = whenMillis; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CountryResult that = (CountryResult) o; if (allZonesHaveSameOffset != that.allZonesHaveSameOffset) { return false; } if (whenMillis != that.whenMillis) { return false; } return zoneId.equals(that.zoneId); } @Override public int hashCode() { int result = zoneId.hashCode(); result = 31 * result + (allZonesHaveSameOffset ? 1 : 0); result = 31 * result + (int) (whenMillis ^ (whenMillis >>> 32)); return result; } @Override public String toString() { return "CountryResult{" + "zoneId='" + zoneId + '\'' + ", allZonesHaveSameOffset=" + allZonesHaveSameOffset + ", whenMillis=" + whenMillis + '}'; } } private static final int MS_PER_HOUR = 60 * 60 * 1000; /** The last CountryTimeZones object retrieved. */ private CountryTimeZones mLastCountryTimeZones; public TimeZoneLookupHelper() {} /** * Finds a time zone ID that fits the supplied time / offset and country information. * Looks for a time zone for the supplied NITZ and country information. * * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates * will be returned. If the current device default zone matches it will be returned in * preference to other candidates. This method can return {@code null} if no matching time * zones are found. * will be returned in the result. If the current device default zone matches it will be * returned in preference to other candidates. This method can return {@code null} if no * matching time zones are found. */ public String guessZoneIdByInstantOffsetDstCountry( long timeMillis, int utcOffsetMillis, boolean isDst, String isoCountryCode) { TimeZone timeZone = TimeUtils.getTimeZone(utcOffsetMillis, isDst, timeMillis, isoCountryCode); return timeZone == null ? null : timeZone.getID(); public OffsetResult lookupByNitzCountry(NitzData nitzData, String isoCountryCode) { CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); if (countryTimeZones == null) { return null; } android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault(); CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias( nitzData.getLocalOffsetMillis(), nitzData.isDst(), nitzData.getCurrentTimeInMillis(), bias); if (offsetResult == null) { return null; } return new OffsetResult(offsetResult.mTimeZone.getID(), offsetResult.mOneMatch); } /** * Finds a time zone ID using only information present in the supplied {@link NitzData} object. * Looks for a time zone using only information present in the supplied {@link NitzData} object. * * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given * time this process is error prone; an arbitrary match is returned when there are multiple Loading @@ -103,9 +180,8 @@ public class TimeZoneLookupHelper { * information provided by NITZ is incorrect. This method can return {@code null} if no matching * time zones are found. */ public String guessZoneIdByNitz(NitzData nitzData) { TimeZone zone = guessZoneByNitzStatic(nitzData); return zone == null ? null : zone.getID(); public OffsetResult lookupByNitz(NitzData nitzData) { return lookupByNitzStatic(nitzData); } /** Loading @@ -115,59 +191,108 @@ public class TimeZoneLookupHelper { * according to the device's current system clock time). If this is not the case then * {@code null} can be returned. */ public String guessZoneIdByCountry(String isoCountryCode, long whenMillis) { CountryTimeZones countryTimeZones = TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode); public CountryResult lookupByCountry(String isoCountryCode, long whenMillis) { CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); if (countryTimeZones == null) { // Unknown country code. return null; } if (countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis)) { return countryTimeZones.getDefaultTimeZoneId(); } if (countryTimeZones.getDefaultTimeZoneId() == null) { return null; } /** Static method for use by {@link ServiceStateTracker}. */ return new CountryResult( countryTimeZones.getDefaultTimeZoneId(), countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis), whenMillis); } /** * Finds a time zone using only information present in the supplied {@link NitzData} object. * This is a static method for use by {@link ServiceStateTracker}. * * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given * time this process is error prone; an arbitrary match is returned when there are multiple * candidates. The algorithm can also return a non-exact match by assuming that the DST * information provided by NITZ is incorrect. This method can return {@code null} if no matching * time zones are found. */ static TimeZone guessZoneByNitzStatic(NitzData nitzData) { OffsetResult result = lookupByNitzStatic(nitzData); return result != null ? TimeZone.getTimeZone(result.zoneId) : null; } private static OffsetResult lookupByNitzStatic(NitzData nitzData) { int utcOffsetMillis = nitzData.getLocalOffsetMillis(); boolean isDst = nitzData.isDst(); long timeMillis = nitzData.getCurrentTimeInMillis(); TimeZone guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (guess == null) { OffsetResult match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (match == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); } return guess; return match; } private static TimeZone guessByInstantOffsetDst(long timeMillis, int utcOffsetMillis, private static OffsetResult lookupByInstantOffsetDst(long timeMillis, int utcOffsetMillis, boolean isDst) { int rawOffset = utcOffsetMillis; if (isDst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; TimeZone match = null; Date d = new Date(timeMillis); boolean isOnlyMatch = true; for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) { guess = tz; if (match == null) { match = tz; } else { isOnlyMatch = false; break; } } } return guess; if (match == null) { return null; } return new OffsetResult(match.getID(), isOnlyMatch); } /** * Returns {@code true} if the supplied (lower-case) ISO country code is for a country known to * use a raw offset of zero from UTC. * use a raw offset of zero from UTC at the time specified. */ public boolean countryUsesUtc(String isoCountryCode) { return Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) >= 0; public boolean countryUsesUtc(String isoCountryCode, long whenMillis) { if (TextUtils.isEmpty(isoCountryCode)) { return false; } CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode); return countryTimeZones != null && countryTimeZones.hasUtcZone(whenMillis); } private CountryTimeZones getCountryTimeZones(String isoCountryCode) { // A single entry cache of the last CountryTimeZones object retrieved since there should // be strong consistency across calls. synchronized (this) { if (mLastCountryTimeZones != null) { if (mLastCountryTimeZones.isForCountryCode(isoCountryCode)) { return mLastCountryTimeZones; } } // Perform the lookup. It's very unlikely to return null, but we won't cache null. CountryTimeZones countryTimeZones = TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode); if (countryTimeZones != null) { mLastCountryTimeZones = countryTimeZones; } return countryTimeZones; } } }
tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTest.java +38 −34 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult; import com.android.internal.telephony.util.TimeStampedValue; import org.junit.After; Loading @@ -51,6 +52,9 @@ public class NitzStateMachineTest extends TelephonyTest { @Mock private TimeZoneLookupHelper mTimeZoneLookupHelper; @Mock private TimeServiceHelper mTimeServiceHelper; private NitzStateMachine mNitzStateMachine; @Before Loading @@ -62,8 +66,8 @@ public class NitzStateMachineTest extends TelephonyTest { when(mDeviceState.getIgnoreNitz()).thenReturn(false); when(mDeviceState.getNitzUpdateDiffMillis()).thenReturn(2000); when(mDeviceState.getNitzUpdateSpacingMillis()).thenReturn(1000 * 60 * 10); when(mDeviceState.elapsedRealtime()).thenReturn(123456789L); when(mDeviceState.currentTimeMillis()).thenReturn(987654321L); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(123456789L); when(mTimeServiceHelper.currentTimeMillis()).thenReturn(987654321L); mNitzStateMachine = new NitzStateMachine( mPhone, mTimeServiceHelper, mDeviceState, mTimeZoneLookupHelper); Loading @@ -78,8 +82,6 @@ public class NitzStateMachineTest extends TelephonyTest { verify(mDeviceState, atLeast(0)).getIgnoreNitz(); verify(mDeviceState, atLeast(0)).getNitzUpdateDiffMillis(); verify(mDeviceState, atLeast(0)).getNitzUpdateSpacingMillis(); verify(mDeviceState, atLeast(0)).elapsedRealtime(); verify(mDeviceState, atLeast(0)).currentTimeMillis(); verify(mDeviceState, atLeast(0)).getNetworkCountryIsoForPhone(); verifyNoMoreInteractions(mDeviceState); Loading @@ -90,6 +92,8 @@ public class NitzStateMachineTest extends TelephonyTest { verify(mTimeServiceHelper, atLeast(0)).isTimeDetectionEnabled(); verify(mTimeServiceHelper, atLeast(0)).isTimeZoneDetectionEnabled(); verify(mTimeServiceHelper, atLeast(0)).isTimeZoneSettingInitialized(); verify(mTimeServiceHelper, atLeast(0)).elapsedRealtime(); verify(mTimeServiceHelper, atLeast(0)).currentTimeMillis(); verifyNoMoreInteractions(mTimeServiceHelper); super.tearDown(); Loading @@ -110,9 +114,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -122,15 +126,15 @@ public class NitzStateMachineTest extends TelephonyTest { // Check resulting state and side effects. long expectedAdjustedCurrentTimeMillis = testNitzSignal.getAdjustedCurrentTimeMillis(mDeviceState.elapsedRealtime()); testNitzSignal.getAdjustedCurrentTimeMillis(mTimeServiceHelper.elapsedRealtime()); verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); verifyTimeServiceTimeWasSet(expectedAdjustedCurrentTimeMillis); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -149,9 +153,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -161,14 +165,14 @@ public class NitzStateMachineTest extends TelephonyTest { // Check resulting state and side effects. long expectedAdjustedCurrentTimeMillis = testNitzSignal.getAdjustedCurrentTimeMillis(mDeviceState.elapsedRealtime()); testNitzSignal.getAdjustedCurrentTimeMillis(mTimeServiceHelper.elapsedRealtime()); verifyTimeServiceTimeZoneWasNotSet(); verifyTimeServiceTimeWasSet(expectedAdjustedCurrentTimeMillis); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -186,9 +190,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -197,12 +201,12 @@ public class NitzStateMachineTest extends TelephonyTest { mNitzStateMachine.handleNitzReceived(testNitzSignal.asTimeStampedValue()); // Check resulting state and side effects. verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); verifyTimeServiceTimeWasNotSet(); assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading @@ -221,9 +225,9 @@ public class NitzStateMachineTest extends TelephonyTest { TestNitzSignal testNitzSignal = createTestNitzSignal(); // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the elapsedRealtime() value incrementing with the passage of time. incrementSimulatedDeviceClock(1000); Loading @@ -237,7 +241,7 @@ public class NitzStateMachineTest extends TelephonyTest { assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } @Test Loading Loading @@ -271,22 +275,22 @@ public class NitzStateMachineTest extends TelephonyTest { // // Configure expected time zone lookup and the result. String testTimeZoneId = US_TIME_ZONE_ID; when(mTimeZoneLookupHelper.guessZoneIdByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testTimeZoneId); OffsetResult testLookupResult = new OffsetResult(US_TIME_ZONE_ID, true /* oneMatch */); when(mTimeZoneLookupHelper.lookupByNitzCountry( testNitzSignal.getNitzData(), US_ISO_CODE)).thenReturn(testLookupResult); // Simulate the country being known. when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(US_ISO_CODE); mNitzStateMachine.handleNetworkCountryCodeSet(true); // Check resulting state and side effects. verifyTimeServiceTimeZoneWasSet(testTimeZoneId); verifyTimeServiceTimeZoneWasSet(testLookupResult.zoneId); // TODO(nfuller): The following line should probably be assertTrue but the logic under test // is probably buggy. Fix. assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful()); assertEquals(testNitzSignal.getNitzData(), mNitzStateMachine.getCachedNitzData()); assertEquals(testTimeZoneId, mNitzStateMachine.getSavedTimeZoneId()); assertEquals(testLookupResult.zoneId, mNitzStateMachine.getSavedTimeZoneId()); } private void verifyTimeServiceTimeZoneWasNotSet() { Loading @@ -308,10 +312,10 @@ public class NitzStateMachineTest extends TelephonyTest { } private void incrementSimulatedDeviceClock(int incMillis) { long currentElapsedRealtime = mDeviceState.elapsedRealtime(); when(mDeviceState.elapsedRealtime()).thenReturn(currentElapsedRealtime + incMillis); long currentTimeMillis = mDeviceState.currentTimeMillis(); when(mDeviceState.elapsedRealtime()).thenReturn(currentTimeMillis + incMillis); long currentElapsedRealtime = mTimeServiceHelper.elapsedRealtime(); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentElapsedRealtime + incMillis); long currentTimeMillis = mTimeServiceHelper.currentTimeMillis(); when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentTimeMillis + incMillis); } private static long createTime(TimeZone timeZone, int year, int monthOfYear, int dayOfMonth, Loading @@ -327,7 +331,7 @@ public class NitzStateMachineTest extends TelephonyTest { * receivedRealtimeMillis from the current {@code mDeviceState.elapsedRealtime()}. */ private TestNitzSignal createTestNitzSignal() { long receivedRealtimeMillis = mDeviceState.elapsedRealtime(); long receivedRealtimeMillis = mTimeServiceHelper.elapsedRealtime(); // Create an arbitrary time. long timeMillis = createTime(TimeZone.getTimeZone("UTC"), 2017, 1, 2, 12, 45, 25); // Create arbitrary NITZ data. Loading