Merge "Support multi sim on ims reg state change"
diff --git a/res/values/config.xml b/res/values/config.xml
index e6c578a..7dd26bb 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -306,4 +306,8 @@
 
     <!-- Whether or not to support RCS VoLTE single registration  -->
     <bool name="config_rcsVolteSingleRegistrationEnabled">true</bool>
+
+    <!-- Whether or not to support device to device communication using RTP and DTMF communication
+         transports. -->
+    <bool name="config_use_device_to_device_communication">false</bool>
 </resources>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 5f99ab7..8261756 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -9581,10 +9581,11 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .addRcsProvisioningCallbackForSubscription(callback, subId);
-        } catch (ImsException e) {
-            throw new ServiceSpecificException(e.getCode());
+            if (!RcsProvisioningMonitor.getInstance()
+                    .registerRcsProvisioningChangedCallback(subId, callback)) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                        "Service not available for the subscription.");
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -9608,11 +9609,8 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .removeRcsProvisioningCallbackForSubscription(callback, subId);
-        } catch (ImsException e) {
-            Log.i(LOG_TAG, "unregisterRcsProvisioningChangedCallback: " + subId
-                    + "is inactive, ignoring unregister.");
+            RcsProvisioningMonitor.getInstance()
+                    .unregisterRcsProvisioningChangedCallback(subId, callback);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index c4f367d..7b51eeb 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -36,19 +36,18 @@
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsConfig;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.text.TextUtils;
 
-import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.CollectionUtils;
 import com.android.telephony.Rlog;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
@@ -69,10 +68,8 @@
 
     private final PhoneGlobals mPhone;
     private final Handler mHandler;
-    //cache the rcs config per sub id
-    private final Map<Integer, byte[]> mConfigs = Collections.synchronizedMap(new HashMap<>());
-    //cache the single registration config per sub id
-    private final ConcurrentHashMap<Integer, Integer> mSingleRegistrations =
+    // Cache the RCS provsioning info and related sub id
+    private final ConcurrentHashMap<Integer, RcsProvisioningInfo> mRcsProvisioningInfos =
             new ConcurrentHashMap<>();
     private Boolean mDeviceSingleRegistrationEnabledOverride;
     private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
@@ -185,6 +182,105 @@
         }
     }
 
+    private final class RcsProvisioningInfo {
+        private int mSubId;
+        private volatile int mSingleRegistrationCapability;
+        private volatile byte[] mConfig;
+        private HashSet<IRcsConfigCallback> mRcsConfigCallbacks;
+
+        RcsProvisioningInfo(int subId, int singleRegistrationCapability, byte[] config) {
+            mSubId = subId;
+            mSingleRegistrationCapability = singleRegistrationCapability;
+            mConfig = config;
+            mRcsConfigCallbacks = new HashSet<>();
+        }
+
+        void setSingleRegistrationCapability(int singleRegistrationCapability) {
+            mSingleRegistrationCapability = singleRegistrationCapability;
+        }
+
+        int getSingleRegistrationCapability() {
+            return mSingleRegistrationCapability;
+        }
+
+        void setConfig(byte[] config) {
+            mConfig = config;
+        }
+
+        byte[] getConfig() {
+            return mConfig;
+        }
+
+        boolean addRcsConfigCallback(IRcsConfigCallback cb) {
+            IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
+            if (imsConfig == null) {
+                logd("fail to addRcsConfigCallback as imsConfig is null");
+                return false;
+            }
+
+            synchronized (mRcsConfigCallbacks) {
+                try {
+                    imsConfig.addRcsConfigCallback(cb);
+                } catch (RemoteException e) {
+                    loge("fail to addRcsConfigCallback due to " + e);
+                    return false;
+                }
+                mRcsConfigCallbacks.add(cb);
+            }
+            return true;
+        }
+
+        boolean removeRcsConfigCallback(IRcsConfigCallback cb) {
+            boolean result = true;
+            IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
+
+            synchronized (mRcsConfigCallbacks) {
+                if (imsConfig != null) {
+                    try {
+                        imsConfig.removeRcsConfigCallback(cb);
+                    } catch (RemoteException e) {
+                        loge("fail to removeRcsConfigCallback due to " + e);
+                    }
+                } else {
+                    // Return false but continue to remove the callback
+                    result = false;
+                }
+
+                try {
+                    cb.onRemoved();
+                } catch (RemoteException e) {
+                    logd("Failed to notify onRemoved due to dead binder of " + cb);
+                }
+                mRcsConfigCallbacks.remove(cb);
+            }
+            return result;
+        }
+
+        void clear() {
+            setConfig(null);
+            synchronized (mRcsConfigCallbacks) {
+                IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
+                Iterator<IRcsConfigCallback> it = mRcsConfigCallbacks.iterator();
+                while (it.hasNext()) {
+                    IRcsConfigCallback cb = it.next();
+                    if (imsConfig != null) {
+                        try {
+                            imsConfig.removeRcsConfigCallback(cb);
+                        } catch (RemoteException e) {
+                            loge("fail to removeRcsConfigCallback due to " + e);
+                        }
+                    }
+                    try {
+                        cb.onRemoved();
+                    } catch (RemoteException e) {
+                        logd("Failed to notify onRemoved due to dead binder of " + cb);
+                    }
+                    it.remove();
+                }
+            }
+        }
+    }
+
     @VisibleForTesting
     public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager) {
         mPhone = app;
@@ -252,15 +348,19 @@
      */
     @VisibleForTesting
     public byte[] getConfig(int subId) {
-        return mConfigs.get(subId);
+        if (mRcsProvisioningInfos.containsKey(subId)) {
+            return mRcsProvisioningInfos.get(subId).getConfig();
+        }
+        return null;
     }
 
     /**
      * Returns whether Rcs Volte single registration is enabled for the sub.
      */
     public boolean isRcsVolteSingleRegistrationEnabled(int subId) {
-        if (mSingleRegistrations.containsKey(subId)) {
-            return mSingleRegistrations.get(subId) == ProvisioningManager.STATUS_CAPABLE;
+        if (mRcsProvisioningInfos.containsKey(subId)) {
+            return mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
+                    == ProvisioningManager.STATUS_CAPABLE;
         }
         return false;
     }
@@ -281,6 +381,34 @@
     }
 
     /**
+     * Called when the application registers rcs provisioning changed callback
+     */
+    public boolean registerRcsProvisioningChangedCallback(int subId, IRcsConfigCallback cb) {
+        RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+        // should not happen in normal case
+        if (info == null) {
+            logd("fail to register rcs provisioning changed due to subscription unavailable");
+            return false;
+        }
+
+        return info.addRcsConfigCallback(cb);
+    }
+
+    /**
+     * Called when the application unregisters rcs provisioning changed callback
+     */
+    public boolean unregisterRcsProvisioningChangedCallback(int subId, IRcsConfigCallback cb) {
+        RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+        // should not happen in normal case
+        if (info == null) {
+            logd("fail to unregister rcs provisioning changed due to subscription unavailable");
+            return false;
+        }
+
+        return info.removeRcsConfigCallback(cb);
+    }
+
+    /**
      * override the device config whether single registration is enabled
      */
     public void overrideDeviceSingleRegistrationEnabled(Boolean enabled) {
@@ -291,7 +419,7 @@
      * Overrides the carrier config whether single registration is enabled
      */
     public boolean overrideCarrierSingleRegistrationEnabled(int subId, Boolean enabled) {
-        if (!mSingleRegistrations.containsKey(subId)) {
+        if (!mRcsProvisioningInfos.containsKey(subId)) {
             return false;
         }
         mHandler.sendMessage(mHandler.obtainMessage(
@@ -303,8 +431,9 @@
      * Returns the device config whether single registration is enabled
      */
     public boolean getDeviceSingleRegistrationEnabled() {
-        for (int val : mSingleRegistrations.values()) {
-            return (val & ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) == 0;
+        for (RcsProvisioningInfo info : mRcsProvisioningInfos.values()) {
+            return (info.getSingleRegistrationCapability()
+                    & ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) == 0;
         }
         return false;
     }
@@ -313,8 +442,8 @@
      * Returns the carrier config whether single registration is enabled
      */
     public boolean getCarrierSingleRegistrationEnabled(int subId) {
-        if (mSingleRegistrations.containsKey(subId)) {
-            return (mSingleRegistrations.get(subId)
+        if (mRcsProvisioningInfos.containsKey(subId)) {
+            return (mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
                     & ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE) == 0;
         }
         return false;
@@ -323,20 +452,21 @@
     private void onDefaultMessagingApplicationChanged() {
         final String packageName = getDmaPackageName();
         if (!TextUtils.equals(mDmaPackageName, packageName)) {
-            //clear old callbacks
-            ImsManager.getInstance(mPhone, mPhone.getPhone().getPhoneId())
-                    .clearRcsProvisioningCallbacks();
             mDmaPackageName = packageName;
             logv("new default messaging application " + mDmaPackageName);
-            mConfigs.forEach((k, v) -> {
+
+            mRcsProvisioningInfos.forEach((k, v) -> {
+                byte[] cachedConfig = v.getConfig();
+                //clear old callbacks
+                v.clear();
                 if (isAcsUsed(k)) {
                     logv("acs used, trigger to re-configure.");
-                    mConfigs.put(k, null);
                     notifyRcsAutoConfigurationRemoved(k);
                     triggerRcsReconfiguration(k);
                 } else {
+                    v.setConfig(cachedConfig);
                     logv("acs not used, notify.");
-                    notifyRcsAutoConfigurationReceived(k, v, false);
+                    notifyRcsAutoConfigurationReceived(k, v.getConfig(), false);
                 }
             });
         }
@@ -425,58 +555,68 @@
 
     private void onCarrierConfigChange() {
         logv("onCarrierConfigChange");
-        mConfigs.forEach((subId, config) -> {
+        mRcsProvisioningInfos.forEach((subId, info) -> {
             int value = getSingleRegistrationCapableValue(subId);
-            if (value != mSingleRegistrations.get(subId)) {
-                mSingleRegistrations.put(subId, value);
-                notifyDmaForSub(subId);
+            if (value != info.getSingleRegistrationCapability()) {
+                info.setSingleRegistrationCapability(value);
+                notifyDmaForSub(subId, value);
             }
         });
     }
 
     private void onSubChanged() {
         final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
-        final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mConfigs.keySet());
+        final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mRcsProvisioningInfos.keySet());
 
         for (int i : activeSubs) {
             subsToBeDeactivated.remove(i);
-            if (!mConfigs.containsKey(i)) {
+            if (!mRcsProvisioningInfos.containsKey(i)) {
                 byte[] data = RcsConfig.loadRcsConfigForSub(mPhone, i, false);
-                logv("new config is created for sub : " + i);
-                mConfigs.put(i, data);
+                int capability = getSingleRegistrationCapableValue(i);
+                logv("new info is created for sub : " + i + ", single registration capability :"
+                        + capability + ", rcs config : " + data);
+                mRcsProvisioningInfos.put(i, new RcsProvisioningInfo(i, capability, data));
                 notifyRcsAutoConfigurationReceived(i, data, false);
-                mSingleRegistrations.put(i, getSingleRegistrationCapableValue(i));
-                notifyDmaForSub(i);
+                notifyDmaForSub(i, capability);
             }
         }
 
         subsToBeDeactivated.forEach(i -> {
-            mConfigs.remove(i);
+            RcsProvisioningInfo info = mRcsProvisioningInfos.remove(i);
             notifyRcsAutoConfigurationRemoved(i);
+            if (info != null) {
+                info.clear();
+            }
         });
     }
 
     private void onConfigReceived(int subId, byte[] config, boolean isCompressed) {
         logv("onConfigReceived, subId:" + subId + ", config:"
                 + config + ", isCompressed:" + isCompressed);
-        mConfigs.put(subId, isCompressed ? RcsConfig.decompressGzip(config) : config);
+        RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+        if (info != null) {
+            info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
+        }
         RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
         notifyRcsAutoConfigurationReceived(subId, config, isCompressed);
     }
 
     private void onReconfigRequest(int subId) {
         logv("onReconfigRequest, subId:" + subId);
-        mConfigs.put(subId, null);
+        RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+        if (info != null) {
+            info.setConfig(null);
+        }
         notifyRcsAutoConfigurationRemoved(subId);
         triggerRcsReconfiguration(subId);
     }
 
-    private void notifyDmaForSub(int subId) {
+    private void notifyDmaForSub(int subId, int capability) {
         final Intent intent = new Intent(
                 ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
         intent.setPackage(mDmaPackageName);
         intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
-        intent.putExtra(ProvisioningManager.EXTRA_STATUS, mSingleRegistrations.get(subId));
+        intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
         logv("notify " + intent);
         mPhone.sendBroadcast(intent);
     }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 3e7f29c..6814a00 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -46,6 +46,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.RtpHeaderExtension;
+import android.telephony.ims.RtpHeaderExtensionType;
 import android.text.TextUtils;
 import android.util.Pair;
 
@@ -61,6 +63,9 @@
 import com.android.internal.telephony.Connection.PostDialListener;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.d2d.Communicator;
+import com.android.internal.telephony.d2d.RtpAdapter;
+import com.android.internal.telephony.d2d.RtpTransport;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -85,7 +90,7 @@
 /**
  * Base class for CDMA and GSM connections.
  */
-abstract class TelephonyConnection extends Connection implements Holdable {
+abstract class TelephonyConnection extends Connection implements Holdable, Communicator.Callback {
     private static final String LOG_TAG = "TelephonyConnection";
 
     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
@@ -487,6 +492,19 @@
         public void onRingbackRequested(Connection c, boolean ringback) {}
     }
 
+    public static class D2DCallStateAdapter extends TelephonyConnectionListener {
+        private Communicator mCommunicator;
+
+        D2DCallStateAdapter(Communicator communicator) {
+            mCommunicator = communicator;
+        }
+
+        @Override
+        public void onStateChanged(android.telecom.Connection c, int state) {
+            mCommunicator.onStateChanged(c, state);
+        }
+    }
+
     private final PostDialListener mPostDialListener = new PostDialListener() {
         @Override
         public void onPostDialWait() {
@@ -701,6 +719,20 @@
         public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {
             setIsNetworkIdentifiedEmergencyCall(isEmergencyCall);
         }
+
+        /**
+         * Indicates data from an RTP header extension has been received from the network.
+         * @param extensionData The extension data.
+         */
+        @Override
+        public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {
+            if (mRtpTransport == null) {
+                return;
+            }
+            Log.i(this, "onReceivedRtpHeaderExtensions: received %d extensions",
+                    extensionData.size());
+            mRtpTransport.onRtpHeaderExtensionsReceived(extensionData);
+        }
     };
 
     private TelephonyConnectionService mTelephonyConnectionService;
@@ -805,6 +837,18 @@
     private int mHangupDisconnectCause = DisconnectCause.NOT_VALID;
 
     /**
+     * Provides a means for a {@link Communicator} to be informed of call state changes.
+     */
+    private D2DCallStateAdapter mD2DCallStateAdapter;
+
+    private RtpTransport mRtpTransport;
+
+    /**
+     * Facilitates device to device communication.
+     */
+    private Communicator mCommunicator;
+
+    /**
      * Listeners to our TelephonyConnection specific callbacks
      */
     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
@@ -1404,6 +1448,9 @@
         if (isImsConnection()) {
             mWasImsConnection = true;
         }
+        if (originalConnection instanceof ImsPhoneConnection) {
+            maybeConfigureDeviceToDeviceCommunication();
+        }
         mIsMultiParty = mOriginalConnection.isMultiparty();
 
         Bundle extrasToPut = new Bundle();
@@ -2218,6 +2265,10 @@
                 case DISCONNECTING:
                     break;
             }
+
+            if (mCommunicator != null) {
+                mCommunicator.onStateChanged(this, getState());
+            }
         }
     }
 
@@ -3112,6 +3163,56 @@
     }
 
     /**
+     * Where device to device communication is available and this is an IMS call, configures the
+     * D2D communication infrastructure for operation.
+     */
+    private void maybeConfigureDeviceToDeviceCommunication() {
+        if (!getPhone().getContext().getResources().getBoolean(
+                R.bool.config_use_device_to_device_communication) || !isImsConnection()) {
+            Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+            return;
+        }
+        // Implement abstracted out RTP functionality the RTP transport depends on.
+        RtpAdapter rtpAdapter = new RtpAdapter() {
+            @Override
+            public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
+                if (!isImsConnection()) {
+                    return Collections.EMPTY_SET;
+                }
+                ImsPhoneConnection originalConnection =
+                        (ImsPhoneConnection) mOriginalConnection;
+                return originalConnection.getAcceptedRtpHeaderExtensions();
+            }
+
+            @Override
+            public void sendRtpHeaderExtensions(
+                    @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
+                if (!isImsConnection()) {
+                    Log.w(this, "sendRtpHeaderExtensions: not an ims connection.");
+                }
+                ImsPhoneConnection originalConnection =
+                        (ImsPhoneConnection) mOriginalConnection;
+                originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
+            }
+        };
+        mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler);
+        mCommunicator = new Communicator(List.of(mRtpTransport), this);
+        mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
+        addTelephonyConnectionListener(mD2DCallStateAdapter);
+    }
+
+    /**
+     * Called by {@link Communicator} associated with this {@link TelephonyConnection} when there
+     * are incoming device-to-device messages received.
+     * @param messages the incoming messages.
+     */
+    @Override
+    public void onMessagesReceived(@NonNull Set<Communicator.Message> messages) {
+        Log.i(this, "onMessagesReceived: got d2d messages: %s", messages);
+        // TODO: Actually do something WITH the messages.
+    }
+
+    /**
      * Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
      * operation has started.
      */
diff --git a/src/com/android/services/telephony/rcs/DelegateStateTracker.java b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
index 1d8fa3b..18ad98e 100644
--- a/src/com/android/services/telephony/rcs/DelegateStateTracker.java
+++ b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
@@ -163,8 +163,8 @@
     public void dump(PrintWriter printWriter) {
         printWriter.println("Last reg state: " + mLastRegState);
         printWriter.println("Denied tags: " + mDelegateDeniedTags);
-        printWriter.println("Most recent logs: ");
         printWriter.println();
+        printWriter.println("Most recent logs: ");
         mLocalLog.dump(printWriter);
     }
 
diff --git a/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
index 0691ae5..c42472d 100644
--- a/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
+++ b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
@@ -343,6 +343,7 @@
 
     /** Dump state about this tracker that should be included in the dumpsys */
     public void dump(PrintWriter printWriter) {
+        printWriter.println("Most recent logs:");
         mLocalLog.dump(printWriter);
     }
 
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
index 4b3176a..2d6d4f0 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateController.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -376,14 +376,23 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("SipDelegateController" + "[" + mSubId + "]:");
         pw.increaseIndent();
+        pw.println("Most recent logs:");
+        pw.increaseIndent();
+        mLocalLog.dump(pw);
+        pw.decreaseIndent();
+
+        pw.println();
         pw.println("DelegateStateTracker:");
         pw.increaseIndent();
-        mDelegateStateTracker.dump(printWriter);
+        mDelegateStateTracker.dump(pw);
         pw.decreaseIndent();
+
+        pw.println();
         pw.println("MessageStateTracker:");
         pw.increaseIndent();
-        mMessageTransportStateTracker.dump(printWriter);
+        mMessageTransportStateTracker.dump(pw);
         pw.decreaseIndent();
+
         pw.decreaseIndent();
     }
 
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
index d1f91d1..09288f1 100644
--- a/src/com/android/services/telephony/rcs/UceControllerManager.java
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -51,6 +51,7 @@
     private final ExecutorService mExecutorService;
 
     private volatile UceController mUceController;
+    private volatile RcsFeatureManager mRcsFeatureManager;
 
     public UceControllerManager(Context context, int slotId, int subId) {
         Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
@@ -74,12 +75,18 @@
 
     @Override
     public void onRcsConnected(RcsFeatureManager manager) {
-        mExecutorService.submit(() -> mUceController.onRcsConnected(manager));
+        mExecutorService.submit(() -> {
+            mRcsFeatureManager = manager;
+            mUceController.onRcsConnected(manager);
+        });
     }
 
     @Override
     public void onRcsDisconnected() {
-        mExecutorService.submit(() -> mUceController.onRcsDisconnected());
+        mExecutorService.submit(() -> {
+            mRcsFeatureManager = null;
+            mUceController.onRcsDisconnected();
+        });
     }
 
     @Override
@@ -103,6 +110,12 @@
             // Destroy existing UceController and create a new one.
             mUceController.onDestroy();
             mUceController = new UceController(mContext, subId);
+
+            // The RCS should be connected when the mRcsFeatureManager is not null. Set it to the
+            // new UceController instance.
+            if (mRcsFeatureManager != null) {
+                mUceController.onRcsConnected(mRcsFeatureManager);
+            }
         });
     }
 
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index fb1e502..02d2f8a 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -53,6 +53,7 @@
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsConfig;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -90,7 +91,7 @@
             + "\t\t<GroupChatAuth>1</GroupChatAuth>\n"
             + "\t\t<ftAuth>1</ftAuth>\n"
             + "\t\t<standaloneMsgAuth>1</standaloneMsgAuth>\n"
-            + "\t\t<geolocPushAuth>1<geolocPushAuth>\n"
+            + "\t\t<geolocPushAuth>1</geolocPushAuth>\n"
             + "\t\t<Ext>\n"
             + "\t\t\t<DataOff>\n"
             + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n"
@@ -135,6 +136,8 @@
     private Resources mResources;
     @Mock
     private PhoneGlobals mPhone;
+    @Mock
+    private IRcsConfigCallback mCallback;
 
     private Executor mExecutor = new Executor() {
         @Override
@@ -433,7 +436,6 @@
         verify(mIImsConfig, times(1)).triggerRcsReconfiguration();
     }
 
-
     @Test
     @SmallTest
     public void testIsRcsVolteSingleRegistrationEnabled() throws Exception {
@@ -463,6 +465,56 @@
         assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
     }
 
+    @Test
+    @SmallTest
+    public void testRegisterThenUnregisterCallback() throws Exception {
+        createMonitor(1);
+
+        boolean result = mRcsProvisioningMonitor.registerRcsProvisioningChangedCallback(
+                FAKE_SUB_ID_BASE, mCallback);
+
+        assertTrue(result);
+        verify(mIImsConfig, times(1)).addRcsConfigCallback(eq(mCallback));
+
+        result = mRcsProvisioningMonitor.unregisterRcsProvisioningChangedCallback(
+                FAKE_SUB_ID_BASE, mCallback);
+
+        assertTrue(result);
+        verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+        verify(mCallback, times(1)).onRemoved();
+    }
+
+    @Test
+    @SmallTest
+    public void testCallbackRemovedWhenSubInfoChanged() throws Exception {
+        createMonitor(1);
+
+        boolean result = mRcsProvisioningMonitor.registerRcsProvisioningChangedCallback(
+                FAKE_SUB_ID_BASE, mCallback);
+        makeFakeActiveSubIds(0);
+        mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+        processAllMessages();
+
+        assertTrue(result);
+        verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+        verify(mCallback, times(1)).onRemoved();
+    }
+
+    @Test
+    @SmallTest
+    public void testCallbackRemovedWhenDmaChanged() throws Exception {
+        createMonitor(1);
+
+        boolean result = mRcsProvisioningMonitor.registerRcsProvisioningChangedCallback(
+                FAKE_SUB_ID_BASE, mCallback);
+        updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+        processAllMessages();
+
+        assertTrue(result);
+        verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+        verify(mCallback, times(1)).onRemoved();
+    }
+
     private void createMonitor(int subCount) {
         if (Looper.myLooper() == null) {
             Looper.prepare();