[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: febb11ca24 -s ours am: 21de308b62 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/20148386

Change-Id: I79bd3d84a82d9910de074a1c979cb527d1ae2aa9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8218a84..d0f427c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -207,8 +207,6 @@
     <style name="DialerAlertDialogTheme"
         parent="@android:style/Theme.Material.Light.Dialog">
         <item name="android:forceDarkAllowed">true</item>
-        <item name="android:colorAccent">@color/dialer_theme_color</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
     </style>
 
     <style name="Empty" parent="@android:style/Theme.Material.Light">
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index f2641a1..226664d 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.READ_PHONE_STATE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -47,6 +48,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -59,6 +61,7 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.phone.settings.VoicemailSettingsActivity;
@@ -895,15 +898,21 @@
         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) {
+        // "Network selection unavailable" notification should only show when network selection is
+        // visible to the end user. Some CC items e.g. KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL
+        // can be overridden to hide the network selection to the end user. In this case, the
+        // notification is not shown to avoid confusion to the end user.
+        if (!shouldDisplayNetworkSelectOptions(subId)) {
+            logi("Skipping network selection unavailable notification due to carrier policy.");
+            return;
+        }
+
+        // In unstable network condition, the phone may go in and out of service. Add logic here to
+        // debounce the network selection notification. The notification only shows after phone is
+        // out of service, AND fulfills one of the two conditions below:
+        // - Out of service lasts {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}
+        // - Or has checked {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times
         if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
             if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
                     != ServiceState.STATE_OUT_OF_SERVICE) {
@@ -955,4 +964,111 @@
     private static long getTimeStamp() {
         return SystemClock.elapsedRealtime();
     }
+
+    // TODO(b/243010310): merge methods below with Settings#MobileNetworkUtils and optimize them.
+    // The methods below are copied from com.android.settings.network.telephony.MobileNetworkUtils
+    // to make sure the network selection unavailable notification should not show when network
+    // selection menu is not visible to the end user in Settings app.
+    private boolean shouldDisplayNetworkSelectOptions(int subId) {
+        final TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(subId);
+        final CarrierConfigManager carrierConfigManager = mContext.getSystemService(
+                CarrierConfigManager.class);
+        final PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
+
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || carrierConfig == null
+                || !carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL)
+                || carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
+                || (carrierConfig.getBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL)
+                && !telephonyManager.isManualNetworkSelectionAllowed())) {
+            return false;
+        }
+
+        if (isWorldMode(carrierConfig)) {
+            final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
+                    (int) telephonyManager.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
+            if (networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO) {
+                return false;
+            }
+            if (shouldSpeciallyUpdateGsmCdma(telephonyManager, carrierConfig)) {
+                return false;
+            }
+            if (networkMode == RILConstants.NETWORK_MODE_LTE_GSM_WCDMA) {
+                return true;
+            }
+        }
+
+        return isGsmBasicOptions(telephonyManager, carrierConfig);
+    }
+
+    private static boolean isWorldMode(@NonNull PersistableBundle carrierConfig) {
+        return carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL);
+    }
+
+    private static boolean shouldSpeciallyUpdateGsmCdma(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (!isWorldMode(carrierConfig)) {
+            return false;
+        }
+
+        final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
+                (int) telephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
+        if (networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA
+                || networkMode
+                == RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) {
+            if (!isTdscdmaSupported(telephonyManager, carrierConfig)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isTdscdmaSupported(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL)) {
+            return true;
+        }
+        final String[] numericArray = carrierConfig.getStringArray(
+                CarrierConfigManager.KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY);
+        if (numericArray == null) {
+            return false;
+        }
+        final ServiceState serviceState = telephonyManager.getServiceState();
+        final String operatorNumeric =
+                (serviceState != null) ? serviceState.getOperatorNumeric() : null;
+        if (operatorNumeric == null) {
+            return false;
+        }
+        for (String numeric : numericArray) {
+            if (operatorNumeric.equals(numeric)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isGsmBasicOptions(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (!carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
+                && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) {
+            return true;
+        }
+
+        if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            return true;
+        }
+
+        return false;
+    }
+    // END of TODO:(b/243010310): merge methods above with Settings#MobileNetworkUtils and optimize.
 }
diff --git a/src/com/android/phone/PhoneDisplayMessage.java b/src/com/android/phone/PhoneDisplayMessage.java
index be7fc7f..c487cba 100644
--- a/src/com/android/phone/PhoneDisplayMessage.java
+++ b/src/com/android/phone/PhoneDisplayMessage.java
@@ -80,7 +80,8 @@
         sDisplayMessageDialog.getWindow().addFlags(
                 WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
 
         sDisplayMessageDialog.show();
     }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 99e73ff..f6ec75d 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -549,15 +549,15 @@
                     mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
             mTelephonyCallbacks = new PhoneAppCallback[tm.getSupportedModemCount()];
-
-            for (Phone phone : PhoneFactory.getPhones()) {
-                int subId = phone.getSubId();
-                PhoneAppCallback callback = new PhoneAppCallback(subId);
-                tm.createForSubscriptionId(subId).registerTelephonyCallback(
-                        TelephonyManager.INCLUDE_LOCATION_DATA_NONE, mHandler::post, callback);
-                mTelephonyCallbacks[phone.getPhoneId()] = callback;
+            if (tm.getSupportedModemCount() > 0) {
+                for (Phone phone : PhoneFactory.getPhones()) {
+                    int subId = phone.getSubId();
+                    PhoneAppCallback callback = new PhoneAppCallback(subId);
+                    tm.createForSubscriptionId(subId).registerTelephonyCallback(
+                            TelephonyManager.INCLUDE_LOCATION_DATA_NONE, mHandler::post, callback);
+                    mTelephonyCallbacks[phone.getPhoneId()] = callback;
+                }
             }
-
             mCarrierVvmPackageInstalledReceiver.register(this);
 
             //set the default values for the preferences in the phone.
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 66cef64..4122dfe 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -8095,8 +8095,7 @@
             }
             String vvmPackage = componentName.getPackageName();
             if (!callingPackage.equals(vvmPackage)) {
-                throw new SecurityException("Caller not current active visual voicemail package["
-                        + vvmPackage + "]");
+                throw new SecurityException("Caller not current active visual voicemail package");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 695a4a4..d0aad4a 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -469,7 +469,7 @@
 
                 // build the dialog
                 final AlertDialog newDialog =
-                        FrameworksUtils.makeAlertDialogBuilder(contextThemeWrapper)
+                        new AlertDialog.Builder(contextThemeWrapper)
                         .setMessage(text)
                         .setView(dialogView)
                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 9321e1e..587ac43 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -25,6 +25,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsReasonInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -101,14 +102,29 @@
     public static DisconnectCause toTelecomDisconnectCause(
             int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
             int phoneId, ImsReasonInfo imsReasonInfo) {
+        return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
+                reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId));
+    }
+
+    /**
+     * Final pre-processing method in creating a DisconnectCause.  This method should NOT be called
+     * from another class directly.  It only has private-package visibility for testing.
+     *
+     * @param carrierConfig
+     */
+    @VisibleForTesting
+    static DisconnectCause toTelecomDisconnectCause(
+            int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
+            int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig) {
         Context context = PhoneGlobals.getInstance();
+
         return new DisconnectCause(
-                toTelecomDisconnectCauseCode(telephonyDisconnectCause),
+                toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig),
                 toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
-                        telephonyPreciseDisconnectCause),
+                        telephonyPreciseDisconnectCause, carrierConfig),
                 toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
-                toTelecomDisconnectReason(context,telephonyDisconnectCause, reason, phoneId),
-                toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId),
+                toTelecomDisconnectReason(context, telephonyDisconnectCause, reason, phoneId),
+                toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig),
                 telephonyDisconnectCause,
                 telephonyPreciseDisconnectCause,
                 imsReasonInfo);
@@ -119,7 +135,16 @@
      * {@link android.telecom.DisconnectCause} disconnect code.
      * @return The disconnect code as defined in {@link android.telecom.DisconnectCause}.
      */
-    private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause) {
+    private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause,
+            PersistableBundle carrierConfig) {
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        // hence, must adjust the disconnectCause CODE to match the tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return DisconnectCause.BUSY;
+        }
+
         switch (telephonyDisconnectCause) {
             case android.telephony.DisconnectCause.LOCAL:
             //  The call was still disconnected locally, so this is not an error condition.
@@ -237,8 +262,17 @@
      * Returns a label for to the disconnect cause to be shown to the user.
      */
     private static CharSequence toTelecomDisconnectCauseLabel(
-            Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause) {
+            Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause,
+            PersistableBundle carrierConfig) {
         CharSequence label;
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        // hence, must adjust the disconnectCause LABEL to match the tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return context.getResources().getString(R.string.callFailed_userBusy);
+        }
+
         if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
             label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
                     telephonyDisconnectCause);
@@ -695,6 +729,10 @@
                 resourceId = R.string.incall_error_emergency_only;
                 break;
 
+            case android.telephony.DisconnectCause.ICC_ERROR:
+                resourceId = R.string.callFailed_simError;
+                break;
+
             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
                 // No network connection.
                 if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
@@ -840,21 +878,15 @@
     /**
      * Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played.
      */
-    private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause, int phoneId) {
-        Phone phone = PhoneFactory.getPhone(phoneId);
-        PersistableBundle config;
-        if (phone != null) {
-            config = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
-        } else {
-            config = PhoneGlobals.getInstance().getCarrierConfig();
+    private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause,
+            PersistableBundle carrierConfig) {
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return ToneGenerator.TONE_SUP_BUSY;
         }
-        int[] busyToneArray = config.getIntArray(
-                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
-        for (int busyTone : busyToneArray) {
-            if (busyTone == telephonyDisconnectCause) {
-                return ToneGenerator.TONE_SUP_BUSY;
-            }
-        }
+
         switch (telephonyDisconnectCause) {
             case android.telephony.DisconnectCause.CONGESTION:
                 return ToneGenerator.TONE_SUP_CONGESTION;
@@ -886,4 +918,37 @@
                 return ToneGenerator.TONE_PROP_PROMPT;
         }
     }
+
+    /**
+     * Helper method that examines the carrierConfig KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY
+     * containing the DisconnectCauses that are classified as DisconnectCause.BUSY
+     * @param telephonyDisconnectCause
+     * @param carrierConfig object that holds all the carrier specific settings
+     * @return whether the cause is in the carrier config busy tone array
+     */
+    private static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
+            int telephonyDisconnectCause, PersistableBundle carrierConfig) {
+        int[] busyToneArray = carrierConfig.getIntArray(
+                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
+        for (int busyTone : busyToneArray) {
+            if (busyTone == telephonyDisconnectCause) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static PersistableBundle getCarrierConfigBundle(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        PersistableBundle config;
+
+        if (phone != null) {
+            config = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
+        } else {
+            config = PhoneGlobals.getInstance().getCarrierConfig();
+        }
+
+        return config;
+    }
+
 }
diff --git a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 7f9efdc..28a7b02 100644
--- a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
+++ b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
@@ -17,19 +17,104 @@
 package com.android.services.telephony;
 
 import static android.media.ToneGenerator.TONE_PROP_PROMPT;
+import static android.media.ToneGenerator.TONE_SUP_BUSY;
 
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.assertEquals;
 
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.phone.common.R;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+
 
 @RunWith(AndroidJUnit4.class)
-public class DisconnectCauseUtilTest {
+public class DisconnectCauseUtilTest extends TelephonyTestBase {
+
+    // constants
+    public static final int PHONE_ID = 123;
+    public static final String EMPTY_STRING = "";
+
+    // dynamic
+    private Context mContext;
+    private HashMap<InstanceKey, Object> mOldInstances = new HashMap<InstanceKey, Object>();
+    private ArrayList<InstanceKey> mInstanceKeys = new ArrayList<InstanceKey>();
+
+    //Mocks
+    @Mock
+    private GsmCdmaPhone mMockPhone;
+
+    // inner classes
+    private static class InstanceKey {
+        public final Class mClass;
+        public final String mInstName;
+        public final Object mObj;
+
+        InstanceKey(final Class c, final String instName, final Object obj) {
+            mClass = c;
+            mInstName = instName;
+            mObj = obj;
+        }
+
+        @Override
+        public int hashCode() {
+            return (mClass.getName().hashCode() * 31 + mInstName.hashCode()) * 31;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null || !(obj instanceof InstanceKey)) {
+                return false;
+            }
+
+            InstanceKey other = (InstanceKey) obj;
+            return (other.mClass == mClass && other.mInstName.equals(mInstName)
+                    && other.mObj == mObj);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        // objects that call static getInstance()
+        mMockPhone = Mockito.mock(GsmCdmaPhone.class);
+        mContext = InstrumentationRegistry.getTargetContext();
+        // set mocks
+        setSinglePhone();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // restoreInstance.
+        // Not doing so will potentially "confuse" other tests with the mocked instance
+        restoreInstance(PhoneFactory.class, "sPhones", null);
+        super.tearDown();
+    }
+
+
     /**
      * Verifies that a call drop due to loss of WIFI results in a disconnect cause of error and that
      * the label, description and tone are all present.
@@ -43,4 +128,109 @@
         assertNotNull(tcCause.getDescription());
         assertNotNull(tcCause.getReason());
     }
+
+    /**
+     *  ensure the default behavior was not changed when a disconnect cause comes in as
+     *  DisconnectCause.ERROR_UNSPECIFIED
+     */
+    @Test
+    public void testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray() {
+        android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                DisconnectCause.ERROR_UNSPECIFIED, EMPTY_STRING, PHONE_ID);
+        // CODE
+        assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
+        // LABEL
+        safeAssertLabel(null, tcCause);
+        // TONE
+        assertEquals(TONE_PROP_PROMPT, tcCause.getTone());
+    }
+
+    /**
+     *  Simulate a Carrier classifying the DisconnectCause.ERROR_UNSPECIFIED as a
+     *  DisconnectCause.BUSY.  The code, label, and tone should match DisconnectCause.BUSY.
+     */
+    @Test
+    public void testCarrierSetDisconnectCauseInBusyToneArray() {
+        int[] carrierBusyArr = {DisconnectCause.BUSY, DisconnectCause.ERROR_UNSPECIFIED};
+        PersistableBundle config = new PersistableBundle();
+
+        config.putIntArray(
+                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+                carrierBusyArr);
+
+        android.telecom.DisconnectCause tcCause =
+                DisconnectCauseUtil.toTelecomDisconnectCause(
+                        DisconnectCause.ERROR_UNSPECIFIED, -1,
+                        EMPTY_STRING, PHONE_ID, null, config);
+
+        // CODE
+        assertEquals(android.telecom.DisconnectCause.BUSY, tcCause.getCode());
+        // LABEL
+        safeAssertLabel(R.string.callFailed_userBusy, tcCause);
+        // TONE
+        assertEquals(TONE_SUP_BUSY, tcCause.getTone());
+    }
+
+    private void setSinglePhone() throws Exception {
+        Phone[] mPhones = new Phone[]{mMockPhone};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+    }
+
+
+    protected synchronized void replaceInstance(final Class c, final String instanceName,
+            final Object obj, final Object newValue)
+            throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+
+        InstanceKey key = new InstanceKey(c, instanceName, obj);
+        if (!mOldInstances.containsKey(key)) {
+            mOldInstances.put(key, field.get(obj));
+            mInstanceKeys.add(key);
+        }
+        field.set(obj, newValue);
+    }
+
+    protected synchronized void restoreInstance(final Class c, final String instanceName,
+            final Object obj) throws Exception {
+        InstanceKey key = new InstanceKey(c, instanceName, obj);
+        if (mOldInstances.containsKey(key)) {
+            Field field = c.getDeclaredField(instanceName);
+            field.setAccessible(true);
+            field.set(obj, mOldInstances.get(key));
+            mOldInstances.remove(key);
+            mInstanceKeys.remove(key);
+        }
+    }
+
+    private Resources getResourcesForLocale(Context context, Locale locale) {
+        Configuration config = new Configuration();
+        config.setToDefaults();
+        config.setLocale(locale);
+        Context localeContext = context.createConfigurationContext(config);
+        return localeContext.getResources();
+    }
+
+    private void safeAssertLabel(Integer resourceId,
+            android.telecom.DisconnectCause disconnectCause) {
+        Resources r = getResourcesForLocale(mContext, Locale.US);
+        if (resourceId == null || r == null) {
+            return;
+        }
+        String label = r.getString(resourceId);
+        assertEquals(label, disconnectCause.getLabel());
+    }
+
+    /**
+     * Verifies that an ICC_ERROR disconnect cause generates a message which mentions there is no
+     * SIM.
+     */
+    @Test
+    public void testIccError() {
+        android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                DisconnectCause.ICC_ERROR);
+        assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
+        assertNotNull(tcCause.getLabel());
+        assertNotNull(tcCause.getDescription());
+    }
 }