Supporting Plus code dialing for non-NANP CDMA carrier

Plus Code Dialing in CDMA need to know the current IDD, which is
associated with the current operator MCC, but most of Network won't
broadcast the current operator MCC via OTA, we need to use the other
OTA parameters, such as SID, time zone etc. to find right MCC, and
get right IDD further to be used by Plus Code Dialing.

Change-Id: I369083169bb3504f8de9774b65f3993dd561c525

Conflicts:
	src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
diff --git a/src/java/com/android/internal/telephony/HbpcdLookup.java b/src/java/com/android/internal/telephony/HbpcdLookup.java
new file mode 100644
index 0000000..d9a3e72
--- /dev/null
+++ b/src/java/com/android/internal/telephony/HbpcdLookup.java
@@ -0,0 +1,124 @@
+/*
+**
+** Copyright 2014, 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.net.Uri;
+import android.provider.BaseColumns;
+
+/**
+ * @hide
+ */
+public class HbpcdLookup {
+    public static final String AUTHORITY = "hbpcd_lookup";
+
+    public static final Uri CONTENT_URI =
+        Uri.parse("content://" + AUTHORITY);
+
+    public static final String PATH_MCC_IDD = "idd";
+    public static final String PATH_MCC_LOOKUP_TABLE = "lookup";
+    public static final String PATH_MCC_SID_CONFLICT = "conflict";
+    public static final String PATH_MCC_SID_RANGE = "range";
+    public static final String PATH_NANP_AREA_CODE = "nanp";
+    public static final String PATH_ARBITRARY_MCC_SID_MATCH = "arbitrary";
+    public static final String PATH_USERADD_COUNTRY = "useradd";
+
+    public static final String ID = "_id";
+    public static final int IDINDEX = 0;
+
+    /**
+     * @hide
+     */
+    public static class MccIdd implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_IDD);
+        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
+
+        public static final String MCC = "MCC";
+        public static final String IDD = "IDD";
+
+    }
+
+    /**
+     * @hide
+     */
+    public static class MccLookup implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_LOOKUP_TABLE);
+        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
+
+        public static final String MCC = "MCC";
+        public static final String COUNTRY_CODE = "Country_Code";
+        public static final String COUNTRY_NAME = "Country_Name";
+        public static final String NDD = "NDD";
+        public static final String NANPS = "NANPS";
+        public static final String GMT_OFFSET_LOW = "GMT_Offset_Low";
+        public static final String GMT_OFFSET_HIGH = "GMT_Offset_High";
+        public static final String GMT_DST_LOW = "GMT_DST_Low";
+        public static final String GMT_DST_HIGH = "GMT_DST_High";
+
+    }
+
+    /**
+     * @hide
+     */
+    public static class MccSidConflicts implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_CONFLICT);
+        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
+
+        public static final String MCC = "MCC";
+        public static final String SID_CONFLICT = "SID_Conflict";
+
+    }
+
+    /**
+     * @hide
+     */
+    public static class MccSidRange implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_RANGE);
+        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
+
+        public static final String MCC = "MCC";
+        public static final String RANGE_LOW = "SID_Range_Low";
+        public static final String RANGE_HIGH = "SID_Range_High";
+    }
+
+    /**
+     * @hide
+     */
+    public static class ArbitraryMccSidMatch implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_ARBITRARY_MCC_SID_MATCH);
+        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
+
+        public static final String MCC = "MCC";
+        public static final String SID = "SID";
+
+    }
+
+    /**
+     * @hide
+     */
+    public static class NanpAreaCode implements BaseColumns {
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/" + PATH_NANP_AREA_CODE);
+        public static final String DEFAULT_SORT_ORDER = "Area_Code ASC";
+
+        public static final String AREA_CODE = "Area_Code";
+    }
+}
diff --git a/src/java/com/android/internal/telephony/HbpcdUtils.java b/src/java/com/android/internal/telephony/HbpcdUtils.java
new file mode 100755
index 0000000..acd31a6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/HbpcdUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2014 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.Log;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.database.Cursor;
+
+import com.android.internal.telephony.HbpcdLookup;
+import com.android.internal.telephony.HbpcdLookup.MccIdd;
+import com.android.internal.telephony.HbpcdLookup.MccLookup;
+import com.android.internal.telephony.HbpcdLookup.MccSidConflicts;
+import com.android.internal.telephony.HbpcdLookup.MccSidRange;
+import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
+
+public final class HbpcdUtils {
+    private static final String LOG_TAG = "HbpcdUtils";
+    private static final boolean DBG = false;
+    private ContentResolver resolver = null;
+
+    public HbpcdUtils(Context context) {
+        resolver = context.getContentResolver();
+    }
+
+    /**
+     *  Resolves the unknown MCC with SID and Timezone information.
+    */
+    public int getMcc(int sid, int tz, int DSTflag, boolean isNitzTimeZone) {
+        int tmpMcc = 0;
+
+        // check if SID exists in arbitrary_mcc_sid_match table.
+        // these SIDs are assigned to more than 1 operators, but they are known to
+        // be used by a specific operator, other operators having the same SID are
+        // not using it currently, if that SID is in this table, we don't need to
+        // check other tables.
+        String projection2[] = {ArbitraryMccSidMatch.MCC};
+        Cursor c2 = resolver.query(ArbitraryMccSidMatch.CONTENT_URI, projection2,
+                            ArbitraryMccSidMatch.SID + "=" + sid, null, null);
+
+        if (c2 != null) {
+            int c2Counter = c2.getCount();
+            if (DBG) {
+                Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+            }
+            if (c2Counter == 1) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2 );
+                }
+                c2.moveToFirst();
+                tmpMcc = c2.getInt(0);
+                if (DBG) {
+                    Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+                }
+                c2.close();
+                return tmpMcc;
+            }
+            c2.close();
+        }
+
+        // Then check if SID exists in mcc_sid_conflict table.
+        // and use the timezone in mcc_lookup table to check which MCC matches.
+        String projection3[] = {MccSidConflicts.MCC};
+        Cursor c3 = resolver.query(MccSidConflicts.CONTENT_URI, projection3,
+                MccSidConflicts.SID_CONFLICT + "=" + sid + " and (((" +
+                MccLookup.GMT_OFFSET_LOW + "<=" + tz + ") and (" + tz + "<=" +
+                MccLookup.GMT_OFFSET_HIGH + ") and (" + "0=" + DSTflag + ")) or ((" +
+                MccLookup.GMT_DST_LOW + "<=" + tz + ") and (" + tz + "<=" +
+                MccLookup.GMT_DST_HIGH + ") and (" + "1=" + DSTflag + ")))",
+                        null, null);
+        if (c3 != null) {
+            int c3Counter = c3.getCount();
+            if (c3Counter > 0) {
+                if (c3Counter > 1) {
+                    Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+                }
+                if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3 );
+                c3.moveToFirst();
+                tmpMcc = c3.getInt(0);
+                if (DBG) Log.d(LOG_TAG,
+                        "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+                c3.close();
+                if (isNitzTimeZone) {
+                    return tmpMcc;
+                } else {
+                    // time zone is not accurate, it may get wrong mcc, ignore it.
+                    if (DBG) Log.d(LOG_TAG, "time zone is not accurate, mcc may be "
+                            + tmpMcc);
+                        return 0;
+                }
+            }
+        }
+
+        // if there is no conflict, then check if SID is in mcc_sid_range.
+        String projection5[] = {MccSidRange.MCC};
+        Cursor c5 = resolver.query(MccSidRange.CONTENT_URI, projection5,
+                MccSidRange.RANGE_LOW + "<=" + sid + " and " +
+                MccSidRange.RANGE_HIGH + ">=" + sid,
+                null, null);
+        if (c5 != null) {
+            if (c5.getCount() > 0) {
+                if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5 );
+                c5.moveToFirst();
+                tmpMcc = c5.getInt(0);
+                if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+                c5.close();
+                return tmpMcc;
+            }
+            c5.close();
+        }
+        if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+
+        if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc );
+        // If unknown MCC still could not be resolved,
+        return tmpMcc;
+    }
+
+    /**
+     *  Gets country information with given MCC.
+    */
+    public String getIddByMcc(int mcc) {
+        if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+        String idd = "";
+
+        Cursor c = null;
+
+        String projection[] = {MccIdd.IDD};
+        Cursor cur = resolver.query(MccIdd.CONTENT_URI, projection,
+                MccIdd.MCC + "=" + mcc, null, null);
+        if (cur != null) {
+            if (cur.getCount() > 0) {
+                if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur );
+                // TODO: for those country having more than 1 IDDs, need more information
+                // to decide which IDD would be used. currently just use the first 1.
+                cur.moveToFirst();
+                idd = cur.getString(0);
+                if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
+
+            }
+            cur.close();
+        }
+        if (c != null) c.close();
+
+        if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+        return idd;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 9b151e7..3f8a1d1 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -436,11 +436,16 @@
             String prevOperatorNumeric =
                     SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
             operatorNumeric = mSS.getOperatorNumeric();
+            // try to fix the invalid Operator Numeric
+            if (isInvalidOperatorNumeric(operatorNumeric)) {
+                int sid = mSS.getSystemId();
+                operatorNumeric = fixUnknownMcc(operatorNumeric, sid);
+            }
             mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
             updateCarrierMccMncConfiguration(operatorNumeric,
                     prevOperatorNumeric, mPhone.getContext());
 
-            if (operatorNumeric == null) {
+            if (isInvalidOperatorNumeric(operatorNumeric)) {
                 if (DBG) log("operatorNumeric is null");
                 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
                 mGotCountryCode = false;
@@ -460,6 +465,8 @@
                         isoCountryCode);
                 mGotCountryCode = true;
 
+                setOperatorIdd(operatorNumeric);
+
                 if (shouldFixTimeZoneNow(mPhone, operatorNumeric, prevOperatorNumeric,
                         mNeedFixZone)) {
                     fixTimeZone(isoCountryCode);
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index d6a8456..3f198bc 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -59,6 +59,7 @@
 import com.android.internal.telephony.dataconnection.DcTrackerBase;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.HbpcdUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -82,6 +83,8 @@
     private static final String UNACTIVATED_MIN2_VALUE = "000000";
     private static final String UNACTIVATED_MIN_VALUE = "1111110111";
 
+    private static final int MS_PER_HOUR = 60 * 60 * 1000;
+
     // Current Otasp value
     int mCurrentOtaspMode = OTASP_UNINITIALIZED;
 
@@ -138,6 +141,11 @@
     protected boolean mIsSubscriptionFromRuim = false;
     private CdmaSubscriptionSourceManager mCdmaSSM;
 
+    protected static final String INVALID_MCC = "000";
+    protected static final String DEFAULT_MNC = "00";
+
+    protected HbpcdUtils mHbpcdUtils = null;
+
     /* Used only for debugging purposes. */
     private String mRegistrationDeniedReason;
 
@@ -201,6 +209,8 @@
             Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
             mAutoTimeZoneObserver);
         setSignalStrengthDefaultValues();
+
+        mHbpcdUtils = new HbpcdUtils(phone.getContext());
     }
 
     @Override
@@ -1092,11 +1102,19 @@
             String prevOperatorNumeric =
                     SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
             operatorNumeric = mSS.getOperatorNumeric();
+
+            // try to fix the invalid Operator Numeric
+            if (isInvalidOperatorNumeric(operatorNumeric)) {
+                int sid = mSS.getSystemId();
+                operatorNumeric = fixUnknownMcc(operatorNumeric, sid);
+            }
+
             mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
             updateCarrierMccMncConfiguration(operatorNumeric,
                     prevOperatorNumeric, mPhone.getContext());
-            if (operatorNumeric == null) {
-                if (DBG) log("operatorNumeric is null");
+
+            if (isInvalidOperatorNumeric(operatorNumeric)) {
+                if (DBG) log("operatorNumeric "+ operatorNumeric +"is invalid");
                 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
                 mGotCountryCode = false;
             } else {
@@ -1115,6 +1133,8 @@
                         isoCountryCode);
                 mGotCountryCode = true;
 
+                setOperatorIdd(operatorNumeric);
+
                 if (shouldFixTimeZoneNow(mPhone, operatorNumeric, prevOperatorNumeric,
                         mNeedFixZone)) {
                     fixTimeZone(isoCountryCode);
@@ -1155,6 +1175,56 @@
         // TODO: Add CdmaCellIdenity updating, see CdmaLteServiceStateTracker.
     }
 
+    protected boolean isInvalidOperatorNumeric(String operatorNumeric) {
+        return operatorNumeric == null || operatorNumeric.length() < 5 ||
+                    operatorNumeric.startsWith(INVALID_MCC);
+    }
+
+    protected String fixUnknownMcc(String operatorNumeric, int sid) {
+        if (sid <= 0) {
+            // no cdma information is available, do nothing
+            return operatorNumeric;
+        }
+
+        // resolve the mcc from sid;
+        // if mSavedTimeZone is null, TimeZone would get the default timeZone,
+        // and the fixTimeZone couldn't help, because it depends on operator Numeric;
+        // if the sid is conflict and timezone is unavailable, the mcc may be not right.
+        boolean isNitzTimeZone = false;
+        int timeZone = 0;
+        TimeZone tzone = null;
+        if (mSavedTimeZone != null) {
+             timeZone =
+                     TimeZone.getTimeZone(mSavedTimeZone).getRawOffset()/MS_PER_HOUR;
+             isNitzTimeZone = true;
+        } else {
+             tzone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+             if (tzone != null)
+                     timeZone = tzone.getRawOffset()/MS_PER_HOUR;
+        }
+
+        int mcc = mHbpcdUtils.getMcc(sid,
+                timeZone, (mZoneDst ? 1 : 0), isNitzTimeZone);
+        if (mcc > 0) {
+            operatorNumeric = Integer.toString(mcc) + DEFAULT_MNC;
+        }
+        return operatorNumeric;
+    }
+
+    protected void setOperatorIdd(String operatorNumeric) {
+        // Retrieve the current country information
+        // with the MCC got from opeatorNumeric.
+        String idd = mHbpcdUtils.getIddByMcc(
+                Integer.parseInt(operatorNumeric.substring(0,3)));
+        if (idd != null && !idd.isEmpty()) {
+            mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING,
+                     idd);
+        } else {
+            // use default "+", since we don't know the current IDP
+            mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING, "+");
+        }
+    }
+
     /**
      * Returns a TimeZone object based only on parameters from the NITZ string.
      */
@@ -1171,7 +1241,7 @@
     private TimeZone findTimeZone(int offset, boolean dst, long when) {
         int rawOffset = offset;
         if (dst) {
-            rawOffset -= 3600000;
+            rawOffset -= MS_PER_HOUR;
         }
         String[] zones = TimeZone.getAvailableIDs(rawOffset);
         TimeZone guess = null;