Merge "[maguro] Add Cheers' MCCMNC in SIMRecords.java"
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 93299eb..2883eca 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -25,6 +25,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.TimeZone;
 import java.util.Date;
 
@@ -35,18 +37,26 @@
  */
 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();
@@ -58,6 +68,107 @@
         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");
 
@@ -73,43 +184,33 @@
 
                 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;
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 5afc1f3..cf96ab2 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -2566,13 +2566,20 @@
                 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;
 
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index abb4523..f95e081 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -182,4 +182,9 @@
      * 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";
 }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index a4fb1d8..148b139 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -59,9 +59,12 @@
 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;
 
 /**
@@ -112,6 +115,9 @@
     private boolean mGotCountryCode = false;
     private ContentResolver cr;
 
+    /** Boolean is true is setTimeFromNITZString was called */
+    private boolean mNitzUpdatedTime = false;
+
     String mSavedTimeZone;
     long mSavedTime;
     long mSavedAtTime;
@@ -698,6 +704,7 @@
                 newCellLoc.setStateInvalid();
                 setSignalStrengthDefaultValues();
                 mGotCountryCode = false;
+                mNitzUpdatedTime = false;
                 pollStateDone();
             break;
 
@@ -706,6 +713,7 @@
                 newCellLoc.setStateInvalid();
                 setSignalStrengthDefaultValues();
                 mGotCountryCode = false;
+                mNitzUpdatedTime = false;
                 pollStateDone();
             break;
 
@@ -826,6 +834,12 @@
 
         if (hasRegistered) {
             mNetworkAttachedRegistrants.notifyRegistrants();
+
+            if (DBG) {
+                log("pollStateDone: registering current mNitzUpdatedTime=" +
+                        mNitzUpdatedTime + " changing to false");
+            }
+            mNitzUpdatedTime = false;
         }
 
         if (hasChanged) {
@@ -840,28 +854,71 @@
             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)) {
@@ -876,22 +933,36 @@
                             // 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));
                     }
                 }
             }
@@ -1440,6 +1511,7 @@
                     long end = SystemClock.elapsedRealtime();
                     log("NITZ: end=" + end + " dur=" + (end - start));
                 }
+                mNitzUpdatedTime = true;
             } finally {
                 mWakeLock.release();
             }
@@ -1489,6 +1561,10 @@
         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);
+        }
     }
 
     /**