Loading src/java/com/android/internal/telephony/NitzData.java +4 −44 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; /** Loading @@ -35,7 +34,6 @@ import java.util.TimeZone; @VisibleForTesting(visibility = PACKAGE) public final class NitzData { private static final String LOG_TAG = ServiceStateTracker.LOG_TAG; private static final int MS_PER_HOUR = 60 * 60 * 1000; private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000; /* Time stamp after 19 January 2038 is not supported under 32 bit */ Loading @@ -53,7 +51,7 @@ public final class NitzData { private final TimeZone mEmulatorHostTimeZone; private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone timeZone) { long utcTimeMillis, TimeZone emulatorHostTimeZone) { if (originalString == null) { throw new NullPointerException("originalString==null"); } Loading @@ -61,7 +59,7 @@ public final class NitzData { this.mZoneOffset = zoneOffsetMillis; this.mDstOffset = dstOffsetMillis; this.mCurrentTimeMillis = utcTimeMillis; this.mEmulatorHostTimeZone = timeZone; this.mEmulatorHostTimeZone = emulatorHostTimeZone; } /** Loading Loading @@ -139,9 +137,9 @@ public final class NitzData { /** A method for use in tests to create NitzData instances. */ public static NitzData createForTests(int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone timeZone) { long utcTimeMillis, TimeZone emulatorHostTimeZone) { return new NitzData("Test data", zoneOffsetMillis, dstOffsetMillis, utcTimeMillis, timeZone); emulatorHostTimeZone); } /** Loading Loading @@ -187,44 +185,6 @@ public final class NitzData { return mEmulatorHostTimeZone; } /** * Using information present in the supplied {@link NitzData} object, guess the time zone. * 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 time zones are found. */ public static TimeZone guessTimeZone(NitzData nitzData) { int offset = nitzData.getLocalOffsetMillis(); boolean dst = nitzData.isDst(); long when = nitzData.getCurrentTimeInMillis(); TimeZone guess = findTimeZone(offset, dst, when); if (guess == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = findTimeZone(offset, !dst, when); } return guess; } private static TimeZone findTimeZone(int offset, boolean dst, long when) { int rawOffset = offset; if (dst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; Date d = new Date(when); for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(when) == offset && tz.inDaylightTime(d) == dst) { guess = tz; break; } } return guess; } @Override public boolean equals(Object o) { if (this == o) { Loading src/java/com/android/internal/telephony/NitzStateMachine.java +42 −100 Original line number Diff line number Diff line Loading @@ -30,12 +30,11 @@ import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.util.TimeStampedValue; import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.TimeZone; /** Loading Loading @@ -117,63 +116,9 @@ public class NitzStateMachine { } } /** A pair containing a value and an associated time stamp. */ private static class TimeStampedValue<T> { /** The value. */ final T mValue; /** * The value of {@link SystemClock#elapsedRealtime} or equivalent when value was * determined. */ final long mElapsedRealtime; TimeStampedValue(T value, long elapsedRealtime) { this.mValue = value; this.mElapsedRealtime = elapsedRealtime; } @Override public String toString() { return "TimeStampedValue{" + "mValue=" + mValue + ", mElapsedRealtime=" + mElapsedRealtime + '}'; } } private static final String LOG_TAG = ServiceStateTracker.LOG_TAG; private static final boolean DBG = ServiceStateTracker.DBG; /** * 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. */ 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 }; // Time detection state. /** Loading Loading @@ -212,6 +157,7 @@ public class NitzStateMachine { private final GsmCdmaPhone mPhone; private final DeviceState mDeviceState; private final TimeServiceHelper mTimeServiceHelper; private final TimeZoneLookupHelper mTimeZoneLookupHelper; /** Wake lock used while setting time of day. */ private final PowerManager.WakeLock mWakeLock; private static final String WAKELOCK_TAG = "NitzStateMachine"; Loading @@ -219,12 +165,13 @@ public class NitzStateMachine { public NitzStateMachine(GsmCdmaPhone phone) { this(phone, TelephonyComponentFactory.getInstance().makeTimeServiceHelper(phone.getContext()), new DeviceState(phone)); new DeviceState(phone), new TimeZoneLookupHelper()); } @VisibleForTesting public NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper, DeviceState deviceState) { DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) { mPhone = phone; Context context = phone.getContext(); Loading @@ -233,6 +180,7 @@ public class NitzStateMachine { mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); mDeviceState = deviceState; mTimeZoneLookupHelper = timeZoneLookupHelper; mTimeServiceHelper = timeServiceHelper; mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() { @Override Loading Loading @@ -266,23 +214,22 @@ public class NitzStateMachine { + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized + " mNitzData=" + mNitzData + " iso-cc='" + isoCountryCode + "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode)); + "' countryUsesUtc=" + mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode)); } TimeZone zone; String zoneId; if ("".equals(isoCountryCode) && mNeedFixZoneAfterNitz) { // Country code not found. This is likely a test network. // Get a TimeZone based only on the NITZ parameters (best guess). // mNeedFixZoneAfterNitz is only set to true when mNitzData is set so there's no need to // check mNitzData == null. zone = NitzData.guessTimeZone(mNitzData); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(mNitzData); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned " + (zone == null ? zone : zone.getID())); Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned " + zoneId); } } else if ((mNitzData == null || nitzOffsetMightBeBogus(mNitzData)) && isTimeZoneSettingInitialized && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) { && !mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode)) { // This case means that (1) the device received no NITZ signal yet or received an NITZ // signal that looks bogus due to having a zero offset from UTC, (2) the device has a Loading @@ -290,7 +237,8 @@ public class NitzStateMachine { // 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 time // may be adjusted by assuming the current zone setting is correct. zone = TimeZone.getDefault(); TimeZone zone = TimeZone.getDefault(); zoneId = zone.getID(); // Note that mNeedFixZoneAfterNitz => (implies) { mNitzData != null }. Therefore, if // mNitzData == null, mNeedFixZoneAfterNitz cannot be true. The code in this section Loading Loading @@ -330,15 +278,14 @@ public class NitzStateMachine { } else if (mNitzData == null) { // The use of 1/1/1970 UTC is unusual but consistent with historical behavior when // it wasn't possible to detect whether a previous NITZ signal had been saved. zone = TimeUtils.getTimeZone(0 /* offset */, false /* dst */, 0 /* when */, isoCountryCode); zoneId = mTimeZoneLookupHelper.guessZoneIdByInstantOffsetDstCountry( 0 /* when */, 0 /* offset */, false /* dst */, isoCountryCode); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone: No cached NITZ data available, using only country" + " code. zone=" + zone); + " code. zone=" + zoneId); } } else { zone = TimeUtils.getTimeZone(mNitzData.getLocalOffsetMillis(), mNitzData.isDst(), mNitzData.getCurrentTimeInMillis(), isoCountryCode); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(mNitzData, isoCountryCode); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone: using getTimeZone(off, dst, time, iso)"); } Loading @@ -348,18 +295,18 @@ public class NitzStateMachine { + " mNitzData=" + mNitzData + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz + " zone=" + (zone != null ? zone.getID() : "NULL"); + " zoneId=" + zoneId; mTimeZoneLog.log(tmpLog); if (zone != null) { Rlog.d(LOG_TAG, "fixTimeZone: zone != null zone.getID=" + zone.getID()); if (zoneId != null) { Rlog.d(LOG_TAG, "fixTimeZone: zone != null zoneId=" + zoneId); if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) { setAndBroadcastNetworkSetTimeZone(zone.getID()); setAndBroadcastNetworkSetTimeZone(zoneId); } else { Rlog.d(LOG_TAG, "fixTimeZone: skip changing zone as getAutoTimeZone was false"); } if (mNeedFixZoneAfterNitz) { saveNitzTimeZone(zone.getID()); saveNitzTimeZone(zoneId); } } else { Rlog.d(LOG_TAG, "fixTimeZone: zone == null, do nothing for zone"); Loading Loading @@ -389,27 +336,22 @@ public class NitzStateMachine { private void setTimeZoneFromNitz(NitzData newNitzData, long nitzReceiveTime) { try { String iso = mDeviceState.getNetworkCountryIsoForPhone(); TimeZone zone; String zoneId; if (newNitzData.getEmulatorHostTimeZone() != null) { zone = newNitzData.getEmulatorHostTimeZone(); zoneId = newNitzData.getEmulatorHostTimeZone().getID(); } else { if (!mGotCountryCode) { zone = null; zoneId = null; } else if (iso != null && iso.length() > 0) { zone = TimeUtils.getTimeZone( newNitzData.getLocalOffsetMillis(), newNitzData.isDst(), newNitzData.getCurrentTimeInMillis(), iso); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(newNitzData, iso); } else { // We don't have a valid iso country code. 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. zone = NitzData.guessTimeZone(newNitzData); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(newNitzData); if (DBG) { Rlog.d(LOG_TAG, "setTimeFromNITZ(): guessNitzTimeZone returned " + (zone == null ? zone : zone.getID())); Rlog.d(LOG_TAG, "setTimeZoneFromNitz(): findByNitz returned " + zoneId); } } } Loading @@ -425,7 +367,7 @@ public class NitzStateMachine { previousUtcOffset = mNitzData.getLocalOffsetMillis(); previousIsDst = mNitzData.isDst(); } if ((zone == null) if ((zoneId == null) || (newNitzData.getLocalOffsetMillis() != previousUtcOffset) || (newNitzData.isDst() != previousIsDst)) { // We got the time before the country or the zone has changed Loading @@ -437,7 +379,7 @@ public class NitzStateMachine { String tmpLog = "NITZ: newNitzData=" + newNitzData + " nitzReceiveTime=" + nitzReceiveTime + " zone=" + (zone != null ? zone.getID() : "NULL") + " zoneId=" + zoneId + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz + " isTimeZoneDetectionEnabled()=" Loading @@ -447,12 +389,12 @@ public class NitzStateMachine { } mTimeZoneLog.log(tmpLog); if (zone != null) { if (zoneId != null) { if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) { setAndBroadcastNetworkSetTimeZone(zone.getID()); setAndBroadcastNetworkSetTimeZone(zoneId); } mNitzTimeZoneDetectionSuccessful = true; saveNitzTimeZone(zone.getID()); saveNitzTimeZone(zoneId); } } catch (RuntimeException ex) { Rlog.e(LOG_TAG, "NITZ: Processing NITZ data " + newNitzData + " ex=" + ex); Loading Loading @@ -642,27 +584,27 @@ public class NitzStateMachine { } /** * Update time zone by network country code, works on countries which only have one time zone. * Update time zone by network country code, works well on countries which only have one time * zone or multiple zones with the same offset. * * @param iso Country code from network MCC */ public void updateTimeZoneByNetworkCountryCode(String iso) { List<String> uniqueZoneIds = TimeUtils.getTimeZoneIdsWithUniqueOffsets(iso); if (uniqueZoneIds.size() == 1) { String zoneId = uniqueZoneIds.get(0); String zoneId = mTimeZoneLookupHelper.guessZoneIdByCountry( iso, mDeviceState.currentTimeMillis()); if (zoneId != null) { if (DBG) { Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: no nitz but one TZ for iso-cc=" + iso + " with zone.getID=" + zoneId); Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId + " iso=" + iso); } mTimeZoneLog.log("updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId + " iso=" + iso); setAndBroadcastNetworkSetTimeZone(zoneId); } else { if (DBG) { Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: there are " + uniqueZoneIds.size() + " unique offsets for iso-cc='" + iso "updateTimeZoneByNetworkCountryCode: no good zone for iso-cc='" + iso + "', do nothing"); } } Loading src/java/com/android/internal/telephony/ServiceStateTracker.java +1 −1 Original line number Diff line number Diff line Loading @@ -3092,7 +3092,7 @@ public class ServiceStateTracker extends Handler { if (lastNitzData == null) { tzone = null; } else { tzone = NitzData.guessTimeZone(lastNitzData); tzone = TimeZoneLookupHelper.guessZoneByNitzStatic(lastNitzData); if (ServiceStateTracker.DBG) { log("fixUnknownMcc(): guessNitzTimeZone returned " + (tzone == null ? tzone : tzone.getID())); Loading src/java/com/android/internal/telephony/TimeZoneLookupHelper.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.util.TimeUtils; import libcore.util.CountryTimeZones; import libcore.util.TimeZoneFinder; import java.util.Arrays; import java.util.Date; import java.util.TimeZone; /** * An interface to various time zone lookup behaviors. */ // 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. */ 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 TimeZoneLookupHelper() {} /** * 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. */ public String guessZoneIdByNitzCountry(NitzData nitzData, String isoCountryCode) { return guessZoneIdByInstantOffsetDstCountry( nitzData.getCurrentTimeInMillis(), nitzData.getLocalOffsetMillis(), nitzData.isDst(), isoCountryCode); } /** * Finds a time zone ID that fits the supplied time / offset 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. */ 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(); } /** * Finds a time zone ID 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 * 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. */ public String guessZoneIdByNitz(NitzData nitzData) { TimeZone zone = guessZoneByNitzStatic(nitzData); return zone == null ? null : zone.getID(); } /** * Returns a time zone ID for the country if possible. For counties that use a single time zone * this will provide a good choice. For countries with multiple time zones, a time zone is * returned if all time zones used in the country currently have the same offset (currently == * 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); if (countryTimeZones == null) { // Unknown country code. return null; } if (countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis)) { return countryTimeZones.getDefaultTimeZoneId(); } return null; } /** Static method for use by {@link ServiceStateTracker}. */ static TimeZone guessZoneByNitzStatic(NitzData nitzData) { int utcOffsetMillis = nitzData.getLocalOffsetMillis(); boolean isDst = nitzData.isDst(); long timeMillis = nitzData.getCurrentTimeInMillis(); TimeZone guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (guess == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); } return guess; } private static TimeZone guessByInstantOffsetDst(long timeMillis, int utcOffsetMillis, boolean isDst) { int rawOffset = utcOffsetMillis; if (isDst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; Date d = new Date(timeMillis); for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) { guess = tz; break; } } return guess; } /** * 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. */ public boolean countryUsesUtc(String isoCountryCode) { return Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) >= 0; } } src/java/com/android/internal/telephony/util/TimeStampedValue.java 0 → 100644 +73 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
src/java/com/android/internal/telephony/NitzData.java +4 −44 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; /** Loading @@ -35,7 +34,6 @@ import java.util.TimeZone; @VisibleForTesting(visibility = PACKAGE) public final class NitzData { private static final String LOG_TAG = ServiceStateTracker.LOG_TAG; private static final int MS_PER_HOUR = 60 * 60 * 1000; private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000; /* Time stamp after 19 January 2038 is not supported under 32 bit */ Loading @@ -53,7 +51,7 @@ public final class NitzData { private final TimeZone mEmulatorHostTimeZone; private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone timeZone) { long utcTimeMillis, TimeZone emulatorHostTimeZone) { if (originalString == null) { throw new NullPointerException("originalString==null"); } Loading @@ -61,7 +59,7 @@ public final class NitzData { this.mZoneOffset = zoneOffsetMillis; this.mDstOffset = dstOffsetMillis; this.mCurrentTimeMillis = utcTimeMillis; this.mEmulatorHostTimeZone = timeZone; this.mEmulatorHostTimeZone = emulatorHostTimeZone; } /** Loading Loading @@ -139,9 +137,9 @@ public final class NitzData { /** A method for use in tests to create NitzData instances. */ public static NitzData createForTests(int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone timeZone) { long utcTimeMillis, TimeZone emulatorHostTimeZone) { return new NitzData("Test data", zoneOffsetMillis, dstOffsetMillis, utcTimeMillis, timeZone); emulatorHostTimeZone); } /** Loading Loading @@ -187,44 +185,6 @@ public final class NitzData { return mEmulatorHostTimeZone; } /** * Using information present in the supplied {@link NitzData} object, guess the time zone. * 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 time zones are found. */ public static TimeZone guessTimeZone(NitzData nitzData) { int offset = nitzData.getLocalOffsetMillis(); boolean dst = nitzData.isDst(); long when = nitzData.getCurrentTimeInMillis(); TimeZone guess = findTimeZone(offset, dst, when); if (guess == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = findTimeZone(offset, !dst, when); } return guess; } private static TimeZone findTimeZone(int offset, boolean dst, long when) { int rawOffset = offset; if (dst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; Date d = new Date(when); for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(when) == offset && tz.inDaylightTime(d) == dst) { guess = tz; break; } } return guess; } @Override public boolean equals(Object o) { if (this == o) { Loading
src/java/com/android/internal/telephony/NitzStateMachine.java +42 −100 Original line number Diff line number Diff line Loading @@ -30,12 +30,11 @@ import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.util.TimeStampedValue; import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.TimeZone; /** Loading Loading @@ -117,63 +116,9 @@ public class NitzStateMachine { } } /** A pair containing a value and an associated time stamp. */ private static class TimeStampedValue<T> { /** The value. */ final T mValue; /** * The value of {@link SystemClock#elapsedRealtime} or equivalent when value was * determined. */ final long mElapsedRealtime; TimeStampedValue(T value, long elapsedRealtime) { this.mValue = value; this.mElapsedRealtime = elapsedRealtime; } @Override public String toString() { return "TimeStampedValue{" + "mValue=" + mValue + ", mElapsedRealtime=" + mElapsedRealtime + '}'; } } private static final String LOG_TAG = ServiceStateTracker.LOG_TAG; private static final boolean DBG = ServiceStateTracker.DBG; /** * 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. */ 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 }; // Time detection state. /** Loading Loading @@ -212,6 +157,7 @@ public class NitzStateMachine { private final GsmCdmaPhone mPhone; private final DeviceState mDeviceState; private final TimeServiceHelper mTimeServiceHelper; private final TimeZoneLookupHelper mTimeZoneLookupHelper; /** Wake lock used while setting time of day. */ private final PowerManager.WakeLock mWakeLock; private static final String WAKELOCK_TAG = "NitzStateMachine"; Loading @@ -219,12 +165,13 @@ public class NitzStateMachine { public NitzStateMachine(GsmCdmaPhone phone) { this(phone, TelephonyComponentFactory.getInstance().makeTimeServiceHelper(phone.getContext()), new DeviceState(phone)); new DeviceState(phone), new TimeZoneLookupHelper()); } @VisibleForTesting public NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper, DeviceState deviceState) { DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) { mPhone = phone; Context context = phone.getContext(); Loading @@ -233,6 +180,7 @@ public class NitzStateMachine { mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); mDeviceState = deviceState; mTimeZoneLookupHelper = timeZoneLookupHelper; mTimeServiceHelper = timeServiceHelper; mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() { @Override Loading Loading @@ -266,23 +214,22 @@ public class NitzStateMachine { + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized + " mNitzData=" + mNitzData + " iso-cc='" + isoCountryCode + "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode)); + "' countryUsesUtc=" + mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode)); } TimeZone zone; String zoneId; if ("".equals(isoCountryCode) && mNeedFixZoneAfterNitz) { // Country code not found. This is likely a test network. // Get a TimeZone based only on the NITZ parameters (best guess). // mNeedFixZoneAfterNitz is only set to true when mNitzData is set so there's no need to // check mNitzData == null. zone = NitzData.guessTimeZone(mNitzData); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(mNitzData); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned " + (zone == null ? zone : zone.getID())); Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned " + zoneId); } } else if ((mNitzData == null || nitzOffsetMightBeBogus(mNitzData)) && isTimeZoneSettingInitialized && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) { && !mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode)) { // This case means that (1) the device received no NITZ signal yet or received an NITZ // signal that looks bogus due to having a zero offset from UTC, (2) the device has a Loading @@ -290,7 +237,8 @@ public class NitzStateMachine { // 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 time // may be adjusted by assuming the current zone setting is correct. zone = TimeZone.getDefault(); TimeZone zone = TimeZone.getDefault(); zoneId = zone.getID(); // Note that mNeedFixZoneAfterNitz => (implies) { mNitzData != null }. Therefore, if // mNitzData == null, mNeedFixZoneAfterNitz cannot be true. The code in this section Loading Loading @@ -330,15 +278,14 @@ public class NitzStateMachine { } else if (mNitzData == null) { // The use of 1/1/1970 UTC is unusual but consistent with historical behavior when // it wasn't possible to detect whether a previous NITZ signal had been saved. zone = TimeUtils.getTimeZone(0 /* offset */, false /* dst */, 0 /* when */, isoCountryCode); zoneId = mTimeZoneLookupHelper.guessZoneIdByInstantOffsetDstCountry( 0 /* when */, 0 /* offset */, false /* dst */, isoCountryCode); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone: No cached NITZ data available, using only country" + " code. zone=" + zone); + " code. zone=" + zoneId); } } else { zone = TimeUtils.getTimeZone(mNitzData.getLocalOffsetMillis(), mNitzData.isDst(), mNitzData.getCurrentTimeInMillis(), isoCountryCode); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(mNitzData, isoCountryCode); if (DBG) { Rlog.d(LOG_TAG, "fixTimeZone: using getTimeZone(off, dst, time, iso)"); } Loading @@ -348,18 +295,18 @@ public class NitzStateMachine { + " mNitzData=" + mNitzData + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz + " zone=" + (zone != null ? zone.getID() : "NULL"); + " zoneId=" + zoneId; mTimeZoneLog.log(tmpLog); if (zone != null) { Rlog.d(LOG_TAG, "fixTimeZone: zone != null zone.getID=" + zone.getID()); if (zoneId != null) { Rlog.d(LOG_TAG, "fixTimeZone: zone != null zoneId=" + zoneId); if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) { setAndBroadcastNetworkSetTimeZone(zone.getID()); setAndBroadcastNetworkSetTimeZone(zoneId); } else { Rlog.d(LOG_TAG, "fixTimeZone: skip changing zone as getAutoTimeZone was false"); } if (mNeedFixZoneAfterNitz) { saveNitzTimeZone(zone.getID()); saveNitzTimeZone(zoneId); } } else { Rlog.d(LOG_TAG, "fixTimeZone: zone == null, do nothing for zone"); Loading Loading @@ -389,27 +336,22 @@ public class NitzStateMachine { private void setTimeZoneFromNitz(NitzData newNitzData, long nitzReceiveTime) { try { String iso = mDeviceState.getNetworkCountryIsoForPhone(); TimeZone zone; String zoneId; if (newNitzData.getEmulatorHostTimeZone() != null) { zone = newNitzData.getEmulatorHostTimeZone(); zoneId = newNitzData.getEmulatorHostTimeZone().getID(); } else { if (!mGotCountryCode) { zone = null; zoneId = null; } else if (iso != null && iso.length() > 0) { zone = TimeUtils.getTimeZone( newNitzData.getLocalOffsetMillis(), newNitzData.isDst(), newNitzData.getCurrentTimeInMillis(), iso); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(newNitzData, iso); } else { // We don't have a valid iso country code. 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. zone = NitzData.guessTimeZone(newNitzData); zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(newNitzData); if (DBG) { Rlog.d(LOG_TAG, "setTimeFromNITZ(): guessNitzTimeZone returned " + (zone == null ? zone : zone.getID())); Rlog.d(LOG_TAG, "setTimeZoneFromNitz(): findByNitz returned " + zoneId); } } } Loading @@ -425,7 +367,7 @@ public class NitzStateMachine { previousUtcOffset = mNitzData.getLocalOffsetMillis(); previousIsDst = mNitzData.isDst(); } if ((zone == null) if ((zoneId == null) || (newNitzData.getLocalOffsetMillis() != previousUtcOffset) || (newNitzData.isDst() != previousIsDst)) { // We got the time before the country or the zone has changed Loading @@ -437,7 +379,7 @@ public class NitzStateMachine { String tmpLog = "NITZ: newNitzData=" + newNitzData + " nitzReceiveTime=" + nitzReceiveTime + " zone=" + (zone != null ? zone.getID() : "NULL") + " zoneId=" + zoneId + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz + " isTimeZoneDetectionEnabled()=" Loading @@ -447,12 +389,12 @@ public class NitzStateMachine { } mTimeZoneLog.log(tmpLog); if (zone != null) { if (zoneId != null) { if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) { setAndBroadcastNetworkSetTimeZone(zone.getID()); setAndBroadcastNetworkSetTimeZone(zoneId); } mNitzTimeZoneDetectionSuccessful = true; saveNitzTimeZone(zone.getID()); saveNitzTimeZone(zoneId); } } catch (RuntimeException ex) { Rlog.e(LOG_TAG, "NITZ: Processing NITZ data " + newNitzData + " ex=" + ex); Loading Loading @@ -642,27 +584,27 @@ public class NitzStateMachine { } /** * Update time zone by network country code, works on countries which only have one time zone. * Update time zone by network country code, works well on countries which only have one time * zone or multiple zones with the same offset. * * @param iso Country code from network MCC */ public void updateTimeZoneByNetworkCountryCode(String iso) { List<String> uniqueZoneIds = TimeUtils.getTimeZoneIdsWithUniqueOffsets(iso); if (uniqueZoneIds.size() == 1) { String zoneId = uniqueZoneIds.get(0); String zoneId = mTimeZoneLookupHelper.guessZoneIdByCountry( iso, mDeviceState.currentTimeMillis()); if (zoneId != null) { if (DBG) { Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: no nitz but one TZ for iso-cc=" + iso + " with zone.getID=" + zoneId); Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId + " iso=" + iso); } mTimeZoneLog.log("updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId + " iso=" + iso); setAndBroadcastNetworkSetTimeZone(zoneId); } else { if (DBG) { Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: there are " + uniqueZoneIds.size() + " unique offsets for iso-cc='" + iso "updateTimeZoneByNetworkCountryCode: no good zone for iso-cc='" + iso + "', do nothing"); } } Loading
src/java/com/android/internal/telephony/ServiceStateTracker.java +1 −1 Original line number Diff line number Diff line Loading @@ -3092,7 +3092,7 @@ public class ServiceStateTracker extends Handler { if (lastNitzData == null) { tzone = null; } else { tzone = NitzData.guessTimeZone(lastNitzData); tzone = TimeZoneLookupHelper.guessZoneByNitzStatic(lastNitzData); if (ServiceStateTracker.DBG) { log("fixUnknownMcc(): guessNitzTimeZone returned " + (tzone == null ? tzone : tzone.getID())); Loading
src/java/com/android/internal/telephony/TimeZoneLookupHelper.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.util.TimeUtils; import libcore.util.CountryTimeZones; import libcore.util.TimeZoneFinder; import java.util.Arrays; import java.util.Date; import java.util.TimeZone; /** * An interface to various time zone lookup behaviors. */ // 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. */ 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 TimeZoneLookupHelper() {} /** * 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. */ public String guessZoneIdByNitzCountry(NitzData nitzData, String isoCountryCode) { return guessZoneIdByInstantOffsetDstCountry( nitzData.getCurrentTimeInMillis(), nitzData.getLocalOffsetMillis(), nitzData.isDst(), isoCountryCode); } /** * Finds a time zone ID that fits the supplied time / offset 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. */ 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(); } /** * Finds a time zone ID 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 * 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. */ public String guessZoneIdByNitz(NitzData nitzData) { TimeZone zone = guessZoneByNitzStatic(nitzData); return zone == null ? null : zone.getID(); } /** * Returns a time zone ID for the country if possible. For counties that use a single time zone * this will provide a good choice. For countries with multiple time zones, a time zone is * returned if all time zones used in the country currently have the same offset (currently == * 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); if (countryTimeZones == null) { // Unknown country code. return null; } if (countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis)) { return countryTimeZones.getDefaultTimeZoneId(); } return null; } /** Static method for use by {@link ServiceStateTracker}. */ static TimeZone guessZoneByNitzStatic(NitzData nitzData) { int utcOffsetMillis = nitzData.getLocalOffsetMillis(); boolean isDst = nitzData.isDst(); long timeMillis = nitzData.getCurrentTimeInMillis(); TimeZone guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst); if (guess == null) { // Couldn't find a proper timezone. Perhaps the DST data is wrong. guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst); } return guess; } private static TimeZone guessByInstantOffsetDst(long timeMillis, int utcOffsetMillis, boolean isDst) { int rawOffset = utcOffsetMillis; if (isDst) { rawOffset -= MS_PER_HOUR; } String[] zones = TimeZone.getAvailableIDs(rawOffset); TimeZone guess = null; Date d = new Date(timeMillis); for (String zone : zones) { TimeZone tz = TimeZone.getTimeZone(zone); if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) { guess = tz; break; } } return guess; } /** * 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. */ public boolean countryUsesUtc(String isoCountryCode) { return Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) >= 0; } }
src/java/com/android/internal/telephony/util/TimeStampedValue.java 0 → 100644 +73 −0 File added.Preview size limit exceeded, changes collapsed. Show changes