Adding the ability for carrier app to override SPN and carrier name.

The change allows system apps (holding MODIFY_PHONE_STATE permission) or
carrier apps to override the "carrier branding" on a per ICCID basis. The
override affects the service provider name as well as the network operator
name. The override is also saved as a SharedPreference and will persist for
the iccId across reboots.

Change-Id: I985ba247e10e2501e3d0d21567ccadc46f365879
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index ce9d235..6a5a82d 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -1807,4 +1807,9 @@
      * otherwise return the current voice service state
      */
     int getVoicePhoneServiceState();
+
+    /**
+     * Override the service provider name and the operator name for the input ICCID.
+     */
+    public boolean setOperatorBrandOverride(String iccId, String brand);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneBase.java b/src/java/com/android/internal/telephony/PhoneBase.java
index 15b5a77..642b770 100644
--- a/src/java/com/android/internal/telephony/PhoneBase.java
+++ b/src/java/com/android/internal/telephony/PhoneBase.java
@@ -1816,4 +1816,9 @@
         }
         return getServiceState().getState();
     }
+
+    @Override
+    public boolean setOperatorBrandOverride(String iccId, String brand) {
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneProxy.java b/src/java/com/android/internal/telephony/PhoneProxy.java
index 05b28b1..e043bf9 100644
--- a/src/java/com/android/internal/telephony/PhoneProxy.java
+++ b/src/java/com/android/internal/telephony/PhoneProxy.java
@@ -1359,4 +1359,9 @@
     public int getVoicePhoneServiceState() {
         return mActivePhone.getVoicePhoneServiceState();
     }
+
+    @Override
+    public boolean setOperatorBrandOverride(String iccId, String brand) {
+        return mActivePhone.setOperatorBrandOverride(iccId, brand);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 8462427..7164869 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -467,6 +467,7 @@
     public abstract boolean isConcurrentVoiceAndDataAllowed();
 
     public abstract void setImsRegistrationState(boolean registered);
+    public abstract void pollState();
 
     /**
      * Registration point for transition into DataConnection attached.
diff --git a/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index dd07281..374f814 100644
--- a/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -354,17 +354,21 @@
                 new Integer(PhoneConstants.PHONE_TYPE_CDMA).toString());
         // Sets operator alpha property by retrieving from build-time system property
         String operatorAlpha = SystemProperties.get("ro.cdma.home.operator.alpha");
-        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, operatorAlpha);
+        if (!TextUtils.isEmpty(operatorAlpha)) {
+            setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, operatorAlpha);
+        }
 
         // Sets operator numeric property by retrieving from build-time system property
         String operatorNumeric = SystemProperties.get(PROPERTY_CDMA_HOME_OPERATOR_NUMERIC);
         log("update icc_operator_numeric=" + operatorNumeric);
-        setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric);
-        // Sets iso country property by retrieving from build-time system property
-        setIsoCountryProperty(operatorNumeric);
-        // Updates MCC MNC device configuration information
-        log("update mccmnc=" + operatorNumeric);
-        MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+        if (!TextUtils.isEmpty(operatorNumeric)) {
+            setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric);
+            // Sets iso country property by retrieving from build-time system property
+            setIsoCountryProperty(operatorNumeric);
+            // Updates MCC MNC device configuration information
+            log("update mccmnc=" + operatorNumeric);
+            MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+        }
         // Sets current entry in the telephony carrier table
         updateCurrentCarrierInProvider();
     }
diff --git a/src/java/com/android/internal/telephony/cdma/CDMAPhone.java b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 7991810..d4fb7e3 100644
--- a/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -1719,4 +1719,31 @@
         pw.println(" isMinInfoReady()=" + isMinInfoReady());
         pw.println(" isCspPlmnEnabled()=" + isCspPlmnEnabled());
     }
+
+    @Override
+    public boolean setOperatorBrandOverride(String iccId, String brand) {
+        if (mUiccController == null) {
+            return false;
+        }
+
+        UiccCard card = mUiccController.getUiccCard();
+        if (card == null) {
+            return false;
+        }
+
+        boolean status = card.setOperatorBrandOverride(iccId, brand);
+
+        // Refresh.
+        if (status) {
+            IccRecords iccRecords = mIccRecords.get();
+            if (iccRecords != null) {
+                SystemProperties.set(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA,
+                        iccRecords.getServiceProviderName());
+            }
+            if (mSST != null) {
+                mSST.pollState();
+            }
+        }
+        return status;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 0015cd7..7c698dc 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -236,7 +236,7 @@
     }
 
     @Override
-    protected void pollState() {
+    public void pollState() {
         mPollingContext = new int[1];
         mPollingContext[0] = 0;
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index ca38aab..cbe575f 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -713,7 +713,7 @@
                 } else {
                     mRegistrationDeniedReason = "";
                 }
-    
+
                 if (mRegistrationState == 3) {
                     if (DBG) log("Registration denied, " + mRegistrationDeniedReason);
                 }
@@ -721,8 +721,9 @@
 
             case EVENT_POLL_STATE_OPERATOR_CDMA: // Handle RIL_REQUEST_OPERATOR
                 String opNames[] = (String[])ar.result;
-    
+
                 if (opNames != null && opNames.length >= 3) {
+                    // TODO: Do we care about overriding in this case.
                     // If the NUMERIC field isn't valid use PROPERTY_CDMA_HOME_OPERATOR_NUMERIC
                     if ((opNames[2] == null) || (opNames[2].length() < 5)
                             || ("00000".equals(opNames[2]))) {
@@ -741,17 +742,20 @@
                         // ERI text, so here it is ignored what is coming from the modem.
                         mNewSS.setOperatorName(null, opNames[1], opNames[2]);
                     } else {
-                        mNewSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
+                        String brandOverride = mUiccController.getUiccCard() != null ?
+                            mUiccController.getUiccCard().getOperatorBrandOverride() : null;
+                        if (brandOverride != null) {
+                            mNewSS.setOperatorName(brandOverride, brandOverride, opNames[2]);
+                        } else {
+                            mNewSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
+                        }
                     }
                 } else {
                     if (DBG) log("EVENT_POLL_STATE_OPERATOR_CDMA: error parsing opNames");
                 }
                 break;
 
-            
             default:
-    
-                
                 loge("handlePollStateResultMessage: RIL response handle in wrong phone!"
                         + " Expected CDMA RIL request and get GSM RIL request.");
                 break;
@@ -882,8 +886,8 @@
      * and start over again if the radio notifies us that some
      * event has changed
      */
-    protected void
-    pollState() {
+    @Override
+    public void pollState() {
         mPollingContext = new int[1];
         mPollingContext[0] = 0;
 
diff --git a/src/java/com/android/internal/telephony/gsm/GSMPhone.java b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
index c30a08f..6d2a8ad 100644
--- a/src/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -78,6 +78,7 @@
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccVmNotSupportedException;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.ServiceStateTracker;
@@ -1718,6 +1719,33 @@
         pw.println(" mVmNumber=" + mVmNumber);
     }
 
+    @Override
+    public boolean setOperatorBrandOverride(String iccId, String brand) {
+        if (mUiccController == null) {
+            return false;
+        }
+
+        UiccCard card = mUiccController.getUiccCard();
+        if (card == null) {
+            return false;
+        }
+
+        boolean status = card.setOperatorBrandOverride(iccId, brand);
+
+        // Refresh.
+        if (status) {
+            IccRecords iccRecords = mIccRecords.get();
+            if (iccRecords != null) {
+                SystemProperties.set(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA,
+                        iccRecords.getServiceProviderName());
+            }
+            if (mSST != null) {
+                mSST.pollState();
+            }
+        }
+        return status;
+    }
+
     /**
      * @return operator numeric.
      */
diff --git a/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index f69942f..8e63562 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -785,7 +785,13 @@
                     String opNames[] = (String[])ar.result;
 
                     if (opNames != null && opNames.length >= 3) {
-                         mNewSS.setOperatorName (opNames[0], opNames[1], opNames[2]);
+                        String brandOverride = mUiccController.getUiccCard() != null ?
+                            mUiccController.getUiccCard().getOperatorBrandOverride() : null;
+                        if (brandOverride != null) {
+                            mNewSS.setOperatorName(brandOverride, brandOverride, opNames[2]);
+                        } else {
+                            mNewSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
+                        }
                     }
                     break;
                 }
@@ -841,7 +847,8 @@
      * and start over again if the radio notifies us that some
      * event has changed
      */
-    private void pollState() {
+    @Override
+    public void pollState() {
         mPollingContext = new int[1];
         mPollingContext[0] = 0;
 
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index 2e9bea3..e54dfa4 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -72,7 +72,7 @@
     protected int mMncLength = UNINITIALIZED;
     protected int mMailboxIndex = 0; // 0 is no mailbox dailing number associated
 
-    protected String mSpn;
+    private String mSpn;
 
     protected String mGid1;
 
@@ -304,11 +304,17 @@
     }
 
     /**
-     * Return Service Provider Name stored in SIM (EF_SPN=0x6F46) or in RUIM (EF_RUIM_SPN=0x6F41)
+     * Return Service Provider Name stored in SIM (EF_SPN=0x6F46) or in RUIM (EF_RUIM_SPN=0x6F41).
+     *
      * @return null if SIM is not yet ready or no RUIM entry
      */
     public String getServiceProviderName() {
-        return mSpn;
+        String brandOverride = mParentApp.getUiccCard().getOperatorBrandOverride();
+        return brandOverride == null ? mSpn : brandOverride;
+    }
+
+    protected void setServiceProviderName(String spn) {
+        mSpn = spn;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
index ff2e651..7897753 100755
--- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
@@ -296,24 +296,25 @@
             }
 
             if (numBytes == 0) {
-                mSpn = "";
+                setServiceProviderName("");
                 return;
             }
             try {
                 switch (encoding) {
                 case UserData.ENCODING_OCTET:
                 case UserData.ENCODING_LATIN:
-                    mSpn = new String(spnData, 0, numBytes, "ISO-8859-1");
+                    setServiceProviderName(new String(spnData, 0, numBytes, "ISO-8859-1"));
                     break;
                 case UserData.ENCODING_IA5:
                 case UserData.ENCODING_GSM_7BIT_ALPHABET:
-                    mSpn = GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7);
+                    setServiceProviderName(
+                            GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7));
                     break;
                 case UserData.ENCODING_7BIT_ASCII:
-                    mSpn =  new String(spnData, 0, numBytes, "US-ASCII");
+                    setServiceProviderName(new String(spnData, 0, numBytes, "US-ASCII"));
                 break;
                 case UserData.ENCODING_UNICODE_16:
-                    mSpn =  new String(spnData, 0, numBytes, "utf-16");
+                    setServiceProviderName(new String(spnData, 0, numBytes, "utf-16"));
                     break;
                 default:
                     log("SPN encoding not supported");
@@ -321,9 +322,9 @@
             } catch(Exception e) {
                 log("spn decode error: " + e);
             }
-            if (DBG) log("spn=" + mSpn);
+            if (DBG) log("spn=" + getServiceProviderName());
             if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
-            SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+            SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index dbf573b..772b6c5 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -1375,7 +1375,7 @@
 
     private void setSpnFromConfig(String carrier) {
         if (mSpnOverride.containsCarrier(carrier)) {
-            mSpn = mSpnOverride.getSpn(carrier);
+            setServiceProviderName(mSpnOverride.getSpn(carrier));
         }
     }
 
@@ -1491,7 +1491,7 @@
     @Override
     public int getDisplayRule(String plmn) {
         int rule;
-        if (TextUtils.isEmpty(mSpn) || mSpnDisplayCondition == -1) {
+        if (TextUtils.isEmpty(getServiceProviderName()) || mSpnDisplayCondition == -1) {
             // No EF_SPN content was found on the SIM, or not yet loaded.  Just show ONS.
             rule = SPN_RULE_SHOW_PLMN;
         } else if (isOnMatchingPlmn(plmn)) {
@@ -1579,7 +1579,7 @@
 
         switch(mSpnState){
             case INIT:
-                mSpn = null;
+                setServiceProviderName(null);
 
                 mFh.loadEFTransparent(EF_SPN,
                         obtainMessage(EVENT_GET_SPN_DONE));
@@ -1591,11 +1591,12 @@
                 if (ar != null && ar.exception == null) {
                     data = (byte[]) ar.result;
                     mSpnDisplayCondition = 0xff & data[0];
-                    mSpn = IccUtils.adnStringFieldToString(data, 1, data.length - 1);
+                    setServiceProviderName(IccUtils.adnStringFieldToString(
+                            data, 1, data.length - 1));
 
-                    if (DBG) log("Load EF_SPN: " + mSpn
+                    if (DBG) log("Load EF_SPN: " + getServiceProviderName()
                             + " spnDisplayCondition: " + mSpnDisplayCondition);
-                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
 
                     mSpnState = GetSpnFsmState.IDLE;
                 } else {
@@ -1613,10 +1614,10 @@
             case READ_SPN_CPHS:
                 if (ar != null && ar.exception == null) {
                     data = (byte[]) ar.result;
-                    mSpn = IccUtils.adnStringFieldToString(data, 0, data.length);
+                    setServiceProviderName(IccUtils.adnStringFieldToString(data, 0, data.length));
 
-                    if (DBG) log("Load EF_SPN_CPHS: " + mSpn);
-                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    if (DBG) log("Load EF_SPN_CPHS: " + getServiceProviderName());
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
 
                     mSpnState = GetSpnFsmState.IDLE;
                 } else {
@@ -1630,10 +1631,10 @@
             case READ_SPN_SHORT_CPHS:
                 if (ar != null && ar.exception == null) {
                     data = (byte[]) ar.result;
-                    mSpn = IccUtils.adnStringFieldToString(data, 0, data.length);
+                    setServiceProviderName(IccUtils.adnStringFieldToString(data, 0, data.length));
 
-                    if (DBG) log("Load EF_SPN_SHORT_CPHS: " + mSpn);
-                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    if (DBG) log("Load EF_SPN_SHORT_CPHS: " + getServiceProviderName());
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
                 }else {
                     if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index fa501ad..51521dd 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.content.res.Resources;
@@ -31,8 +32,10 @@
 import android.os.PowerManager;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.preference.PreferenceManager;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.view.WindowManager;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -62,6 +65,8 @@
     protected static final String LOG_TAG = "UiccCard";
     protected static final boolean DBG = true;
 
+    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+
     private final Object mLock = new Object();
     private CardState mCardState;
     private PinState mUniversalPinState;
@@ -503,6 +508,47 @@
                     packageManager, intent);
     }
 
+    public boolean setOperatorBrandOverride(String iccId, String brand) {
+        log("setOperatorBrandOverride: " + iccId + " : " + brand);
+        log("current iccId: " + getIccId());
+
+        if (iccId.isEmpty() || !TextUtils.isDigitsOnly(iccId)) {
+            return false;
+        }
+
+        SharedPreferences.Editor spEditor =
+                PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+        String key = OPERATOR_BRAND_OVERRIDE_PREFIX + iccId;
+        if (brand == null) {
+            spEditor.remove(key).commit();
+        } else {
+            spEditor.putString(key, brand).commit();
+        }
+        return true;
+    }
+
+    public String getOperatorBrandOverride() {
+        String iccId = getIccId();
+        if (iccId == null) {
+          return null;
+        }
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+    }
+
+    private String getIccId() {
+        // ICCID should be same across all the apps.
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) {
+                IccRecords ir = app.getIccRecords();
+                if (ir != null && ir.getIccId() != null) {
+                    return ir.getIccId();
+                }
+            }
+        }
+        return null;
+    }
+
     private void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index 647ef09..94efb98 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -804,6 +804,10 @@
         }
     }
 
+    protected UiccCard getUiccCard() {
+        return mUiccCard;
+    }
+
     private void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }