import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.TimeZone;
import java.util.Date;
*/
public class TimeUtils {
/** @hide */ public TimeUtils() {}
+ private static final boolean DBG = false;
private static final String TAG = "TimeUtils";
+ /** Cached results of getTineZones */
+ private static final Object sLastLockObj = new Object();
+ private static ArrayList<TimeZone> sLastZones = null;
+ private static String sLastCountry = null;
+
+ /** Cached results of getTimeZonesWithUniqueOffsets */
+ private static final Object sLastUniqueLockObj = new Object();
+ private static ArrayList<TimeZone> sLastUniqueZoneOffsets = null;
+ private static String sLastUniqueCountry = null;
+
+
/**
* Tries to return a time zone that would have had the specified offset
* and DST value at the specified moment in the specified country.
* Returns null if no suitable zone could be found.
*/
public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
- if (country == null) {
- return null;
- }
-
TimeZone best = null;
Resources r = Resources.getSystem();
int currentOffset = current.getOffset(when);
boolean currentDst = current.inDaylightTime(d);
+ for (TimeZone tz : getTimeZones(country)) {
+ // If the current time zone is from the right country
+ // and meets the other known properties, keep it
+ // instead of changing to another one.
+
+ if (tz.getID().equals(currentName)) {
+ if (currentOffset == offset && currentDst == dst) {
+ return current;
+ }
+ }
+
+ // Otherwise, take the first zone from the right
+ // country that has the correct current offset and DST.
+ // (Keep iterating instead of returning in case we
+ // haven't encountered the current time zone yet.)
+
+ if (best == null) {
+ if (tz.getOffset(when) == offset &&
+ tz.inDaylightTime(d) == dst) {
+ best = tz;
+ }
+ }
+ }
+
+ return best;
+ }
+
+ /**
+ * Return list of unique time zones for the country. Do not modify
+ *
+ * @param country to find
+ * @return list of unique time zones, maybe empty but never null. Do not modify.
+ * @hide
+ */
+ public static ArrayList<TimeZone> getTimeZonesWithUniqueOffsets(String country) {
+ synchronized(sLastUniqueLockObj) {
+ if ((country != null) && country.equals(sLastUniqueCountry)) {
+ if (DBG) {
+ Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
+ country + "): return cached version");
+ }
+ return sLastUniqueZoneOffsets;
+ }
+ }
+
+ Collection<TimeZone> zones = getTimeZones(country);
+ ArrayList<TimeZone> uniqueTimeZones = new ArrayList<TimeZone>();
+ for (TimeZone zone : zones) {
+ // See if we already have this offset,
+ // Using slow but space efficient and these are small.
+ boolean found = false;
+ for (int i = 0; i < uniqueTimeZones.size(); i++) {
+ if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
+ found = true;
+ break;
+ }
+ }
+ if (found == false) {
+ if (DBG) {
+ Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
+ zone.getRawOffset() + " zone.getID=" + zone.getID());
+ }
+ uniqueTimeZones.add(zone);
+ }
+ }
+
+ synchronized(sLastUniqueLockObj) {
+ // Cache the last result
+ sLastUniqueZoneOffsets = uniqueTimeZones;
+ sLastUniqueCountry = country;
+
+ return sLastUniqueZoneOffsets;
+ }
+ }
+
+ /**
+ * Returns the time zones for the country, which is the code
+ * attribute of the timezone element in time_zones_by_country.xml. Do not modify.
+ *
+ * @param country is a two character country code.
+ * @return TimeZone list, maybe empty but never null. Do not modify.
+ * @hide
+ */
+ public static ArrayList<TimeZone> getTimeZones(String country) {
+ synchronized (sLastLockObj) {
+ if ((country != null) && country.equals(sLastCountry)) {
+ if (DBG) Log.d(TAG, "getTimeZones(" + country + "): return cached version");
+ return sLastZones;
+ }
+ }
+
+ ArrayList<TimeZone> tzs = new ArrayList<TimeZone>();
+
+ if (country == null) {
+ if (DBG) Log.d(TAG, "getTimeZones(null): return empty list");
+ return tzs;
+ }
+
+ Resources r = Resources.getSystem();
+ XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
+
try {
XmlUtils.beginDocument(parser, "timezones");
if (country.equals(code)) {
if (parser.next() == XmlPullParser.TEXT) {
- String maybe = parser.getText();
-
- // If the current time zone is from the right country
- // and meets the other known properties, keep it
- // instead of changing to another one.
-
- if (maybe.equals(currentName)) {
- if (currentOffset == offset && currentDst == dst) {
- return current;
- }
- }
-
- // Otherwise, take the first zone from the right
- // country that has the correct current offset and DST.
- // (Keep iterating instead of returning in case we
- // haven't encountered the current time zone yet.)
-
- if (best == null) {
- TimeZone tz = TimeZone.getTimeZone(maybe);
-
- if (tz.getOffset(when) == offset &&
- tz.inDaylightTime(d) == dst) {
- best = tz;
+ String zoneIdString = parser.getText();
+ TimeZone tz = TimeZone.getTimeZone(zoneIdString);
+ if (tz.getID().startsWith("GMT") == false) {
+ // tz.getID doesn't start not "GMT" so its valid
+ tzs.add(tz);
+ if (DBG) {
+ Log.d(TAG, "getTimeZone('" + country + "'): found tz.getID=="
+ + ((tz != null) ? tz.getID() : "<no tz>"));
}
}
}
}
}
} catch (XmlPullParserException e) {
- Log.e(TAG, "Got exception while getting preferred time zone.", e);
+ Log.e(TAG, "Got xml parser exception getTimeZone('" + country + "'): e=", e);
} catch (IOException e) {
- Log.e(TAG, "Got exception while getting preferred time zone.", e);
+ Log.e(TAG, "Got IO exception getTimeZone('" + country + "'): e=", e);
} finally {
parser.close();
}
- return best;
+ synchronized(sLastLockObj) {
+ // Cache the last result;
+ sLastZones = tzs;
+ sLastCountry = country;
+ return sLastZones;
+ }
}
/**
result[0] = ret;
result[1] = Long.valueOf(nitzReceiveTime);
- if (mNITZTimeRegistrant != null) {
+ boolean ignoreNitz = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_IGNORE_NITZ, false);
- mNITZTimeRegistrant
- .notifyRegistrant(new AsyncResult (null, result, null));
+ if (ignoreNitz) {
+ if (RILJ_LOGD) riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
} else {
- // in case NITZ time registrant isnt registered yet
- mLastNITZTimeInfo = result;
+ if (mNITZTimeRegistrant != null) {
+
+ mNITZTimeRegistrant
+ .notifyRegistrant(new AsyncResult (null, result, null));
+ } else {
+ // in case NITZ time registrant isnt registered yet
+ mLastNITZTimeInfo = result;
+ }
}
break;
* in commercial configuration.
*/
static final String PROPERTY_TEST_CSIM = "persist.radio.test-csim";
+
+ /**
+ * Ignore RIL_UNSOL_NITZ_TIME_RECEIVED completely, used for debugging/testing.
+ */
+ static final String PROPERTY_IGNORE_NITZ = "telephony.test.ignore.nitz";
}
import android.util.Log;
import android.util.TimeUtils;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Date;
+import java.util.HashSet;
import java.util.TimeZone;
/**
private boolean mGotCountryCode = false;
private ContentResolver cr;
+ /** Boolean is true is setTimeFromNITZString was called */
+ private boolean mNitzUpdatedTime = false;
+
String mSavedTimeZone;
long mSavedTime;
long mSavedAtTime;
newCellLoc.setStateInvalid();
setSignalStrengthDefaultValues();
mGotCountryCode = false;
+ mNitzUpdatedTime = false;
pollStateDone();
break;
newCellLoc.setStateInvalid();
setSignalStrengthDefaultValues();
mGotCountryCode = false;
+ mNitzUpdatedTime = false;
pollStateDone();
break;
if (hasRegistered) {
mNetworkAttachedRegistrants.notifyRegistrants();
+
+ if (DBG) {
+ log("pollStateDone: registering current mNitzUpdatedTime=" +
+ mNitzUpdatedTime + " changing to false");
+ }
+ mNitzUpdatedTime = false;
}
if (hasChanged) {
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
if (operatorNumeric == null) {
+ if (DBG) {
+ log("pollStateDone: operatorNumeric is null:" +
+ " clear PROPERTY_OPERATOR_ISO_COUNTRY");
+ }
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
mGotCountryCode = false;
+ mNitzUpdatedTime = false;
} else {
String iso = "";
+ String mcc = operatorNumeric.substring(0, 3);
try{
- iso = MccTable.countryCodeForMcc(Integer.parseInt(
- operatorNumeric.substring(0,3)));
+ iso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
} catch ( NumberFormatException ex){
- loge("countryCodeForMcc error" + ex);
+ loge("pollStateDone: countryCodeForMcc error" + ex);
} catch ( StringIndexOutOfBoundsException ex) {
- loge("countryCodeForMcc error" + ex);
+ loge("pollStateDone: countryCodeForMcc error" + ex);
+ }
+ if (DBG) {
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " mcc=" + mcc + " iso=" + iso);
}
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso);
mGotCountryCode = true;
+ TimeZone zone = null;
+
+ if (!mNitzUpdatedTime && !mcc.equals("000") && !TextUtils.isEmpty(iso) &&
+ getAutoTimeZone()) {
+
+ // Test both paths if ignore nitz is true
+ boolean testOneUniqueOffsetPath = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_IGNORE_NITZ, false) &&
+ ((SystemClock.uptimeMillis() & 1) == 0);
+
+ ArrayList<TimeZone> uniqueZones = TimeUtils.getTimeZonesWithUniqueOffsets(iso);
+ if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) {
+ zone = uniqueZones.get(0);
+ if (DBG) {
+ log("pollStateDone: no nitz but one TZ for iso=" + iso +
+ " with zone.getID=" + zone.getID() +
+ " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath);
+ }
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ } else {
+ if (DBG) {
+ log("pollStateDone: there are " + uniqueZones.size() +
+ " unique offsets for iso='" + iso +
+ " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath +
+ "', do nothing");
+ }
+ }
+ }
+
if (mNeedFixZone) {
- TimeZone zone = null;
// If the offset is (0, false) and the timezone property
// is set, use the timezone property rather than
// GMT.
String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+ if (DBG) {
+ log("pollStateDone: mNeedFixZone==true zoneName='" + zoneName +
+ "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+ " iso='" + iso +
+ "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
+ }
if ((mZoneOffset == 0) && (mZoneDst == false) &&
(zoneName != null) && (zoneName.length() > 0) &&
(Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
// Adjust the saved NITZ time to account for tzOffset.
mSavedTime = mSavedTime - tzOffset;
}
+ if (DBG) log("pollStateDone: using default TimeZone");
} else if (iso.equals("")){
// Country code not found. This is likely a test network.
// Get a TimeZone based only on the NITZ parameters (best guess).
zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+ if (DBG) log("pollStateDone: using NITZ TimeZone");
} else {
- zone = TimeUtils.getTimeZone(mZoneOffset,
- mZoneDst, mZoneTime, iso);
+ zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, iso);
+ if (DBG) log("pollStateDone: using getTimeZone(off, dst, time, iso)");
}
mNeedFixZone = false;
if (zone != null) {
+ log("pollStateDone: zone != null zone.getID=" + zone.getID());
if (getAutoTimeZone()) {
setAndBroadcastNetworkSetTimeZone(zone.getID());
}
saveNitzTimeZone(zone.getID());
+ } else {
+ log("pollStateDone: zone == null");
+ }
+ } else {
+ if (DBG) {
+ String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+ zone = TimeZone.getDefault();
+ log("pollStateDone: mNeedFixZone==false zoneName='" + zoneName +
+ "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+ " iso='" + iso +
+ "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
}
}
}
long end = SystemClock.elapsedRealtime();
log("NITZ: end=" + end + " dur=" + (end - start));
}
+ mNitzUpdatedTime = true;
} finally {
mWakeLock.release();
}
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time-zone", zoneId);
phone.getContext().sendStickyBroadcast(intent);
+ if (DBG) {
+ log("setAndBroadcastNetworkSetTimeZone: call alarm.setTimeZone and broadcast zoneId=" +
+ zoneId);
+ }
}
/**