Merge "Add required flag to registerReceiver calls"
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index 02530da..972884a 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -55,6 +55,7 @@
     protected RegistrantList mNetworkStateRegistrants = new RegistrantList();
     protected RegistrantList mDataCallListChangedRegistrants = new RegistrantList();
     protected RegistrantList mApnUnthrottledRegistrants = new RegistrantList();
+    protected RegistrantList mSlicingConfigChangedRegistrants = new RegistrantList();
     @UnsupportedAppUsage
     protected RegistrantList mVoiceRadioTechChangedRegistrants = new RegistrantList();
     @UnsupportedAppUsage
@@ -319,6 +320,16 @@
     }
 
     @Override
+    public void registerForSlicingConfigChanged(Handler h, int what, Object obj) {
+        mSlicingConfigChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSlicingConfigChanged(Handler h) {
+        mSlicingConfigChangedRegistrants.remove(h);
+    }
+
+    @Override
     public void registerForVoiceRadioTechChanged(Handler h, int what, Object obj) {
         mVoiceRadioTechChangedRegistrants.addUnique(h, what, obj);
     }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index b09123c..3d3fda4 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -228,6 +228,10 @@
     void registerForApnUnthrottled(Handler h, int what, Object obj);
     /** Unregister for apn unthrottled event */
     void unregisterForApnUnthrottled(Handler h);
+    /** Register for the slicing config changed event */
+    void registerForSlicingConfigChanged(Handler h, int what, Object obj);
+    /** Unregister for slicing config changed event */
+    void unregisterForSlicingConfigChanged(Handler h);
 
     /** InCall voice privacy notifications */
     void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj);
diff --git a/src/java/com/android/internal/telephony/DataIndication.java b/src/java/com/android/internal/telephony/DataIndication.java
index dd1fb8e..a87aa2a 100644
--- a/src/java/com/android/internal/telephony/DataIndication.java
+++ b/src/java/com/android/internal/telephony/DataIndication.java
@@ -19,6 +19,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DATA_CALL_LIST_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PCO_DATA;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SLICING_CONFIG_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UNTHROTTLE_APN;
 
 import android.hardware.radio.data.IRadioDataIndication;
@@ -27,6 +28,7 @@
 import android.telephony.PcoData;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
+import android.telephony.data.NetworkSlicingConfig;
 
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 
@@ -107,4 +109,18 @@
 
         mRil.mApnUnthrottledRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
+
+    /**
+     * Current slicing configuration including URSP rules and NSSAIs
+     * (configured, allowed and rejected).
+     * @param indicationType Type of radio indication
+     * @param slicingConfig Current slicing configuration
+     */
+    public void slicingConfigChanged(int indicationType,
+            android.hardware.radio.data.SlicingConfig slicingConfig) throws RemoteException {
+        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SLICING_CONFIG_CHANGED, slicingConfig);
+        NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig);
+        mRil.mApnUnthrottledRegistrants.notifyRegistrants(new AsyncResult(null, ret, null));
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index eeed088..be42c9c 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -905,6 +905,19 @@
     @Override
     public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
             String callingFeatureId) {
+        return getAllSubInfoList(callingPackage, callingFeatureId, false);
+    }
+
+    /**
+     * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
+     * @param skipConditionallyRemoveIdentifier if set, skip removing identifier conditionally
+     * @return List of all SubscriptionInfo records in database,
+     * include those that were inserted before, maybe empty but not null.
+     * @hide
+     */
+    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
+            String callingFeatureId, boolean skipConditionallyRemoveIdentifier) {
         if (VDBG) logd("[getAllSubInfoList]+");
 
         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
@@ -923,9 +936,9 @@
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        if (subList != null) {
+        if (subList != null && !skipConditionallyRemoveIdentifier) {
             if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
-            subList.stream().map(
+            subList = subList.stream().map(
                     subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
                             callingPackage, callingFeatureId, "getAllSubInfoList"))
                     .collect(Collectors.toList());
@@ -3903,8 +3916,10 @@
         List<SubscriptionInfo> subInfoList;
 
         try {
+            // need to bypass removing identifier check because that will remove the subList without
+            // group id.
             subInfoList = getAllSubInfoList(mContext.getOpPackageName(),
-                    mContext.getAttributionTag());
+                    mContext.getAttributionTag(), true);
             if (groupUuid == null || subInfoList == null || subInfoList.isEmpty()) {
                 return new ArrayList<>();
             }
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index 364b18d..40b2713 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -249,7 +249,8 @@
 
             filter.addAction(ACTION_TEST_CHANGE_NUMBER);
             log("register for intent action=" + ACTION_TEST_CHANGE_NUMBER);
-            phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
+            phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler(),
+                    Context.RECEIVER_EXPORTED);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index fd604a0..eb5f866 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -141,13 +141,15 @@
                 sTestBroadcastReceiver = new CdmaCbTestBroadcastReceiver();
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(TEST_ACTION);
-                context.registerReceiver(sTestBroadcastReceiver, filter);
+                context.registerReceiver(sTestBroadcastReceiver, filter,
+                        Context.RECEIVER_EXPORTED);
             }
             if (sTestScpBroadcastReceiver == null) {
                 sTestScpBroadcastReceiver = new CdmaScpTestBroadcastReceiver();
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(SCP_TEST_ACTION);
-                context.registerReceiver(sTestScpBroadcastReceiver, filter);
+                context.registerReceiver(sTestScpBroadcastReceiver, filter,
+                        Context.RECEIVER_EXPORTED);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
index f4b26b6..11a0ae6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
@@ -78,7 +78,8 @@
             filter.addAction(mPhone.getActionDetached());
             log("register for intent action=" + mPhone.getActionDetached());
 
-            phone.getContext().registerReceiver(sIntentReceiver, filter, null, handler);
+            phone.getContext().registerReceiver(sIntentReceiver, filter, null, handler,
+                    Context.RECEIVER_EXPORTED);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
index ba07e12..788da29 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
@@ -89,7 +89,8 @@
             filter.addAction(mPhone.getActionAttached());
             log("register for intent action=" + mPhone.getActionAttached());
 
-            phone.getContext().registerReceiver(mIntentReceiver, filter, null, handler);
+            phone.getContext().registerReceiver(mIntentReceiver, filter, null, handler,
+                    Context.RECEIVER_EXPORTED);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 86e6463..0abd4ab 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -67,7 +67,8 @@
                 sTestBroadcastReceiver = new GsmCbTestBroadcastReceiver();
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(TEST_ACTION);
-                context.registerReceiver(sTestBroadcastReceiver, filter);
+                context.registerReceiver(sTestBroadcastReceiver, filter,
+                        Context.RECEIVER_EXPORTED);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index 8ba390b..ebb4ffc 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
@@ -36,6 +37,7 @@
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsServiceController;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
@@ -57,10 +59,33 @@
     private final SparseArray<ImsRegistrationCompatAdapter> mRegCompatAdapters =
             new SparseArray<>();
 
+    private final MmTelFeatureCompatFactory mMmTelFeatureFactory;
+
+    /**
+     * Used to inject test instances of MmTelFeatureCompatAdapter
+     */
+    @VisibleForTesting
+    public interface MmTelFeatureCompatFactory {
+        /**
+         * @return A new instance of {@link MmTelFeatureCompatAdapter}
+         */
+        MmTelFeatureCompatAdapter create(Context context, int slotId,
+                MmTelInterfaceAdapter compatFeature);
+    }
+
     public ImsServiceControllerCompat(Context context, ComponentName componentName,
             ImsServiceController.ImsServiceControllerCallbacks callbacks,
             ImsFeatureBinderRepository repo) {
         super(context, componentName, callbacks, repo);
+        mMmTelFeatureFactory = MmTelFeatureCompatAdapter::new;
+    }
+
+    @VisibleForTesting
+    public ImsServiceControllerCompat(Context context, ComponentName componentName,
+            ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry,
+            ImsFeatureBinderRepository repo, MmTelFeatureCompatFactory factory) {
+        super(context, componentName, callbacks, handler, rebindRetry, repo);
+        mMmTelFeatureFactory = factory;
     }
 
     @Override
@@ -186,6 +211,10 @@
     protected final void removeImsFeature(int slotId, int featureType)
             throws RemoteException {
         if (featureType == ImsFeature.MMTEL) {
+            MmTelFeatureCompatAdapter adapter = mMmTelCompatAdapters.get(slotId, null);
+            // Need to manually call onFeatureRemoved here, since this is normally called by the
+            // ImsService itself.
+            if (adapter != null) adapter.onFeatureRemoved();
             mMmTelCompatAdapters.remove(slotId);
             mRegCompatAdapters.remove(slotId);
             mConfigCompatAdapters.remove(slotId);
@@ -218,7 +247,7 @@
     private IImsMmTelFeature createMMTelCompat(int slotId)
             throws RemoteException {
         MmTelInterfaceAdapter interfaceAdapter = getInterface(slotId);
-        MmTelFeatureCompatAdapter mmTelAdapter = new MmTelFeatureCompatAdapter(mContext, slotId,
+        MmTelFeatureCompatAdapter mmTelAdapter = mMmTelFeatureFactory.create(mContext, slotId,
                 interfaceAdapter);
         mMmTelCompatAdapters.put(slotId, mmTelAdapter);
         ImsRegistrationCompatAdapter regAdapter = new ImsRegistrationCompatAdapter();
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
old mode 100755
new mode 100644
index cff9e41..c76a649
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1542,6 +1542,9 @@
             mUssdMethod = carrierConfig.getInt(CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT);
         }
 
+        if (!mImsReasonCodeMap.isEmpty()) {
+            mImsReasonCodeMap.clear();
+        }
         String[] mappings = carrierConfig
                 .getStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY);
         if (mappings != null && mappings.length > 0) {
@@ -1562,21 +1565,22 @@
                     if (message == null) {
                         message = "";
                     }
+                    else if (message.equals("*")) {
+                        message = null;
+                    }
                     int toCode = Integer.parseInt(values[2]);
 
                     addReasonCodeRemapping(fromCode, message, toCode);
-                    log("Loaded ImsReasonInfo mapping : fromCode = " +
-                            fromCode == null ? "any" : fromCode + " ; message = " +
-                            message + " ; toCode = " + toCode);
+                    log("Loaded ImsReasonInfo mapping :" +
+                            " fromCode = " + (fromCode == null ? "any" : fromCode) +
+                            " ; message = " + (message == null ? "any" : message) +
+                            " ; toCode = " + toCode);
                 } catch (NumberFormatException nfe) {
                     loge("Invalid ImsReasonInfo mapping found: " + mapping);
                 }
             }
         } else {
             log("No carrier ImsReasonInfo mappings defined.");
-            if (!mImsReasonCodeMap.isEmpty()) {
-                mImsReasonCodeMap.clear();
-            }
         }
     }
 
@@ -2665,6 +2669,7 @@
                 + reason);
         Pair<Integer, String> toCheck = new Pair<>(code, reason);
         Pair<Integer, String> wildcardToCheck = new Pair<>(null, reason);
+        Pair<Integer, String> wildcardMessageToCheck = new Pair<>(code, null);
         if (mImsReasonCodeMap.containsKey(toCheck)) {
             int toCode = mImsReasonCodeMap.get(toCheck);
 
@@ -2682,6 +2687,19 @@
                     " ; message = " + reason + " ; toCode = " + toCode);
             return toCode;
         }
+        else if (mImsReasonCodeMap.containsKey(wildcardMessageToCheck)) {
+            // Handle the case where a wildcard is specified for the reason.
+            // For example, we can set these two strings in
+            // CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY:
+            //   - "1014|call completed elsewhere|1014"
+            //   - "1014|*|510"
+            // to remap CODE_ANSWERED_ELSEWHERE to CODE_USER_TERMINATED_BY_REMOTE
+            // when reason is NOT "call completed elsewhere".
+            int toCode = mImsReasonCodeMap.get(wildcardMessageToCheck);
+            log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() +
+                    " ; message(wildcard) = " + reason + " ; toCode = " + toCode);
+            return toCode;
+        }
         return code;
     }
 
@@ -3128,7 +3146,9 @@
                     getNetworkCountryIso(), emergencyNumberTracker != null
                     ? emergencyNumberTracker.getEmergencyNumberDbVersion()
                     : TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION);
-            mPhone.getVoiceCallSessionStats().onImsCallTerminated(conn, reasonInfo);
+            mPhone.getVoiceCallSessionStats().onImsCallTerminated(conn, new ImsReasonInfo(
+                    maybeRemapReasonCode(reasonInfo),
+                    reasonInfo.mExtraCode, reasonInfo.mExtraMessage));
             // Remove info for the callId from the current calls and add it to the history
             CallQualityMetrics lastCallMetrics = mCallQualityMetrics.remove(callId);
             if (lastCallMetrics != null) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 14f5aa1..3137f74 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -392,6 +392,17 @@
             return registerReceiverFakeImpl(receiver, filter);
         }
 
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+            return registerReceiverFakeImpl(receiver, filter);
+        }
+
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler, int flags) {
+            return registerReceiverFakeImpl(receiver, filter);
+        }
+
         private Intent registerReceiverFakeImpl(BroadcastReceiver receiver, IntentFilter filter) {
             Intent result = null;
             synchronized (mBroadcastReceiversByAction) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index e9c74ae..03d3459 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -1525,4 +1525,12 @@
     @Override
     public void unregisterForSimPhonebookRecordsReceived(Handler h){
     }
+
+    @Override
+    public void registerForSlicingConfigChanged(Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void unregisterForSlicingConfigChanged(Handler h) {
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
new file mode 100644
index 0000000..30fc477
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.internal.telephony.ims;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.ims.ImsFeatureBinderRepository;
+import com.android.ims.ImsFeatureContainer;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsServiceController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsServiceControllerCompatTest extends ImsTestBase {
+
+    private static final int SLOT_0 = 0;
+
+    private static final ImsServiceController.RebindRetry REBIND_RETRY =
+            new ImsServiceController.RebindRetry() {
+                @Override
+                public long getStartDelay() {
+                    return 50;
+                }
+
+                @Override
+                public long getMaximumDelay() {
+                    return 1000;
+                }
+            };
+
+    @Mock MmTelInterfaceAdapter mMockMmTelInterfaceAdapter;
+    @Mock IImsMMTelFeature mMockRemoteMMTelFeature;
+    @Mock IBinder mMockMMTelBinder;
+    @Mock IImsServiceController mMockServiceControllerBinder;
+    @Mock ImsServiceController.ImsServiceControllerCallbacks mMockCallbacks;
+    @Mock IImsConfig mMockImsConfig;
+    @Mock Context mMockContext;
+    private final ComponentName mTestComponentName = new ComponentName("TestPkg",
+            "ImsServiceControllerTest");
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private ImsServiceController mTestImsServiceController;
+    private ImsFeatureBinderRepository mRepo;
+    private MmTelFeatureCompatAdapter mMmTelCompatAdapterSpy;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mRepo = new ImsFeatureBinderRepository();
+        // Can't mock MmTelFeatureCompatAdapter due to final getBinder() method.
+        mMmTelCompatAdapterSpy = spy(new MmTelFeatureCompatAdapter(mMockContext, SLOT_0,
+                mMockMmTelInterfaceAdapter));
+        mTestImsServiceController = new ImsServiceControllerCompat(mMockContext, mTestComponentName,
+                mMockCallbacks, mHandler, REBIND_RETRY, mRepo,
+                (a, b, c) -> mMmTelCompatAdapterSpy);
+        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
+        when(mMockServiceControllerBinder.createMMTelFeature(anyInt()))
+                .thenReturn(mMockRemoteMMTelFeature);
+        when(mMockRemoteMMTelFeature.getConfigInterface()).thenReturn(mMockImsConfig);
+        when(mMockRemoteMMTelFeature.asBinder()).thenReturn(mMockMMTelBinder);
+    }
+
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        mTestImsServiceController.stopBackoffTimerForTesting();
+        mTestImsServiceController = null;
+        // Make sure the handler is empty before finishing the test.
+        waitForHandlerAction(mHandler, 1000);
+        super.tearDown();
+    }
+
+    /**
+     * Tests that the MmTelFeatureCompatAdapter is cleaned up properly and ImsServiceController
+     * callbacks are properly called when an ImsService is bound and then crashes.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndCrashCleanUp() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+        // add the MMTelFeature
+        verify(mMockServiceControllerBinder).createMMTelFeature(SLOT_0);
+        verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
+                eq(mTestImsServiceController));
+        validateMmTelFeatureContainerExists(SLOT_0);
+        // Remove the feature
+        conn.onBindingDied(mTestComponentName);
+        verify(mMmTelCompatAdapterSpy).onFeatureRemoved();
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL));
+        validateMmTelFeatureContainerDoesntExist(SLOT_0);
+    }
+
+    private void validateMmTelFeatureContainerExists(int slotId) {
+        ImsFeatureContainer fc =
+                mRepo.getIfExists(slotId, ImsFeature.FEATURE_MMTEL).orElse(null);
+        assertNotNull("MMTEL FeatureContainer should not be null", fc);
+        assertEquals("ImsServiceController did not report MmTelFeature to service repo correctly",
+                mMmTelCompatAdapterSpy.getBinder(), fc.imsFeature);
+        assertEquals(0, (android.telephony.ims.ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL
+                & fc.getCapabilities()));
+    }
+
+    private void validateMmTelFeatureContainerDoesntExist(int slotId) {
+        ImsFeatureContainer fc =
+                mRepo.getIfExists(slotId, ImsFeature.FEATURE_MMTEL).orElse(null);
+        assertNull("FeatureContainer should be null", fc);
+    }
+
+    private ServiceConnection bindAndConnectService() {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        ServiceConnection connection = bindService(testFeatures);
+        IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
+        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
+        connection.onServiceConnected(mTestComponentName, controllerStub);
+        return connection;
+    }
+
+    private ServiceConnection bindService(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+        ArgumentCaptor<ServiceConnection> serviceCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        assertTrue(mTestImsServiceController.bind(testFeatures));
+        verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
+        return serviceCaptor.getValue();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index d1826d5..d977a6f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -770,6 +770,71 @@
                 "Call answered elsewhere.")));
     }
 
+    private void clearCarrierConfig() {
+        PersistableBundle bundle = new PersistableBundle();
+        mCTUT.updateCarrierConfigCache(bundle);
+    }
+
+    private void loadReasonCodeRemapCarrierConfig() {
+        PersistableBundle bundle = new PersistableBundle();
+        String[] mappings = new String[] {
+                // These shall be equivalent to the remappings added in setUp():
+                "*|Wifi signal lost.|1407",
+                "501|Call answered elsewhere.|1014",
+                "510|Call answered elsewhere.|1014",
+                "510||332",
+                "352|emergency calls over wifi not allowed in this location|1622",
+                "332|service not allowed in this location|1623",
+                };
+        bundle.putStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY,
+                mappings);
+        mCTUT.updateCarrierConfigCache(bundle);
+    }
+
+    @Test
+    @SmallTest
+    public void testReasonCodeRemapCarrierConfig() {
+        clearCarrierConfig();
+        // The map shall become empty now
+
+        assertEquals(510, // ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE
+                mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "Call answered elsewhere.")));
+
+        loadReasonCodeRemapCarrierConfig();
+        testReasonCodeRemap();
+        testNumericOnlyRemap();
+        testRemapEmergencyCallsOverWfc();
+        testRemapWfcNotAvailable();
+    }
+
+    private void loadReasonCodeRemapCarrierConfigWithWildcardMessage() {
+        PersistableBundle bundle = new PersistableBundle();
+        String[] mappings = new String[]{
+                "1014|call completed elsewhere|1014",
+                "1014|*|510",
+                };
+        bundle.putStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY,
+                mappings);
+        mCTUT.updateCarrierConfigCache(bundle);
+    }
+
+    @Test
+    @SmallTest
+    public void testReasonCodeRemapCarrierConfigWithWildcardMessage() {
+        clearCarrierConfig();
+        // The map shall become empty now
+
+        loadReasonCodeRemapCarrierConfigWithWildcardMessage();
+        assertEquals(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, mCTUT.maybeRemapReasonCode(
+                new ImsReasonInfo(1014, 200, "Call Rejected By User"))); // 1014 -> 510
+        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, mCTUT.maybeRemapReasonCode(
+                new ImsReasonInfo(1014, 200, "Call completed elsewhere"))); // 1014 -> 1014
+
+        // Simulate that after SIM swap the new carrier config doesn't have the mapping for 1014
+        loadReasonCodeRemapCarrierConfig();
+        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, mCTUT.maybeRemapReasonCode(
+                new ImsReasonInfo(1014, 200, "Call Rejected By User"))); // 1014 -> 1014
+    }
 
     @Test
     @SmallTest