Merge "Do not destroy UCE when associated subId changes with the same subid" am: 7da1b8edbc

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

Change-Id: I1a214441d0d2d1713ed2e233f8e5198cc8e2ba06
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 5a1acb5..3eefdb0 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -20,7 +20,9 @@
 import android.content.Context;
 import android.net.Uri;
 import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -63,12 +65,16 @@
         void onRcsDisconnected();
 
         /**
-         * The subscription associated with the slot this controller is bound to has changed or its
-         * carrier configuration has changed.
+         * The subscription associated with the slot this controller is bound to has changed.
          */
         void onAssociatedSubscriptionUpdated(int subId);
 
         /**
+         * The carrier configuration associated with the active subscription id has changed.
+         */
+        void onCarrierConfigChanged();
+
+        /**
          * Called when the feature should be destroyed.
          */
         void onDestroy();
@@ -118,6 +124,7 @@
     private final Object mLock = new Object();
     private FeatureConnector<RcsFeatureManager> mFeatureConnector;
     private RcsFeatureManager mFeatureManager;
+    private int mAssociatedSubId;
 
     private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener =
             new FeatureConnector.Listener<RcsFeatureManager>() {
@@ -171,9 +178,10 @@
                 }
             };
 
-    public RcsFeatureController(Context context, int slotId) {
+    public RcsFeatureController(Context context, int slotId, int associatedSubId) {
         mContext = context;
         mSlotId = slotId;
+        mAssociatedSubId = associatedSubId;
         mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
                 mContext.getMainExecutor());
     }
@@ -182,9 +190,11 @@
      * Should only be used to inject registration helpers for testing.
      */
     @VisibleForTesting
-    public RcsFeatureController(Context context, int slotId, RegistrationHelperFactory f) {
+    public RcsFeatureController(Context context, int slotId, int associatedSubId,
+            RegistrationHelperFactory f) {
         mContext = context;
         mSlotId = slotId;
+        mAssociatedSubId = associatedSubId;
         mRegistrationHelperFactory = f;
         mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
                 mContext.getMainExecutor());
@@ -248,17 +258,12 @@
     }
 
     /**
-     * Update the subscription associated with this controller.
+     * Update the Features associated with this controller due to the associated subscription
+     * changing.
      */
     public void updateAssociatedSubscription(int newSubId) {
-        RcsFeatureManager manager = getFeatureManager();
-        if (manager != null) {
-            try {
-                manager.updateCapabilities();
-            } catch (ImsException e) {
-                Log.w(LOG_TAG, "associatedSubscriptionChanged failed:" + e);
-            }
-        }
+        mAssociatedSubId = newSubId;
+        updateCapabilities();
         synchronized (mLock) {
             for (Feature c : mFeatures.values()) {
                 c.onAssociatedSubscriptionUpdated(newSubId);
@@ -267,6 +272,19 @@
     }
 
     /**
+     * Update the features associated with this controller due to the carrier configuration
+     * changing.
+     */
+    public void onCarrierConfigChangedForSubscription() {
+        updateCapabilities();
+        synchronized (mLock) {
+            for (Feature c : mFeatures.values()) {
+                c.onCarrierConfigChanged();
+            }
+        }
+    }
+
+    /**
      * Call before this controller is destroyed to tear down associated features.
      */
     public void destroy() {
@@ -314,8 +332,8 @@
     }
 
     /**
-     * Register an {@link ImsRcsManager.AvailabilityCallback} with the associated RcsFeature,
-     * which will provide availability updates.
+     * Register an {@link ImsRcsManager.OnAvailabilityChangedListener} with the associated
+     * RcsFeature, which will provide availability updates.
      */
     public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
             throws ImsException {
@@ -328,7 +346,7 @@
     }
 
     /**
-     * Remove a registered {@link ImsRcsManager.AvailabilityCallback} from the RcsFeature.
+     * Remove a registered {@link ImsRcsManager.OnAvailabilityChangedListener} from the RcsFeature.
      */
     public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
         RcsFeatureManager manager = getFeatureManager();
@@ -381,10 +399,21 @@
         callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
     }
 
+    private void updateCapabilities() {
+        RcsFeatureManager manager = getFeatureManager();
+        if (manager != null) {
+            try {
+                manager.updateCapabilities(mAssociatedSubId);
+            } catch (ImsException e) {
+                Log.w(LOG_TAG, "updateCapabilities failed:" + e);
+            }
+        }
+    }
+
     private void setupConnectionToService(RcsFeatureManager manager) throws ImsException {
         // Open persistent listener connection, sends RcsFeature#onFeatureReady.
         manager.openConnection();
-        manager.updateCapabilities();
+        manager.updateCapabilities(mAssociatedSubId);
         manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
     }
 
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
index 028e49f..a948cdb 100644
--- a/src/com/android/services/telephony/rcs/SipTransportController.java
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -296,6 +296,11 @@
     }
 
     @Override
+    public void onCarrierConfigChanged() {
+        mExecutorService.submit(this::onCarrierConfigChangedInternal);
+    }
+
+    @Override
     public void onDestroy() {
         mExecutorService.submit(()-> {
             // Ensure new create/destroy requests are denied.
@@ -903,8 +908,7 @@
     }
 
     /**
-     * Called when either the sub ID associated with the slot has changed or the carrier
-     * configuration associated with the same subId has changed.
+     * Called when the sub ID associated with the slot has changed.
      */
     private void onSubIdChanged(int newSubId) {
         logi("subId changed, " + mSubId + "->" + newSubId);
@@ -913,10 +917,14 @@
             mSubId = newSubId;
             scheduleDestroyDelegates(
                     SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
-            return;
         }
-        // TODO: if subId hasn't changed this means that we should load in any new carrier configs
-        // that we care about and apply.
+    }
+
+    /**
+     * Called when the carrier configuration associated with the same subId has changed.
+     */
+    private void onCarrierConfigChangedInternal() {
+        logi("Carrier Config changed for subId: " + mSubId);
     }
 
     /**
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index 66492ae..034382c 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -55,7 +55,7 @@
         /**
          * @return an {@link RcsFeatureController} associated with the slot specified.
          */
-        RcsFeatureController createController(Context context, int slotId);
+        RcsFeatureController createController(Context context, int slotId, int subId);
 
         /**
          * @return an instance of {@link UceControllerManager} associated with the slot specified.
@@ -71,8 +71,8 @@
 
     private FeatureFactory mFeatureFactory = new FeatureFactory() {
         @Override
-        public RcsFeatureController createController(Context context, int slotId) {
-            return new RcsFeatureController(context, slotId);
+        public RcsFeatureController createController(Context context, int slotId, int subId) {
+            return new RcsFeatureController(context, slotId, subId);
         }
 
         @Override
@@ -113,6 +113,8 @@
 
     // Maps slot ID -> RcsFeatureController.
     private SparseArray<RcsFeatureController> mFeatureControllers;
+    // Maps slotId -> associatedSubIds
+    private SparseArray<Integer> mSlotToAssociatedSubIds;
 
     // Whether the device supports User Capability Exchange
     private boolean mRcsUceEnabled;
@@ -132,7 +134,7 @@
                         SubscriptionManager.INVALID_PHONE_INDEX);
                 int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                updateFeatureControllerSubscription(slotId, subId);
+                onCarrierConfigChangedForSlot(slotId, subId);
             }
         }
     };
@@ -159,6 +161,7 @@
         mContext = context;
         mNumSlots = numSlots;
         mFeatureControllers = new SparseArray<>(numSlots);
+        mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
         mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
     }
 
@@ -167,6 +170,7 @@
         mContext = context;
         mNumSlots = numSlots;
         mFeatureControllers = new SparseArray<>(numSlots);
+        mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
         sResourceProxy = resourceProxy;
         mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
     }
@@ -218,6 +222,8 @@
                     // Do not add feature controllers for inactive subscriptions
                     if (c.hasActiveFeatures()) {
                         mFeatureControllers.put(i, c);
+                        // Do not change mSlotToAssociatedSubIds, it will be updated upon carrier
+                        // config change.
                     }
                 }
             } else {
@@ -225,6 +231,7 @@
                     RcsFeatureController c = mFeatureControllers.get(i);
                     if (c != null) {
                         mFeatureControllers.remove(i);
+                        mSlotToAssociatedSubIds.remove(i);
                         c.destroy();
                     }
                 }
@@ -232,19 +239,29 @@
         }
     }
 
-    private void updateFeatureControllerSubscription(int slotId, int newSubId) {
+    /**
+     * ACTION_CARRIER_CONFIG_CHANGED was received by this service for a specific slot.
+     * @param slotId The slotId associated with the event.
+     * @param subId The subId associated with the event. May cause the subId associated with the
+     *              RcsFeatureController to change if the subscription itself has changed.
+     */
+    private void onCarrierConfigChangedForSlot(int slotId, int subId) {
         synchronized (mLock) {
             RcsFeatureController f = mFeatureControllers.get(slotId);
-            Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId + " newSubId="
-                    + newSubId + ", existing feature=" + (f != null));
-            if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
+            final int oldSubId = mSlotToAssociatedSubIds.get(slotId,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            mSlotToAssociatedSubIds.put(slotId, subId);
+            Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId
+                    + ", oldSubId= " + oldSubId + ", subId=" + subId + ", existing feature="
+                    + (f != null));
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                 if (f == null) {
                     // A controller doesn't exist for this slot yet.
-                    f = mFeatureFactory.createController(mContext, slotId);
-                    updateSupportedFeatures(f, slotId, newSubId);
+                    f = mFeatureFactory.createController(mContext, slotId, subId);
+                    updateSupportedFeatures(f, slotId, subId);
                     if (f.hasActiveFeatures()) mFeatureControllers.put(slotId, f);
                 } else {
-                    updateSupportedFeatures(f, slotId, newSubId);
+                    updateSupportedFeatures(f, slotId, subId);
                     // Do not keep an empty container around.
                     if (!f.hasActiveFeatures()) {
                         f.destroy();
@@ -252,13 +269,19 @@
                     }
                 }
             }
-            if (f != null) f.updateAssociatedSubscription(newSubId);
+            if (f != null) {
+                if (oldSubId == subId) {
+                    f.onCarrierConfigChangedForSubscription();
+                } else {
+                    f.updateAssociatedSubscription(subId);
+                }
+            }
         }
     }
 
     private RcsFeatureController constructFeatureController(int slotId) {
-        RcsFeatureController c = mFeatureFactory.createController(mContext, slotId);
         int subId = getSubscriptionFromSlot(slotId);
+        RcsFeatureController c = mFeatureFactory.createController(mContext, slotId, subId);
         updateSupportedFeatures(c, slotId, subId);
         return c;
     }
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
index 2c777d4..3051253 100644
--- a/src/com/android/services/telephony/rcs/UceControllerManager.java
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -52,6 +52,7 @@
     private final Context mContext;
     private final ExecutorService mExecutorService;
 
+    private volatile int mSubId;
     private volatile UceController mUceController;
     private volatile RcsFeatureManager mRcsFeatureManager;
 
@@ -59,6 +60,7 @@
         Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
 
         mSlotId = slotId;
+        mSubId = subId;
         mContext = context;
         mExecutorService = Executors.newSingleThreadExecutor();
         mUceController = new UceController(mContext, subId);
@@ -70,6 +72,7 @@
     @VisibleForTesting
     public UceControllerManager(Context context, int slotId, int subId, ExecutorService executor) {
         mSlotId = slotId;
+        mSubId = subId;
         mContext = context;
         mExecutorService = executor;
         mUceController = new UceController(mContext, subId);
@@ -100,15 +103,19 @@
     }
 
     /**
-     * This method will be called when either the subscription ID associated with the slot has
-     * changed or the carrier configuration associated with the same subId has changed.
+     * This method will be called when the subscription ID associated with the slot has
+     * changed.
      */
     @Override
     public void onAssociatedSubscriptionUpdated(int subId) {
         mExecutorService.submit(() -> {
             Log.i(LOG_TAG, "onAssociatedSubscriptionUpdated: slotId=" + mSlotId
-                    + ", subId=" + subId);
-
+                    + ", subId=" + mSubId + ", newSubId=" + subId);
+            if (mSubId == subId) {
+                Log.w(LOG_TAG, "onAssociatedSubscriptionUpdated called with the same subId");
+                return;
+            }
+            mSubId = subId;
             // Destroy existing UceController and create a new one.
             mUceController.onDestroy();
             mUceController = new UceController(mContext, subId);
@@ -121,6 +128,18 @@
         });
     }
 
+    /**
+     * This method will be called when the carrier config of the subscription associated with this
+     * manager has changed.
+     */
+    @Override
+    public void onCarrierConfigChanged() {
+        mExecutorService.submit(() -> {
+            Log.i(LOG_TAG, "onCarrierConfigChanged: subId=" + mSubId);
+            mUceController.onCarrierConfigChanged();
+        });
+    }
+
     @VisibleForTesting
     public void setUceController(UceController uceController) {
         mUceController = uceController;
diff --git a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
index eecbd2e..da614fc 100644
--- a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
@@ -58,6 +58,8 @@
 @RunWith(AndroidJUnit4.class)
 public class RcsFeatureControllerTest extends TelephonyTestBase {
 
+    private static final int TEST_SUB_ID = 1;
+
     private static final ImsReasonInfo REASON_DISCONNECTED = new ImsReasonInfo(
             ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN, 0, "test");
 
@@ -96,7 +98,7 @@
         // Connect the RcsFeatureManager
         mConnectorListener.getValue().connectionReady(mFeatureManager);
 
-        verify(mFeatureManager).updateCapabilities();
+        verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
         verify(mFeatureManager).registerImsRegistrationCallback(any());
         verify(mMockFeature).onRcsConnected(mFeatureManager);
 
@@ -132,16 +134,16 @@
         mConnectorListener.getValue().connectionReady(mFeatureManager);
 
         try {
-            controller.registerImsRegistrationCallback(0 /*subId*/, regCb);
-            controller.registerRcsAvailabilityCallback(0 /*subId*/, capCb);
+            controller.registerImsRegistrationCallback(TEST_SUB_ID, regCb);
+            controller.registerRcsAvailabilityCallback(TEST_SUB_ID, capCb);
             controller.isCapable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
                     ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
             controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
                     ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
             controller.getRegistrationTech(integer -> {
             });
-            verify(mFeatureManager).registerImsRegistrationCallback(0, regCb);
-            verify(mFeatureManager).registerRcsAvailabilityCallback(0, capCb);
+            verify(mFeatureManager).registerImsRegistrationCallback(TEST_SUB_ID, regCb);
+            verify(mFeatureManager).registerRcsAvailabilityCallback(TEST_SUB_ID, capCb);
             verify(mFeatureManager).isCapable(
                     RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
                     ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
@@ -153,10 +155,10 @@
             fail("ImsException not expected.");
         }
 
-        controller.unregisterImsRegistrationCallback(0, regCb);
-        controller.unregisterRcsAvailabilityCallback(0, capCb);
-        verify(mFeatureManager).unregisterImsRegistrationCallback(0, regCb);
-        verify(mFeatureManager).unregisterRcsAvailabilityCallback(0, capCb);
+        controller.unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+        controller.unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+        verify(mFeatureManager).unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+        verify(mFeatureManager).unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
     }
 
     @Test
@@ -216,13 +218,13 @@
                 FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
 
         try {
-            controller.registerImsRegistrationCallback(0 /*subId*/, null /*callback*/);
+            controller.registerImsRegistrationCallback(TEST_SUB_ID, null /*callback*/);
             fail("ImsException expected for IMS registration.");
         } catch (ImsException e) {
             //expected
         }
         try {
-            controller.registerRcsAvailabilityCallback(0 /*subId*/, null /*callback*/);
+            controller.registerRcsAvailabilityCallback(TEST_SUB_ID, null /*callback*/);
             fail("ImsException expected for availability");
         } catch (ImsException e) {
             //expected
@@ -245,10 +247,25 @@
             assertNotNull(integer);
             assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, integer.intValue());
         });
-        controller.unregisterImsRegistrationCallback(0, regCb);
-        controller.unregisterRcsAvailabilityCallback(0, capCb);
-        verify(mFeatureManager, never()).unregisterImsRegistrationCallback(0, regCb);
-        verify(mFeatureManager, never()).unregisterRcsAvailabilityCallback(0, capCb);
+        controller.unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+        controller.unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+        verify(mFeatureManager, never()).unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+        verify(mFeatureManager, never()).unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+    }
+
+    @Test
+    public void testCarrierConfigChanged() throws Exception {
+        RcsFeatureController controller = createFeatureController();
+        // Connect the RcsFeatureManager
+        mConnectorListener.getValue().connectionReady(mFeatureManager);
+        verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
+        controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+
+        controller.onCarrierConfigChangedForSubscription();
+
+        verify(mFeatureManager, times(2)).updateCapabilities(TEST_SUB_ID);
+        verify(mMockFeature).onCarrierConfigChanged();
+        verify(mMockFeature, never()).onAssociatedSubscriptionUpdated(anyInt());
     }
 
     @Test
@@ -256,13 +273,13 @@
         RcsFeatureController controller = createFeatureController();
         // Connect the RcsFeatureManager
         mConnectorListener.getValue().connectionReady(mFeatureManager);
-        verify(mFeatureManager).updateCapabilities();
+        verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
         controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
 
-        controller.updateAssociatedSubscription(1 /*new sub id*/);
+        controller.updateAssociatedSubscription(2 /*new subId*/);
 
-        verify(mFeatureManager, times(2)).updateCapabilities();
-        verify(mMockFeature).onAssociatedSubscriptionUpdated(1 /*new sub id*/);
+        verify(mFeatureManager).updateCapabilities(2 /*new subId*/);
+        verify(mMockFeature).onAssociatedSubscriptionUpdated(2 /*new subId*/);
     }
 
     @Test
@@ -281,7 +298,7 @@
 
     private RcsFeatureController createFeatureController() {
         RcsFeatureController controller = new RcsFeatureController(mContext, 0 /*slotId*/,
-                mRegistrationFactory);
+                TEST_SUB_ID, mRegistrationFactory);
         controller.setFeatureConnectorFactory(mFeatureFactory);
         doReturn(mFeatureConnector).when(mFeatureFactory).create(any(), anyInt(),
                 mConnectorListener.capture(), any(), any());
diff --git a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
index c367af3..39469b6 100644
--- a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
+++ b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
@@ -67,10 +67,12 @@
         super.setUp();
         doReturn(mFeatureConnector).when(mFeatureConnectorFactory).create(any(), anyInt(),
                 any(), any(), any());
-        mFeatureControllerSlot0 = createFeatureController(0 /*slotId*/);
-        mFeatureControllerSlot1 = createFeatureController(1 /*slotId*/);
-        doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0));
-        doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1));
+        mFeatureControllerSlot0 = createFeatureController(0 /*slotId*/, 1 /*subId*/);
+        mFeatureControllerSlot1 = createFeatureController(1 /*slotId*/, 2 /*subId*/);
+        doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0),
+                anyInt());
+        doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1),
+                anyInt());
         doReturn(mMockUceSlot0).when(mFeatureFactory).createUceControllerManager(any(), eq(0),
                 anyInt());
         doReturn(mMockUceSlot1).when(mFeatureFactory).createUceControllerManager(any(), eq(1),
@@ -227,7 +229,7 @@
     }
 
     @Test
-    public void testCarrierConfigUpdate() {
+    public void testCarrierConfigUpdateAssociatedSub() {
         setCarrierConfig(1 /*subId*/,
                 CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
                 true /*isEnabled*/);
@@ -251,6 +253,26 @@
     }
 
     @Test
+    public void testCarrierConfigNotifyFeatures() {
+        setCarrierConfig(1 /*subId*/,
+                CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+                true /*isEnabled*/);
+        createRcsService(1 /*numSlots*/);
+        verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
+        verify(mFeatureControllerSlot0).connect();
+
+
+        // Send carrier config update twice with no update to subId
+        sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+        verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+        verify(mFeatureControllerSlot0, never()).onCarrierConfigChangedForSubscription();
+        sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+        verify(mFeatureControllerSlot0, times(1)).updateAssociatedSubscription(1);
+        // carrier config changed should be sent here
+        verify(mFeatureControllerSlot0).onCarrierConfigChangedForSubscription();
+    }
+
+    @Test
     public void testCarrierConfigUpdateUceToNoUce() {
         setCarrierConfig(1 /*subId*/,
                 CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
@@ -334,10 +356,10 @@
         return service;
     }
 
-    private RcsFeatureController createFeatureController(int slotId) {
+    private RcsFeatureController createFeatureController(int slotId, int subId) {
         // Create a spy instead of a mock because TelephonyRcsService relies on state provided by
         // RcsFeatureController.
-        RcsFeatureController controller = spy(new RcsFeatureController(mContext, slotId,
+        RcsFeatureController controller = spy(new RcsFeatureController(mContext, slotId, subId,
                 mRegistrationFactory));
         controller.setFeatureConnectorFactory(mFeatureConnectorFactory);
         return controller;
diff --git a/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
index 4148d13..82687f8 100644
--- a/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.net.Uri;
@@ -94,11 +95,15 @@
     }
 
     @Test
-    public void testSubscriptionUpdated() throws Exception {
+    public void testSubIdAndCarrierConfigUpdate() throws Exception {
         UceControllerManager uceCtrlManager = getUceControllerManager();
 
-        uceCtrlManager.onAssociatedSubscriptionUpdated(mSubId);
+        // Updates with the same subId should not destroy the UceController
+        uceCtrlManager.onCarrierConfigChanged();
+        verify(mUceController, never()).onDestroy();
 
+        // Updates with different subIds should trigger the creation of a new controller.
+        uceCtrlManager.onAssociatedSubscriptionUpdated(mSubId + 1);
         verify(mUceController).onDestroy();
     }