Merge "Fix read/write permission error in ProvisioningManager"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 30760dc..ce774d8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -65,6 +65,7 @@
     <protected-broadcast android:name= "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE" />
     <protected-broadcast android:name= "com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
     <protected-broadcast android:name= "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
+    <protected-broadcast android:name= "com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED" />
     <protected-broadcast android:name= "com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED" />
     <protected-broadcast android:name= "com.android.intent.isim_refresh" />
     <protected-broadcast android:name= "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE" />
@@ -90,6 +91,7 @@
     <protected-broadcast android:name= "android.telephony.action.SIM_APPLICATION_STATE_CHANGED" />
     <protected-broadcast android:name= "android.telephony.action.SIM_SLOT_STATUS_CHANGED" />
     <protected-broadcast android:name= "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED" />
+    <protected-broadcast android:name= "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED" />
 
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
@@ -214,7 +216,8 @@
         <activity android:name="EmergencyDialer"
             android:label="@string/emergencyDialerIconLabel"
             android:theme="@style/EmergencyDialerTheme"
-            android:screenOrientation="portrait">
+            android:screenOrientation="portrait"
+            android:resizeableActivity="false">
             <intent-filter>
                 <action android:name="com.android.phone.EmergencyDialer.DIAL" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/ecc/input/eccdata.txt b/ecc/input/eccdata.txt
index 54be16b..403cad7 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -808,7 +808,7 @@
 countries {
   iso_code: "GB"
   eccs {
-    phone_number: "112"
+    phone_number: "999"
     types: POLICE
     types: AMBULANCE
     types: FIRE
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index 2d0165a..679bc36 100644
--- a/ecc/output/eccdata
+++ b/ecc/output/eccdata
Binary files differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7fe534c..d6c5fd7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1804,7 +1804,11 @@
     <!-- Message displayed to the user to indicate that a held call has been released /
          disconnected. -->
     <string name="supp_service_held_call_released">Held call has been released.</string>
-
+    <!-- In-call screen: error message shown when the user attempts to place a call, but the device
+         is currently in the process of being provisioned for service.  Provisioning is the process
+         by which a device confirms which services are available to the user by contacting the
+         mobile service provider. -->
+    <string name="callFailed_otasp_provisioning_in_process">Cannot place a call as the device is currently being provisioned.</string>
     <!-- In-call screen: error message shown when the user has attempted to place a new outgoing
          call, but there is already a call in dialing state. -->
     <string name="callFailed_already_dialing">Cannot place a call as another outgoing call is already dialing.</string>
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index bddfd5f..8194c60 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -70,7 +70,6 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.Math;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -221,10 +220,8 @@
 
                 case EVENT_DO_FETCH_DEFAULT:
                 {
-                    final String iccid = getIccIdForPhoneId(phoneId);
-                    final String gid1 = getGid1ForPhoneId(phoneId);
                     final PersistableBundle config =
-                            restoreConfigFromXml(mPlatformCarrierConfigPackage, iccid, gid1);
+                            restoreConfigFromXml(mPlatformCarrierConfigPackage, phoneId);
                     if (config != null) {
                         log(
                                 "Loaded config from XML. package="
@@ -264,8 +261,6 @@
                         break;
                     }
                     final CarrierIdentifier carrierId = getCarrierIdForPhoneId(phoneId);
-                    final String iccid = getIccIdForPhoneId(phoneId);
-                    final String gid1 = getGid1ForPhoneId(phoneId);
                     // ResultReceiver callback will execute in this Handler's thread.
                     final ResultReceiver resultReceiver =
                             new ResultReceiver(this) {
@@ -286,8 +281,8 @@
                                     }
                                     PersistableBundle config =
                                             resultData.getParcelable(KEY_CONFIG_BUNDLE);
-                                    saveConfigToXml(mPlatformCarrierConfigPackage,
-                                            iccid, gid1, config);
+                                    saveConfigToXml(
+                                            mPlatformCarrierConfigPackage, phoneId, config);
                                     mConfigFromDefaultApp[phoneId] = config;
                                     sendMessage(
                                             obtainMessage(
@@ -342,10 +337,8 @@
                 case EVENT_DO_FETCH_CARRIER:
                 {
                     final String carrierPackageName = getCarrierPackageForPhoneId(phoneId);
-                    final String iccid = getIccIdForPhoneId(phoneId);
-                    final String gid1 = getGid1ForPhoneId(phoneId);
                     final PersistableBundle config =
-                            restoreConfigFromXml(carrierPackageName, iccid, gid1);
+                            restoreConfigFromXml(carrierPackageName, phoneId);
                     if (config != null) {
                         log(
                                 "Loaded config from XML. package="
@@ -384,8 +377,6 @@
                         break;
                     }
                     final CarrierIdentifier carrierId = getCarrierIdForPhoneId(phoneId);
-                    final String iccid = getIccIdForPhoneId(phoneId);
-                    final String gid1 = getGid1ForPhoneId(phoneId);
                     // ResultReceiver callback will execute in this Handler's thread.
                     final ResultReceiver resultReceiver =
                             new ResultReceiver(this) {
@@ -406,8 +397,8 @@
                                     }
                                     PersistableBundle config =
                                             resultData.getParcelable(KEY_CONFIG_BUNDLE);
-                                    saveConfigToXml(getCarrierPackageForPhoneId(phoneId),
-                                            iccid, gid1, config);
+                                    saveConfigToXml(
+                                            getCarrierPackageForPhoneId(phoneId), phoneId, config);
                                     mConfigFromCarrierApp[phoneId] = config;
                                     sendMessage(
                                             obtainMessage(
@@ -635,13 +626,19 @@
      * In case of errors or invalid input, no file will be written.
      *
      * @param packageName the name of the package from which we fetched this bundle.
-     * @param iccid the ICCID of the subscription for which this bundle was fetched.
-     * @param extras First 20 characters of gid1 of the subscription for which the bundle
-     *               was fetched.
+     * @param phoneId the phone ID.
      * @param config the bundle to be written. Null will be treated as an empty bundle.
      */
-    private void saveConfigToXml(String packageName, String iccid, String extras,
-            PersistableBundle config) {
+    private void saveConfigToXml(String packageName, int phoneId, PersistableBundle config) {
+        if (SubscriptionManager.getSimStateForSlotIndex(phoneId)
+                != TelephonyManager.SIM_STATE_LOADED) {
+            log("Skip save config because SIM records are not loaded.");
+            return;
+        }
+
+        final String iccid = getIccIdForPhoneId(phoneId);
+        // extras is the first 20 characters of gid1
+        final String extras = getGid1ForPhoneId(phoneId);
         if (packageName == null || iccid == null) {
             loge("Cannot save config with null packageName or iccid.");
             return;
@@ -693,25 +690,31 @@
      * Reads a bundle from an XML file.
      *
      * This restores a bundle that was written with {@link #saveConfigToXml}. This returns the saved
-     * config bundle for the given package and ICCID.
+     * config bundle for the given package and phone ID.
      *
      * In case of errors, or if the saved config is from a different package version than the
      * current version, then null will be returned.
      *
      * @param packageName the name of the package from which we fetched this bundle.
-     * @param iccid the ICCID of the subscription for which this bundle was fetched.
-     * @param extras First 20 characters of gid1 of the subscription for which the bundle
-     *               was fetched.
+     * @param phoneId the phone ID.
      * @return the bundle from the XML file. Returns null if there is no saved config, the saved
      *         version does not match, or reading config fails.
      */
-    private PersistableBundle restoreConfigFromXml(String packageName, String iccid,
-            String extras) {
+    private PersistableBundle restoreConfigFromXml(String packageName, int phoneId) {
         final String version = getPackageVersion(packageName);
         if (version == null) {
             loge("Failed to get package version for: " + packageName);
             return null;
         }
+        if (SubscriptionManager.getSimStateForSlotIndex(phoneId)
+                != TelephonyManager.SIM_STATE_LOADED) {
+            log("Skip restoring config because SIM records are not yet loaded.");
+            return null;
+        }
+
+        final String iccid = getIccIdForPhoneId(phoneId);
+        // extras is the first 20 characters of gid1
+        final String extras = getGid1ForPhoneId(phoneId);
         if (packageName == null || iccid == null) {
             loge("Cannot restore config with null packageName or iccid.");
             return null;
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 1619aa1..47455c5 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -357,11 +357,16 @@
                 CarrierConfigManager.KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL);
         Log.d(LOG_TAG, "Is the carrier supported: " + isSupport);
         TelephonyManager tm = getSystemService(TelephonyManager.class);
-        if (isSupport && !TextUtils.isEmpty(tm.getNetworkCountryIso())) {
-            mAreEmergencyDialerShortcutsEnabled = true;
-        } else {
-            mAreEmergencyDialerShortcutsEnabled = false;
+        String countryIso = tm.getNetworkCountryIso();
+        mAreEmergencyDialerShortcutsEnabled = false;
+        if (isSupport && !TextUtils.isEmpty(countryIso)) {
+            if (EccInfoHelper.isCountryEccInfoAvailable(this, countryIso)) {
+                mAreEmergencyDialerShortcutsEnabled = true;
+            } else {
+                Log.d(LOG_TAG, "ECC info is unavailable. Disable emergency dialer shortcut.");
+            }
         }
+        mAreEmergencyDialerShortcutsEnabled = false;
         Log.d(LOG_TAG, "Enable emergency dialer shortcut: "
                 + mAreEmergencyDialerShortcutsEnabled);
 
@@ -458,7 +463,7 @@
         mEmergencyInfoGroup = (EmergencyInfoGroup) findViewById(R.id.emergency_info_button);
 
         if (mAreEmergencyDialerShortcutsEnabled) {
-            mEccInfoHelper = new EccInfoHelper(new IsoToEccProtobufRepository());
+            mEccInfoHelper = new EccInfoHelper(IsoToEccProtobufRepository.getInstance());
             setupEmergencyShortcutsView();
         }
     }
@@ -928,7 +933,7 @@
         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         int ringerMode = audioManager.getRingerMode();
         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
-            || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
+                || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
             return;
         }
 
@@ -1054,8 +1059,8 @@
                 boolean isCellAvailable =
                         ss.getRilVoiceRadioTechnology() != RIL_RADIO_TECHNOLOGY_UNKNOWN;
                 Log.i(LOG_TAG, "showWfcWarningTask: isWfcAvailable=" + isWfcAvailable
-                                + " isCellAvailable=" + isCellAvailable
-                                + "(rat=" + ss.getRilVoiceRadioTechnology() + ")");
+                        + " isCellAvailable=" + isCellAvailable
+                        + "(rat=" + ss.getRilVoiceRadioTechnology() + ")");
                 return isWfcAvailable && !isCellAvailable;
             }
 
diff --git a/src/com/android/phone/NetworkSelectSetting.java b/src/com/android/phone/NetworkSelectSetting.java
index 248ecc1..9b905b4 100644
--- a/src/com/android/phone/NetworkSelectSetting.java
+++ b/src/com/android/phone/NetworkSelectSetting.java
@@ -417,7 +417,6 @@
 
                 mConnectedNetworkOperatorsPreference.addPreference(pref);
             } else {
-                loge("Invalid CellIfno: " + cellInfo.toString());
                 // Remove the connected network operators category
                 removeConnectedNetworkOperatorPreference();
             }
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 5aa87d0..de3fffc 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -30,7 +30,10 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -50,6 +53,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 import android.widget.Toast;
 
 import com.android.internal.telephony.Phone;
@@ -89,6 +93,14 @@
     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 5;
     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
 
+    // Event for network selection notification.
+    private static final int EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION = 1;
+
+    private static final long NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS = 10000L;
+    private static final int NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES = 10;
+
+    private static final int STATE_UNKNOWN_SERVICE = -1;
+
     /** The singleton NotificationMgr instance. */
     private static NotificationMgr sInstance;
 
@@ -103,12 +115,36 @@
     private TelecomManager mTelecomManager;
     private TelephonyManager mTelephonyManager;
 
-    // used to track the notification of selected network unavailable
-    private boolean mSelectedUnavailableNotify = false;
+    // used to track the notification of selected network unavailable, per subscription id.
+    private SparseArray<Boolean> mSelectedUnavailableNotify = new SparseArray<>();
 
     // used to track whether the message waiting indicator is visible, per subscription id.
     private ArrayMap<Integer, Boolean> mMwiVisible = new ArrayMap<Integer, Boolean>();
 
+    // those flags are used to track whether to show network selection notification or not.
+    private SparseArray<Integer> mPreviousServiceState = new SparseArray<>();
+    private SparseArray<Long> mOOSTimestamp = new SparseArray<>();
+    private SparseArray<Integer> mPendingEventCounter = new SparseArray<>();
+    // maps each subId to selected network operator name.
+    private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>();
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION:
+                    int subId = (int) msg.obj;
+                    TelephonyManager telephonyManager =
+                            mTelephonyManager.createForSubscriptionId(subId);
+                    if (telephonyManager.getServiceState() != null) {
+                        shouldShowNotification(telephonyManager.getServiceState().getState(),
+                                subId);
+                    }
+                    break;
+            }
+        }
+    };
+
     /**
      * Private constructor (this is a singleton).
      * @see #init(PhoneGlobals)
@@ -592,19 +628,21 @@
         intent.putExtra(GsmUmtsOptions.EXTRA_SUB_ID, subId);
         builder.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0));
         mNotificationManager.notifyAsUser(
-                null /* tag */,
+                Integer.toString(subId) /* tag */,
                 SELECTED_OPERATOR_FAIL_NOTIFICATION,
                 builder.build(),
                 UserHandle.ALL);
+        mSelectedUnavailableNotify.put(subId, true);
     }
 
     /**
      * Turn off the network selection "no service" notification
      */
-    private void cancelNetworkSelection() {
+    private void cancelNetworkSelection(int subId) {
         if (DBG) log("cancelNetworkSelection()...");
         mNotificationManager.cancelAsUser(
-                null /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION, UserHandle.ALL);
+                Integer.toString(subId) /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION,
+                UserHandle.ALL);
     }
 
     /**
@@ -645,18 +683,34 @@
                             + (isManualSelection ? selectedNetworkOperatorName : ""));
                 }
 
-                if (serviceState == ServiceState.STATE_OUT_OF_SERVICE && isManualSelection) {
-                    showNetworkSelection(selectedNetworkOperatorName, subId);
-                    mSelectedUnavailableNotify = true;
+                if (isManualSelection) {
+                    mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
+                    shouldShowNotification(serviceState, subId);
                 } else {
-                    if (mSelectedUnavailableNotify) {
-                        cancelNetworkSelection();
-                        mSelectedUnavailableNotify = false;
-                    }
+                    dismissNetworkSelectionNotification(subId);
+                    clearUpNetworkSelectionNotificationParam(subId);
                 }
             } else {
                 if (DBG) log("updateNetworkSelection()..." + "state = " +
                         serviceState + " not updating network due to invalid subId " + subId);
+                dismissNetworkSelectionNotificationForInactiveSubId();
+            }
+        }
+    }
+
+    private void dismissNetworkSelectionNotification(int subId) {
+        if (mSelectedUnavailableNotify.get(subId, false)) {
+            cancelNetworkSelection(subId);
+            mSelectedUnavailableNotify.remove(subId);
+        }
+    }
+
+    private void dismissNetworkSelectionNotificationForInactiveSubId() {
+        for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
+            int subId = mSelectedUnavailableNotify.keyAt(i);
+            if (!mSubscriptionManager.isActiveSubId(subId)) {
+                dismissNetworkSelectionNotification(subId);
+                clearUpNetworkSelectionNotificationParam(subId);
             }
         }
     }
@@ -677,4 +731,65 @@
     private void logi(String msg) {
         Log.i(LOG_TAG, msg);
     }
+
+    /**
+     * In case network selection notification shows up repeatedly under
+     * unstable network condition. The logic is to check whether or not
+     * the service state keeps in no service condition for at least
+     * {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}.
+     * And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times.
+     * To avoid the notification showing up for the momentary state.
+     */
+    private void shouldShowNotification(int serviceState, int subId) {
+        if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
+            if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
+                    != ServiceState.STATE_OUT_OF_SERVICE) {
+                mOOSTimestamp.put(subId, getTimeStamp());
+            }
+            if ((getTimeStamp() - mOOSTimestamp.get(subId, 0L)
+                    >= NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS)
+                    || mPendingEventCounter.get(subId, 0)
+                    > NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES) {
+                showNetworkSelection(mSelectedNetworkOperatorName.get(subId), subId);
+                clearUpNetworkSelectionNotificationParam(subId);
+            } else {
+                startPendingNetworkSelectionNotification(subId);
+            }
+        } else {
+            dismissNetworkSelectionNotification(subId);
+        }
+        mPreviousServiceState.put(subId, serviceState);
+        if (DBG) {
+            log("shouldShowNotification()..." + " subId = " + subId
+                    + " serviceState = " + serviceState
+                    + " mOOSTimestamp = " + mOOSTimestamp
+                    + " mPendingEventCounter = " + mPendingEventCounter);
+        }
+    }
+
+    private void startPendingNetworkSelectionNotification(int subId) {
+        if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
+            if (DBG) {
+                log("startPendingNetworkSelectionNotification: subId = " + subId);
+            }
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId),
+                    NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS);
+            mPendingEventCounter.put(subId, mPendingEventCounter.get(subId, 0) + 1);
+        }
+    }
+
+    private void clearUpNetworkSelectionNotificationParam(int subId) {
+        if (mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
+            mHandler.removeMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId);
+        }
+        mPreviousServiceState.remove(subId);
+        mOOSTimestamp.remove(subId);
+        mPendingEventCounter.remove(subId);
+        mSelectedNetworkOperatorName.remove(subId);
+    }
+
+    private static long getTimeStamp() {
+        return SystemClock.elapsedRealtime();
+    }
 }
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
index df151bf..333e0ec 100644
--- a/src/com/android/phone/PhoneApp.java
+++ b/src/com/android/phone/PhoneApp.java
@@ -19,6 +19,7 @@
 import android.app.Application;
 import android.os.UserHandle;
 
+import com.android.phone.ecc.IsoToEccProtobufRepository;
 import com.android.services.telephony.TelecomAccountRegistry;
 
 /**
@@ -40,5 +41,10 @@
 
             TelecomAccountRegistry.getInstance(this).setupOnBoot();
         }
+
+        new Thread(() -> {
+            // Preload ECC table in background.
+            IsoToEccProtobufRepository.getInstance().loadMappingTable(PhoneApp.this);
+        }).start();
     }
 }
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 8f66dc2..7c7d230 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -78,6 +78,7 @@
 import android.telephony.UssdResponse;
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.gsm.GsmCellLocation;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
@@ -117,6 +118,8 @@
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.SmsApplication;
+import com.android.internal.telephony.SmsApplication.SmsApplicationData;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.euicc.EuiccConnector;
@@ -140,6 +143,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -2055,6 +2059,40 @@
         }
     }
 
+    @Override
+    public int getSubscriptionMNOCarrierId(int subId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Phone phone = getPhone(subId);
+            return phone == null ? TelephonyManager.UNKNOWN_CARRIER_ID : phone.getMNOCarrierId();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public int getSubscriptionPreciseCarrierId(int subId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Phone phone = getPhone(subId);
+            return phone == null ? TelephonyManager.UNKNOWN_CARRIER_ID
+                    : phone.getPreciseCarrierId();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public String getSubscriptionPreciseCarrierName(int subId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Phone phone = getPhone(subId);
+            return phone == null ? null : phone.getPreciseCarrierName();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     //
     // Internal helper methods.
     //
@@ -5764,6 +5802,88 @@
         }
     }
 
+    private void ensureUserRunning(int userId) {
+        if (!mUserManager.isUserRunning(userId)) {
+            throw new IllegalStateException("User " + userId + " does not exist or not running");
+        }
+    }
+
+    /**
+     * Returns a list of SMS apps on a given user.
+     *
+     * Only the shell user (UID 2000 or 0) can call it.
+     * Target user must be running.
+     */
+    @Override
+    public String[] getSmsApps(int userId) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getSmsApps");
+        ensureUserRunning(userId);
+
+        final Collection<SmsApplicationData> apps =
+                SmsApplication.getApplicationCollectionAsUser(mApp, userId);
+
+        String[] ret = new String[apps.size()];
+        int i = 0;
+        for (SmsApplicationData app : apps) {
+            ret[i++] = app.mPackageName;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns the default SMS app package name on a given user.
+     *
+     * Only the shell user (UID 2000 or 0) can call it.
+     * Target user must be running.
+     */
+    @Override
+    public String getDefaultSmsApp(int userId) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getDefaultSmsApp");
+        ensureUserRunning(userId);
+
+        final ComponentName cn = SmsApplication.getDefaultSmsApplicationAsUser(mApp,
+                /* updateIfNeeded= */ true, userId);
+        return cn == null ? null : cn.getPackageName();
+    }
+
+    /**
+     * Set a package as the default SMS app on a given user.
+     *
+     * Only the shell user (UID 2000 or 0) can call it.
+     * Target user must be running.
+     */
+    @Override
+    public void setDefaultSmsApp(int userId, String packageName) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setDefaultSmsApp");
+        ensureUserRunning(userId);
+
+        boolean found = false;
+        for (String pkg : getSmsApps(userId)) {
+            if (TextUtils.equals(packageName, pkg)) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            throw new IllegalArgumentException("Package " + packageName + " is not an SMS app");
+        }
+
+        SmsApplication.setDefaultApplicationAsUser(packageName, mApp, userId);
+    }
+
+    @Override
+    public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList(
+            String callingPackage) {
+        // TODO connect with internal content
+        return null;
+    }
+
+    @Override
+    public boolean isCurrentEmergencyNumber(String number) {
+        // TODO connect with internal content
+        return false;
+    }
+
     @Override
     public List<String> getCertsFromCarrierPrivilegeAccessRules(int subId) {
         enforceReadPrivilegedPermission("getCertsFromCarrierPrivilegeAccessRules");
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 4acb46b..1a25ae3 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -18,6 +18,7 @@
 
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
@@ -39,11 +40,16 @@
     private static final int DEFAULT_PHONE_ID = 0;
 
     private static final String IMS_SUBCOMMAND = "ims";
+    private static final String SMS_SUBCOMMAND = "sms";
     private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service";
     private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
     private static final String IMS_ENABLE = "enable";
     private static final String IMS_DISABLE = "disable";
 
+    private static final String SMS_GET_APPS = "get-apps";
+    private static final String SMS_GET_DEFAULT_APP = "get-default-app";
+    private static final String SMS_SET_DEFAULT_APP = "set-default-app";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -61,6 +67,9 @@
             case IMS_SUBCOMMAND: {
                 return handleImsCommand();
             }
+            case SMS_SUBCOMMAND: {
+                return handleSmsCommand();
+            }
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -75,7 +84,10 @@
         pw.println("    Print this help text.");
         pw.println("  ims");
         pw.println("    IMS Commands.");
+        pw.println("  sms");
+        pw.println("    SMS Commands.");
         onHelpIms();
+        onHelpSms();
     }
 
     private void onHelpIms() {
@@ -103,6 +115,17 @@
         pw.println("    slot if none is specified.");
     }
 
+    private void onHelpSms() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("SMS Commands:");
+        pw.println("  sms get-apps [--user USER_ID]");
+        pw.println("    Print all SMS apps on a user.");
+        pw.println("  sms get-default-app [--user USER_ID]");
+        pw.println("    Get the default SMS app.");
+        pw.println("  sms set-default-app [--user USER_ID] PACKAGE_NAME");
+        pw.println("    Set PACKAGE_NAME as the default SMS app.");
+    }
+
     private int handleImsCommand() {
         String arg = getNextArg();
         if (arg == null) {
@@ -296,4 +319,85 @@
         }
         return slotId;
     }
+
+    private int handleSmsCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpSms();
+            return 0;
+        }
+
+        try {
+            switch (arg) {
+                case SMS_GET_APPS: {
+                    return handleSmsGetApps();
+                }
+                case SMS_GET_DEFAULT_APP: {
+                    return handleSmsGetDefaultApp();
+                }
+                case SMS_SET_DEFAULT_APP: {
+                    return handleSmsSetDefaultApp();
+                }
+                default:
+                    getErrPrintWriter().println("Unknown command " + arg);
+            }
+        } catch (RemoteException e) {
+            getErrPrintWriter().println("RemoteException: " + e.getMessage());
+        }
+
+        return -1;
+    }
+
+    private int maybeParseUserIdArg() {
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user": {
+                    try {
+                        userId = Integer.parseInt(getNextArgRequired());
+                    } catch (NumberFormatException e) {
+                        getErrPrintWriter().println("Invalid user ID for --user");
+                        return -1;
+                    }
+                    break;
+                }
+            }
+        }
+        return userId;
+    }
+
+    private int handleSmsGetApps() throws RemoteException {
+        final int userId = maybeParseUserIdArg();
+        if (userId < 0) {
+            return -1;
+        }
+
+        for (String packageName : mInterface.getSmsApps(userId)) {
+            getOutPrintWriter().println(packageName);
+        }
+        return 0;
+    }
+
+    private int handleSmsGetDefaultApp() throws RemoteException {
+        final int userId = maybeParseUserIdArg();
+        if (userId < 0) {
+            return -1;
+        }
+
+        getOutPrintWriter().println(mInterface.getDefaultSmsApp(userId));
+        return 0;
+    }
+
+    private int handleSmsSetDefaultApp() throws RemoteException {
+        final int userId = maybeParseUserIdArg();
+        if (userId < 0) {
+            return -1;
+        }
+
+        String packageName = getNextArgRequired();
+        mInterface.setDefaultSmsApp(userId, packageName);
+        getOutPrintWriter().println("SMS app set to " + mInterface.getDefaultSmsApp(userId));
+        return 0;
+    }
 }
diff --git a/src/com/android/phone/ecc/EccInfoHelper.java b/src/com/android/phone/ecc/EccInfoHelper.java
index cd47dde..c471c4b 100644
--- a/src/com/android/phone/ecc/EccInfoHelper.java
+++ b/src/com/android/phone/ecc/EccInfoHelper.java
@@ -50,6 +50,32 @@
     private static final boolean DBG = false;
     private static final String LOG_TAG = "EccInfoHelper";
 
+    /**
+     * Check if current CountryEccInfo is available for current environment.
+     */
+    public static boolean isCountryEccInfoAvailable(Context context, String countryIso) {
+        CountryEccInfo countryEccInfo;
+        try {
+            countryEccInfo = IsoToEccProtobufRepository.getInstance()
+                    .getCountryEccInfo(context, countryIso);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Failed to retrieve ECC: ", e);
+            return false;
+        }
+
+        if (countryEccInfo == null) {
+            return false;
+        }
+        for (EccInfo entry : countryEccInfo.getEccInfoList()) {
+            if (!PhoneNumberUtils.isEmergencyNumber(entry.getNumber())) {
+                // The CountryEccInfo is unavailable if any ecc number in the local table was
+                // declined.
+                return false;
+            }
+        }
+        return true;
+    }
+
     // country ISO to ECC list data source
     private IsoToEccRepository mEccRepo;
 
@@ -136,7 +162,8 @@
         }.execute();
     }
 
-    private @NonNull CountryEccInfo getDialableCountryEccInfo(CountryEccInfo countryEccInfo) {
+    @NonNull
+    private CountryEccInfo getDialableCountryEccInfo(CountryEccInfo countryEccInfo) {
         ArrayList<EccInfo> dialableECCList = new ArrayList<>();
         String dialableFallback = null;
 
@@ -155,7 +182,8 @@
         return new CountryEccInfo(dialableFallback, dialableECCList);
     }
 
-    private @Nullable String getCurrentCountryIso(@NonNull Context context) {
+    @Nullable
+    private String getCurrentCountryIso(@NonNull Context context) {
         // Do not detect country ISO if airplane mode is on
         int airplaneMode = Settings.System.getInt(context.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0);
@@ -185,7 +213,8 @@
     // XXX: According to ServiceStateTracker implementation, to actually get current cell info,
     // this method must be called in a separate thread from ServiceStateTracker, which is the
     // main thread of Telephony service.
-    private @Nullable String getCurrentMccFromCellInfo(@NonNull Context context) {
+    @Nullable
+    private String getCurrentMccFromCellInfo(@NonNull Context context) {
         // retrieve mcc info from base station even no SIM present.
         TelephonyManager tm = (TelephonyManager) context.getSystemService(
                 Context.TELEPHONY_SERVICE);
diff --git a/src/com/android/phone/ecc/IsoToEccProtobufRepository.java b/src/com/android/phone/ecc/IsoToEccProtobufRepository.java
index 817ff1d..0cd3108 100644
--- a/src/com/android/phone/ecc/IsoToEccProtobufRepository.java
+++ b/src/com/android/phone/ecc/IsoToEccProtobufRepository.java
@@ -40,7 +40,22 @@
 public class IsoToEccProtobufRepository implements IsoToEccRepository {
     private static final String LOG_TAG = "EccRepository";
 
-    private Map<String, CountryEccInfo> mEccTable = null;
+    private static IsoToEccProtobufRepository sInstance;
+
+    /**
+     * Returns the singleton instance of IsoToEccProtobufRepository
+     */
+    public static synchronized IsoToEccProtobufRepository getInstance() {
+        if (sInstance == null) {
+            sInstance = new IsoToEccProtobufRepository();
+        }
+        return sInstance;
+    }
+
+    private final Map<String, CountryEccInfo> mEccTable = new HashMap<>();
+
+    private IsoToEccProtobufRepository() {
+    }
 
     @Override
     @Nullable
@@ -50,24 +65,28 @@
             return null;
         }
 
-        if (mEccTable == null) {
-            mEccTable = initMappingTable(context);
+        synchronized (mEccTable) {
+            return mEccTable.get(iso.toUpperCase());
         }
-        return mEccTable.get(iso.toUpperCase());
     }
 
-    private Map<String, CountryEccInfo> initMappingTable(@NonNull Context context)
-            throws IOException {
+    /**
+     * Loads the mapping table.
+     */
+    public void loadMappingTable(@NonNull Context context) {
         ProtobufEccData.AllInfo allEccData = null;
 
         long startTime = SystemClock.uptimeMillis();
-        allEccData = parseEccData(new BufferedInputStream(
-                context.getAssets().open("eccdata")));
+        try {
+            allEccData = parseEccData(new BufferedInputStream(
+                    context.getAssets().open("eccdata")));
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Failed to retrieve ECC: ", e);
+        }
         long endTime = SystemClock.uptimeMillis();
 
         if (allEccData == null) {
-            // Returns an empty table.
-            return new HashMap<>();
+            return;
         }
 
         if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
@@ -77,16 +96,17 @@
         }
 
         // Converts to run-time data from Protobuf data.
-        Map<String, CountryEccInfo> table = new HashMap<>();
-        for (ProtobufEccData.CountryInfo countryData : allEccData.getCountriesList()) {
-            if (countryData.hasIsoCode()) {
-                CountryEccInfo countryInfo = loadCountryEccInfo(countryData);
-                if (countryInfo != null) {
-                    table.put(countryData.getIsoCode().toUpperCase(), countryInfo);
+        synchronized (mEccTable) {
+            mEccTable.clear();
+            for (ProtobufEccData.CountryInfo countryData : allEccData.getCountriesList()) {
+                if (countryData.hasIsoCode()) {
+                    CountryEccInfo countryInfo = loadCountryEccInfo(countryData);
+                    if (countryInfo != null) {
+                        mEccTable.put(countryData.getIsoCode().toUpperCase(), countryInfo);
+                    }
                 }
             }
         }
-        return table;
     }
 
     private ProtobufEccData.AllInfo parseEccData(InputStream input) throws IOException {
diff --git a/src/com/android/phone/otasp/OtaspActivationService.java b/src/com/android/phone/otasp/OtaspActivationService.java
index c41640c..7490880 100644
--- a/src/com/android/phone/otasp/OtaspActivationService.java
+++ b/src/com/android/phone/otasp/OtaspActivationService.java
@@ -28,6 +28,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.telephony.GsmCdmaConnection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.phone.PhoneGlobals;
@@ -45,8 +46,10 @@
 public class OtaspActivationService extends Service {
     private static final String TAG = OtaspActivationService.class.getSimpleName();
     private static final boolean DBG = true;
-    /* non-interactive otasp number */
-    private static final String OTASP_NUMBER = "*22899";
+    /**
+     * non-interactive otasp number
+     */
+    private static final String OTASP_NUMBER = GsmCdmaConnection.OTASP_NUMBER;
 
     /**
      * Otasp call follows with SIM reloading which might triggers a retry loop on activation
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 611d147..9ed34e7 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -173,6 +173,7 @@
             case android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING:
             case android.telephony.DisconnectCause.CALLING_DISABLED:
             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
+            case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
             case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER:
             case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING:
             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD:
@@ -337,11 +338,12 @@
             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
                 resourceId = R.string.callFailed_too_many_calls;
                 break;
-
             case android.telephony.DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL:
                 resourceId = R.string.incall_error_power_off;
                 break;
-
+            case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
+                resourceId = R.string.callFailed_otasp_provisioning_in_process;
+                break;
             default:
                 break;
         }
@@ -717,11 +719,12 @@
             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
                 resourceId = R.string.callFailed_too_many_calls;
                 break;
-
             case android.telephony.DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL:
                 resourceId = R.string.incall_error_power_off;
                 break;
-
+            case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
+                resourceId = R.string.callFailed_otasp_provisioning_in_process;
+                break;
             default:
                 break;
         }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 07754c6..f986994 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -1140,6 +1140,9 @@
                 case CallStateException.ERROR_TOO_MANY_CALLS:
                     cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS;
                     break;
+                case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS:
+                    cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
+                    break;
             }
             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                     cause, e.getMessage(), phone.getPhoneId()));
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 5f19509..708ea66 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -18,6 +18,7 @@
           package="com.android.phone.testapps.telephonyregistry">
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
     <application android:label="TelephonyRegistryTestApp">
         <activity
             android:name=".TelephonyRegistryTestApp"
diff --git a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
index 8593245..74cafcd 100644
--- a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
+++ b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
@@ -54,7 +54,7 @@
         put(PhoneStateListener.LISTEN_PRECISE_CALL_STATE, "PRECISE_CALL_STATE");
         put(PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE,
                 "PRECISE_DATA_CONNECTION_STATE");
-        put(PhoneStateListener.LISTEN_VOLTE_STATE, "VOLTE_STATE");
+        put(PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED, "SRVCC_STATE");
         put(PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE, "CARRIER_NETWORK_CHANGE");
         put(PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE, "VOICE_ACTIVATION_STATE");
         put(PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE, "DATA_ACTIVATION_STATE");
@@ -71,6 +71,11 @@
             notify("onCellInfoChanged", cellInfo);
         }
 
+        @Override
+        public void onSrvccStateChanged(int srvccState) {
+            notify("onSrvccStateChanged", srvccState);
+        }
+
         private void notify(String method, Object data) {
             Notification.Builder builder = new Notification.Builder(TelephonyRegistryTestApp.this,
                     NOTIFICATION_CHANNEL);