Merge "Catch SecurityException instead Exception"
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index 57c1787..edad754 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -68,6 +68,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -143,6 +144,10 @@
     private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
     private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
 
+    // Container to store ImsManager instance by subId
+    private final ConcurrentHashMap<Integer, ImsManager> mSubIdToImsManagerCache =
+            new ConcurrentHashMap<>();
+
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyRegistryManager mTelephonyRegistryManager;
     private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
@@ -282,6 +287,13 @@
             if (mSubId == subId) return;
             logd(mLogPrefix + "setSubId changed subId=" + subId);
 
+            // subId changed from valid to invalid
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                if (VDBG) logv(mLogPrefix + "setSubId remove ImsManager " + mSubId);
+                // remove ImsManager reference associated with subId
+                mSubIdToImsManagerCache.remove(mSubId);
+            }
+
             mSubId = subId;
         }
 
@@ -298,6 +310,12 @@
             mSubId = subId;
             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
 
+            // store ImsManager reference associated with subId
+            if (manager != null) {
+                if (VDBG) logv(mLogPrefix + "connectionReady add ImsManager " + subId);
+                mSubIdToImsManagerCache.put(subId, manager);
+            }
+
             mState = STATE_READY;
             mReason = AVAILABLE;
             mHasConfig = true;
@@ -311,6 +329,10 @@
             reason = convertReasonType(reason);
             if (mReason == reason) return;
 
+            // remove ImsManager reference associated with subId
+            if (VDBG) logv(mLogPrefix + "connectionUnavailable remove ImsManager " + mSubId);
+            mSubIdToImsManagerCache.remove(mSubId);
+
             connectionUnavailableInternal(reason);
         }
 
@@ -319,7 +341,7 @@
             mReason = reason;
 
             /* If having no IMS package for MMTEL,
-             * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+             * discard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
             if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
 
             onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
@@ -973,6 +995,19 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
     }
 
+    /**
+     * Get ImsManager reference associated with subId
+     *
+     * @param subId subscribe ID
+     * @return instance of ImsManager associated with subId, but if ImsService is not
+     * available return null
+     */
+    public ImsManager getImsManager(int subId) {
+        if (VDBG) logv("getImsManager subId = " + subId);
+
+        return mSubIdToImsManagerCache.get(subId);
+    }
+
     private void removeInactiveCallbacks(
             ArrayList<IBinder> inactiveCallbacks, String message) {
         if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index f2641a1..27e1606 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;
@@ -57,8 +59,10 @@
 import android.util.SparseArray;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
 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;
@@ -163,7 +167,8 @@
      * Private constructor (this is a singleton).
      * @see #init(PhoneGlobals)
      */
-    private NotificationMgr(PhoneGlobals app) {
+    @VisibleForTesting
+    /* package */ NotificationMgr(PhoneGlobals app) {
         mApp = app;
         mContext = app;
         mStatusBarManager =
@@ -895,15 +900,22 @@
         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("Carrier configs refuse to show network selection not available notification");
+            return;
+        }
+
+        // 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.
         if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
             if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
                     != ServiceState.STATE_OUT_OF_SERVICE) {
@@ -930,6 +942,113 @@
         }
     }
 
+    // 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 present 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.
+
     private void startPendingNetworkSelectionNotification(int subId) {
         if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
             if (DBG) {
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 84dd722..0ab3fad 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -4135,7 +4135,18 @@
         try {
             int slotId = getSlotIndexOrException(subId);
             verifyImsMmTelConfiguredOrThrow(slotId);
-            ImsManager.getInstance(mApp, slotId).addRegistrationCallbackForSubscription(c, subId);
+
+            ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+            if (controller != null) {
+                ImsManager imsManager = controller.getImsManager(subId);
+                if (imsManager != null) {
+                    imsManager.addRegistrationCallbackForSubscription(c, subId);
+                } else {
+                    throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+                }
+            } else {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+            }
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4156,14 +4167,20 @@
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
         }
         final long token = Binder.clearCallingIdentity();
+
         try {
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .removeRegistrationCallbackForSubscription(c, subId);
-        } catch (ImsException e) {
-            Log.i(LOG_TAG, "unregisterImsRegistrationCallback: " + subId
-                    + "is inactive, ignoring unregister.");
-            // If the subscription is no longer active, just return, since the callback
-            // will already have been removed internally.
+            ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+            if (controller != null) {
+                ImsManager imsManager = controller.getImsManager(subId);
+                if (imsManager != null) {
+                    imsManager.removeRegistrationCallbackForSubscription(c, subId);
+                } else {
+                    Log.i(LOG_TAG, "unregisterImsRegistrationCallback: " + subId
+                            + "is inactive, ignoring unregister.");
+                    // If the ImsManager is not valid, just return, since the callback
+                    // will already have been removed internally.
+                }
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -8184,8 +8201,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);
@@ -9322,9 +9338,11 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             for (Phone phone: PhoneFactory.getPhones()) {
+                //Note: we ignore passed in param exactMatch. We can remove it once
+                // TelephonyManager#isPotentialEmergencyNumber is removed completely
                 if (phone.getEmergencyNumberTracker() != null
                         && phone.getEmergencyNumberTracker()
-                                .isEmergencyNumber(number, exactMatch)) {
+                                .isEmergencyNumber(number)) {
                     return true;
                 }
             }
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 1ed0d72..a948d08 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -828,7 +828,7 @@
 
     private void onConfigReceived(int subId, byte[] config, boolean isCompressed) {
         logv("onConfigReceived, subId:" + subId + ", config:"
-                + config + ", isCompressed:" + isCompressed);
+                + Arrays.toString(config) + ", isCompressed:" + isCompressed);
         RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
         if (info == null) {
             logd("sub[" + subId + "] has been removed");
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 97676fc..2c13060 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -63,6 +63,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
@@ -167,6 +168,13 @@
     private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
     private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
 
+    private static final String INVALID_ENTRY_ERROR = "An emergency number (only allow '0'-'9', "
+            + "'*', '#' or '+') needs to be specified after -a in the command ";
+
+    private static final int[] ROUTING_TYPES = {EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL};
+
     private static final String GET_ALLOWED_NETWORK_TYPES_FOR_USER =
             "get-allowed-network-types-for-users";
     private static final String SET_ALLOWED_NETWORK_TYPES_FOR_USER =
@@ -789,6 +797,24 @@
         return 0;
     }
 
+    private void removeEmergencyNumberTestMode(String emergencyNumber) {
+        PrintWriter errPw = getErrPrintWriter();
+        for (int routingType : ROUTING_TYPES) {
+            try {
+                mInterface.updateEmergencyNumberListTestMode(
+                        EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE,
+                        new EmergencyNumber(emergencyNumber, "", "",
+                                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                                new ArrayList<String>(),
+                                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
+                                routingType));
+            } catch (RemoteException ex) {
+                Log.w(LOG_TAG, "emergency-number-test-mode " + "error " + ex.getMessage());
+                errPw.println("Exception: " + ex.getMessage());
+            }
+        }
+    }
+
     private int handleEmergencyNumberTestModeCommand() {
         PrintWriter errPw = getErrPrintWriter();
         String opt = getNextOption();
@@ -796,26 +822,52 @@
             onHelpEmergencyNumber();
             return 0;
         }
-
         switch (opt) {
             case "-a": {
                 String emergencyNumberCmd = getNextArgRequired();
-                if (emergencyNumberCmd == null
-                        || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) {
-                    errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs"
-                            + " to be specified after -a in the command ");
+                if (emergencyNumberCmd == null){
+                    errPw.println(INVALID_ENTRY_ERROR);
                     return -1;
                 }
+                String[] params = emergencyNumberCmd.split(":");
+                String emergencyNumber;
+                if (params[0] == null ||
+                        !EmergencyNumber.validateEmergencyNumberAddress(params[0])){
+                    errPw.println(INVALID_ENTRY_ERROR);
+                    return -1;
+                } else {
+                    emergencyNumber = params[0];
+                }
+                removeEmergencyNumberTestMode(emergencyNumber);
+                int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+                if (params.length > 1) {
+                    switch (params[1].toLowerCase(Locale.ROOT)) {
+                        case "emergency":
+                            emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY;
+                            break;
+                        case "normal":
+                            emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+                            break;
+                        case "unknown":
+                            break;
+                        default:
+                            errPw.println("\"" + params[1] + "\" is not a valid specification for "
+                                    + "emergency call routing. Please enter either \"normal\", "
+                                    + "\"unknown\", or \"emergency\" for call routing. "
+                                    + "(-a 1234:normal)");
+                            return -1;
+                    }
+                }
                 try {
                     mInterface.updateEmergencyNumberListTestMode(
                             EmergencyNumberTracker.ADD_EMERGENCY_NUMBER_TEST_MODE,
-                            new EmergencyNumber(emergencyNumberCmd, "", "",
+                            new EmergencyNumber(emergencyNumber, "", "",
                                     EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
                                     new ArrayList<String>(),
                                     EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
-                                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+                                    emergencyCallRouting));
                 } catch (RemoteException ex) {
-                    Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumberCmd
+                    Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumber
                             + ", error " + ex.getMessage());
                     errPw.println("Exception: " + ex.getMessage());
                     return -1;
@@ -841,20 +893,7 @@
                             + " to be specified after -r in the command ");
                     return -1;
                 }
-                try {
-                    mInterface.updateEmergencyNumberListTestMode(
-                            EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE,
-                            new EmergencyNumber(emergencyNumberCmd, "", "",
-                                    EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
-                                    new ArrayList<String>(),
-                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
-                                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
-                } catch (RemoteException ex) {
-                    Log.w(LOG_TAG, "emergency-number-test-mode -r " + emergencyNumberCmd
-                            + ", error " + ex.getMessage());
-                    errPw.println("Exception: " + ex.getMessage());
-                    return -1;
-                }
+                removeEmergencyNumberTestMode(emergencyNumberCmd);
                 break;
             }
             case "-p": {
diff --git a/src/com/android/phone/settings/VoicemailProviderSettings.java b/src/com/android/phone/settings/VoicemailProviderSettings.java
index fc2e7f8..10f0ddb 100644
--- a/src/com/android/phone/settings/VoicemailProviderSettings.java
+++ b/src/com/android/phone/settings/VoicemailProviderSettings.java
@@ -21,6 +21,8 @@
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CommandsInterface;
 
+import java.util.Arrays;
+
 /**
  * Settings for a voicemail provider, including any conditional forwarding information.
  */
@@ -88,7 +90,7 @@
     @Override
     public String toString() {
         return mVoicemailNumber + ((mForwardingSettings == null) ? ""
-                : ", " + mForwardingSettings.toString());
+                : ", " + Arrays.toString(mForwardingSettings));
     }
 
     public String getVoicemailNumber() {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index f1c0383..1e4bc1a 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -2105,7 +2105,7 @@
         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
             if (phone.getEmergencyNumberTracker() != null) {
                 if (phone.getEmergencyNumberTracker().isEmergencyNumber(
-                        emergencyNumberAddress, true)) {
+                        emergencyNumberAddress)) {
                     if (isAvailableForEmergencyCalls(phone)) {
                         // a)
                         if (phone.getPhoneId() == defaultVoicePhoneId) {
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
index cb4321c..60374bc 100644
--- a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -28,6 +28,8 @@
 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.any;
@@ -874,6 +876,36 @@
         assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
     }
 
+    @Test
+    @SmallTest
+    public void testImsManagerInstance() throws Exception {
+        createController(1);
+
+        // MmTelConnection not ready
+        // check ImsManager instance
+        ImsManager imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNull(imsManager);
+
+        // MmTelConnection ready
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionReady(mMmTelFeatureManager, SLOT_0_SUB_ID);
+        processAllMessages();
+
+        // check ImsManager instance
+        imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNotNull(imsManager);
+
+        // MmTelConnection unavailable
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+
+        // MmTelConnection unavailable
+        // check ImsManager instance
+        imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNull(imsManager);
+    }
+
     private void createController(int slotCount) throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
diff --git a/tests/src/com/android/phone/NotificationMgrTest.java b/tests/src/com/android/phone/NotificationMgrTest.java
new file mode 100644
index 0000000..a6ee276
--- /dev/null
+++ b/tests/src/com/android/phone/NotificationMgrTest.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static android.telephony.RadioAccessFamily.RAF_1xRTT;
+import static android.telephony.RadioAccessFamily.RAF_EDGE;
+import static android.telephony.RadioAccessFamily.RAF_EHRPD;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_0;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_A;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_B;
+import static android.telephony.RadioAccessFamily.RAF_GPRS;
+import static android.telephony.RadioAccessFamily.RAF_GSM;
+import static android.telephony.RadioAccessFamily.RAF_HSDPA;
+import static android.telephony.RadioAccessFamily.RAF_HSPA;
+import static android.telephony.RadioAccessFamily.RAF_HSPAP;
+import static android.telephony.RadioAccessFamily.RAF_HSUPA;
+import static android.telephony.RadioAccessFamily.RAF_IS95A;
+import static android.telephony.RadioAccessFamily.RAF_IS95B;
+import static android.telephony.RadioAccessFamily.RAF_LTE;
+import static android.telephony.RadioAccessFamily.RAF_LTE_CA;
+import static android.telephony.RadioAccessFamily.RAF_TD_SCDMA;
+import static android.telephony.RadioAccessFamily.RAF_UMTS;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static com.android.phone.NotificationMgr.DATA_ROAMING_NOTIFICATION;
+import static com.android.phone.NotificationMgr.LIMITED_SIM_FUNCTION_NOTIFICATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.util.NotificationChannelController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unit Test for NotificationMgr
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationMgrTest {
+
+    private static final int TEST_SUB_ID = 1;
+    private static final long SERIAL_NUMBER_OF_USER = 1234567L;
+    private static final String TEST_LABEL_CF = "test_call_forwarding";
+    private static final String TEST_SUB_INFO_DISPLAY_NAME = "display_name";
+    private static final String TEST_PACKAGE_NAME = "com.android.phone";
+    private static final String TEST_SELECTED_NETWORK_OPERATOR_NAME = "TheOperator";
+    private static final String MOBILE_NETWORK_SELECTION_PACKAGE = "com.android.phone";
+    private static final String MOBILE_NETWORK_SELECTION_CLASS = ".testClass";
+    private static final String CARRIER_NAME = "CoolCarrier";
+
+    @Mock PhoneGlobals mApp;
+    @Mock StatusBarManager mStatusBarManager;
+    @Mock UserManager mUserManager;
+    @Mock SubscriptionManager mSubscriptionManager;
+    @Mock TelecomManager mTelecomManager;
+    @Mock TelephonyManager mTelephonyManager;
+    @Mock Phone mPhone;
+    @Mock SharedPreferences mSharedPreferences;
+    @Mock NotificationManager mNotificationManager;
+    @Mock SubscriptionInfo mSubscriptionInfo;
+    @Mock Resources mResources;
+    @Mock ServiceState mServiceState;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+
+    private Phone[] mPhones;
+    private NotificationMgr mNotificationMgr;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mPhones = new Phone[]{mPhone};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        when(mApp.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
+
+        when(mApp.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mApp.getSystemService(Context.STATUS_BAR_SERVICE)).thenReturn(mStatusBarManager);
+        when(mApp.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mApp.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
+                mSubscriptionManager);
+        when(mApp.getSystemServiceName(TelecomManager.class)).thenReturn(Context.TELECOM_SERVICE);
+        when(mApp.getSystemService(TelecomManager.class)).thenReturn(mTelecomManager);
+        when(mApp.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mApp.getSystemServiceName(CarrierConfigManager.class)).thenReturn(
+                Context.CARRIER_CONFIG_SERVICE);
+        when(mApp.getSystemService(CarrierConfigManager.class)).thenReturn(mCarrierConfigManager);
+        when(mApp.getSystemServiceName(CarrierConfigManager.class)).thenReturn(
+                Context.CARRIER_CONFIG_SERVICE);
+        when(mApp.getSystemService(CarrierConfigManager.class)).thenReturn(mCarrierConfigManager);
+
+        when(mApp.createPackageContextAsUser(any(), eq(0), any())).thenReturn(mApp);
+        when(mApp.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);
+        when(mUserManager.getSerialNumbersOfUsers(true)).thenReturn(
+                new long[]{SERIAL_NUMBER_OF_USER});
+        when(mUserManager.getUserForSerialNumber(eq(SERIAL_NUMBER_OF_USER))).thenReturn(
+                UserHandle.SYSTEM);
+        when(mApp.getResources()).thenReturn(mResources);
+        when(mResources.getString(R.string.labelCF)).thenReturn(TEST_LABEL_CF);
+        ApplicationInfo appWithSdkS = buildApplicationInfo(Build.VERSION_CODES.S);
+        when(mApp.getApplicationInfo()).thenReturn(appWithSdkS);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getServiceState()).thenReturn(mServiceState);
+
+        mNotificationMgr = new NotificationMgr(mApp);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_noActiveSubscription_notificationNeverSent()
+            throws Exception {
+        // Given no active subscription available
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(null);
+
+        // When updateCfi method is called
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        // Then the notification should never be sent
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_singleSIM_notificationSent() throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(1);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_multiSIM_notificationSentWithoutDisplayName()
+            throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(2);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+        when(mSubscriptionInfo.getDisplayName()).thenReturn(null);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_multiSIM_notificationSentWithDisplayName()
+            throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(2);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+        when(mSubscriptionInfo.getDisplayName()).thenReturn(TEST_SUB_INFO_DISPLAY_NAME);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_invisible_hasUnmanagedProfile_notificationCanceled()
+            throws Exception {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/false, /*isFresh=*/false);
+
+        verify(mNotificationManager).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testUpdateCfi_invisible_allProfilesAreManaged_notificationNeverCanceled()
+            throws Exception {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/false, /*isFresh=*/false);
+
+        verify(mNotificationManager, never()).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testShowDataRoamingNotification_roamingOn() throws Exception {
+        mNotificationMgr.showDataRoamingNotification(TEST_SUB_ID, /*roamingOn=*/true);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS);
+    }
+
+    @Test
+    public void testShowDataRoamingNotification_roamingOff() throws Exception {
+        mNotificationMgr.showDataRoamingNotification(TEST_SUB_ID, /*roamingOn=*/false);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS);
+    }
+
+    @Test
+    public void testHideDataRoamingNotification() {
+        mNotificationMgr.hideDataRoamingNotification();
+
+        verify(mNotificationManager).cancel(any(), eq(DATA_ROAMING_NOTIFICATION));
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_justOutOfService_notificationNeverSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(2000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_oosEnoughTime_selectionVisibleToUser_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(true);
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        // TODO: use effective TestLooper time eclipse instead of sleeping
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_invalidSubscription_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(true);
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE,
+                INVALID_SUBSCRIPTION_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE,
+                INVALID_SUBSCRIPTION_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_nullCarrierConfig_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(null);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_userNotAllowedToChooseOperator_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        // User is NOT allowed to choose operator
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_OverrideHideCarrierNetworkSelection_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        // Hide network selection menu
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_simPreventManualSelection_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        // SIM card can prevent manual network selection which is forbidden
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, true);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetLTE_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as LTE
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_IS95A | RAF_IS95B | RAF_1xRTT | RAF_EVDO_0
+                        | RAF_EVDO_A | RAF_EVDO_B | RAF_EHRPD));
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetTDSCDMA_notSupported_notifNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as NETWORK_MODE_LTE_TDSCDMA_GSM
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_TD_SCDMA | RAF_GSM | RAF_GPRS | RAF_EDGE));
+        // But TDSCDMA is NOT supported
+        config.putBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetWCDMA_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as NETWORK_MODE_LTE_TDSCDMA_GSM
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_GSM | RAF_GPRS | RAF_EDGE | RAF_HSUPA | RAF_HSDPA
+                        | RAF_HSPA | RAF_HSPAP | RAF_UMTS));
+        // But TDSCDMA is NOT supported
+        config.putBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldPhone_networkSelectionNotHide_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        // World phone is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_gsmBasicOptionOn_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World phone is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_GSM);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_gsmBasicOptionOff_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+        when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testShowLimitedSimFunctionWarningNotification_forTheFirstTime_notificationSent()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY);
+    }
+
+    @Test
+    public void testShowLimitedSimFunctionWarningNotification_consecutiveCall_notificationSentOnce()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+
+        // Call the method TWICE with the same subscription
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        // Verify the notification is only sent ONCE
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY);
+    }
+
+    @Test
+    public void testDismissLimitedSimFunctionWarningNotification_noShowCalledBefore_noCancelSent()
+            throws Exception {
+        // showLimitedSimFunctionWarningNotification was never called before
+
+        mNotificationMgr.dismissLimitedSimFunctionWarningNotification(TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testDismissLimitedSimFunctionWarningNotification_showCalledBefore_cancelSent()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        mNotificationMgr.dismissLimitedSimFunctionWarningNotification(TEST_SUB_ID);
+
+        verify(mNotificationManager).cancel(any(), eq(LIMITED_SIM_FUNCTION_NOTIFICATION));
+    }
+
+    private ApplicationInfo buildApplicationInfo(int targetSdkVersion) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = targetSdkVersion;
+        return applicationInfo;
+    }
+
+    private void verifyNotificationSentWithChannelId(String expectedNotificationChannelId) {
+        ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
+                Notification.class);
+        verify(mNotificationManager).notify(any(), anyInt(), notificationArgumentCaptor.capture());
+        Notification capturedNotification = notificationArgumentCaptor.getAllValues().get(0);
+        assertThat(capturedNotification.getChannelId()).isEqualTo(expectedNotificationChannelId);
+    }
+
+    private void prepareResourcesForNetworkSelection() {
+        when(mSharedPreferences.getString(Phone.NETWORK_SELECTION_NAME_KEY + TEST_SUB_ID,
+                "")).thenReturn(TEST_SELECTED_NETWORK_OPERATOR_NAME);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.skip_restoring_network_selection)).thenReturn(false);
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mApp.getString(R.string.mobile_network_settings_package)).thenReturn(
+                MOBILE_NETWORK_SELECTION_PACKAGE);
+        when(mApp.getString(R.string.mobile_network_settings_class)).thenReturn(
+                MOBILE_NETWORK_SELECTION_CLASS);
+    }
+
+    private static void replaceInstance(final Class c,
+            final String instanceName, final Object obj, final Object newValue) throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+        field.set(obj, newValue);
+    }
+}