Merge "Add command to restart the modem"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 37e009d..18e8d04 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -40,6 +40,7 @@
     <protected-broadcast android:name="android.provider.Telephony.SIM_FULL" />
     <protected-broadcast android:name="com.android.internal.telephony.data-restart-trysetup" />
     <protected-broadcast android:name="com.android.internal.telephony.data-stall" />
+    <protected-broadcast android:name="com.android.internal.telephony.provisioning_apn_alarm" />
     <protected-broadcast android:name="android.intent.action.DATA_SMS_RECEIVED" />
     <protected-broadcast android:name="android.provider.Telephony.SMS_RECEIVED" />
     <protected-broadcast android:name="android.provider.Telephony.SMS_DELIVER" />
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/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 12c8cda..2c87f7c 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -263,10 +263,6 @@
     public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
             List<Uri> contactNumbers, IRcsUceControllerCallback c) {
         enforceReadPrivilegedPermission("requestCapabilities");
-        if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
-            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
-                    "The user has not enabled UCE for this subscription.");
-        }
         final long token = Binder.clearCallingIdentity();
         try {
             UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -284,13 +280,9 @@
     }
 
     @Override
-    public void requestNetworkAvailability(int subId, String callingPackage,
+    public void requestAvailability(int subId, String callingPackage,
             String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
-        enforceReadPrivilegedPermission("requestNetworkAvailability");
-        if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
-            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
-                    "The user has not enabled UCE for this subscription.");
-        }
+        enforceReadPrivilegedPermission("requestAvailability");
         final long token = Binder.clearCallingIdentity();
         try {
             UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index ce8e9b3..5c97597 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -69,6 +69,8 @@
 import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
 import com.android.internal.telephony.ims.ImsResolver;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.settings.SettingsConstants;
 import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -360,6 +362,24 @@
                         defaultImsRcsPackage, PhoneFactory.getPhones().length,
                         new ImsFeatureBinderRepository());
                 mImsResolver.initialize();
+
+                // With the IMS phone created, load static config.xml values from the phone process
+                // so that it can be provided to the ImsPhoneCallTracker.
+                for (Phone p : PhoneFactory.getPhones()) {
+                    Phone imsPhone = p.getImsPhone();
+                    if (imsPhone != null && imsPhone instanceof ImsPhone) {
+                        ImsPhone theImsPhone = (ImsPhone) imsPhone;
+                        if (theImsPhone.getCallTracker() instanceof ImsPhoneCallTracker) {
+                            ImsPhoneCallTracker ict = (ImsPhoneCallTracker)
+                                    theImsPhone.getCallTracker();
+
+                            ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
+                            config.isD2DCommunicationSupported = getResources().getBoolean(
+                                    R.bool.config_use_device_to_device_communication);
+                            ict.setConfig(config);
+                        }
+                    }
+                }
                 RcsProvisioningMonitor.make(this);
             }
 
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index b8f87ea..10b8bc8 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -87,8 +87,11 @@
 import android.telephony.PhoneNumberRange;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
+import android.telephony.RadioInterfaceCapabilities;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -127,6 +130,7 @@
 
 import com.android.ims.ImsManager;
 import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.rcs.uce.eab.EabUtil;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.CallStateException;
@@ -201,6 +205,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -312,6 +317,10 @@
     private static final int EVENT_SET_DATA_THROTTLING_DONE = 100;
     private static final int CMD_SET_SIM_POWER = 101;
     private static final int EVENT_SET_SIM_POWER_DONE = 102;
+    private static final int CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST = 103;
+    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE = 104;
+    private static final int CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 105;
+    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE = 106;
 
     // Parameters of select command.
     private static final int SELECT_COMMAND = 0xA4;
@@ -1791,6 +1800,60 @@
                     }
                     break;
                 }
+                case CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+                    request = (MainThreadRequest) msg.obj;
+
+                    final Phone phone = getPhoneFromRequest(request);
+                    if (phone == null || phone.getServiceStateTracker() == null) {
+                        request.result = new IllegalStateException("Phone or SST is null");
+                        notifyRequester(request);
+                        break;
+                    }
+
+                    Pair<Integer, SignalStrengthUpdateRequest> pair =
+                            (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
+                    onCompleted = obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
+                            request);
+                    phone.getServiceStateTracker().setSignalStrengthUpdateRequest(
+                                    request.subId, pair.first /*callingUid*/,
+                                    pair.second /*request*/, onCompleted);
+                    break;
+                }
+                case EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    // request.result will be the exception of ar if present, true otherwise.
+                    // Be cautious not to leave result null which will wait() forever
+                    request.result = ar.exception != null ? ar.exception : true;
+                    notifyRequester(request);
+                    break;
+                }
+                case CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+                    request = (MainThreadRequest) msg.obj;
+
+                    Phone phone = getPhoneFromRequest(request);
+                    if (phone == null || phone.getServiceStateTracker() == null) {
+                        request.result = new IllegalStateException("Phone or SST is null");
+                        notifyRequester(request);
+                        break;
+                    }
+
+                    Pair<Integer, SignalStrengthUpdateRequest> pair =
+                            (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
+                    onCompleted = obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
+                            request);
+                    phone.getServiceStateTracker().clearSignalStrengthUpdateRequest(
+                                    request.subId, pair.first /*callingUid*/,
+                                    pair.second /*request*/, onCompleted);
+                    break;
+                }
+                case EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    request.result = ar.exception != null ? ar.exception : true;
+                    notifyRequester(request);
+                    break;
+                }
 
                 default:
                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
@@ -3015,7 +3078,7 @@
      *
      * @throws SecurityException if the caller is not system.
      */
-    private void enforceSystemCaller() {
+    private static void enforceSystemCaller() {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             throw new SecurityException("Caller must be system");
         }
@@ -3228,7 +3291,9 @@
 
         String authorizedPackage = NumberVerificationManager.getAuthorizedPackage(mApp);
         if (!TextUtils.equals(callingPackage, authorizedPackage)) {
-            throw new SecurityException("Calling package must be configured in the device config");
+            throw new SecurityException("Calling package must be configured in the device config: "
+                    + "calling package: " + callingPackage
+                    + ", configured package: " + authorizedPackage);
         }
 
         if (range == null) {
@@ -5493,12 +5558,20 @@
         }
     }
 
-    public void setImsRegistrationState(boolean registered) {
+    /**
+     * Sets the ims registration state on all valid {@link Phone}s.
+     */
+    public void setImsRegistrationState(final boolean registered) {
         enforceModifyPermission();
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            getDefaultPhone().setImsRegistrationState(registered);
+            // NOTE: Before S, this method only set the default phone.
+            for (final Phone phone : PhoneFactory.getPhones()) {
+                if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) {
+                    phone.setImsRegistrationState(registered);
+                }
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -9255,6 +9328,18 @@
     }
 
     @Override
+    public boolean isRadioInterfaceCapabilitySupported(
+            @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
+        RadioInterfaceCapabilities radioInterfaceCapabilities =
+                mPhoneConfigurationManager.getRadioInterfaceCapabilities();
+        if (radioInterfaceCapabilities == null) {
+            throw new RuntimeException("radio interface capabilities are not available");
+        } else {
+            return radioInterfaceCapabilities.isSupported(capability);
+        }
+    }
+
+    @Override
     public void bootstrapAuthenticationRequest(int subId, int appType, Uri nafUrl,
             UaSecurityProtocolIdentifier securityProtocol,
             boolean forceBootStrapping, IBootstrapAuthenticationCallback callback)
@@ -9353,8 +9438,8 @@
             switch (thermalMitigationAction) {
                 case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_DATA_THROTTLING:
                     thermalMitigationResult =
-                        handleDataThrottlingRequest(subId,
-                                thermalMitigationRequest.getDataThrottlingRequest());
+                            handleDataThrottlingRequest(subId,
+                                    thermalMitigationRequest.getDataThrottlingRequest());
                     break;
                 case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_VOICE_ONLY:
                     if (thermalMitigationRequest.getDataThrottlingRequest() != null) {
@@ -9387,14 +9472,14 @@
                         Phone phone = getPhone(subId);
                         if (phone == null) {
                             thermalMitigationResult =
-                                TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+                                    TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
                             break;
                         }
 
                         if (PhoneConstantConversions.convertCallState(phone.getState())
-                                    != TelephonyManager.CALL_STATE_IDLE
-                                    || phone.isInEmergencySmsMode() || phone.isInEcm()
-                                    || (service != null && service.isEmergencyCallPending())) {
+                                != TelephonyManager.CALL_STATE_IDLE
+                                || phone.isInEmergencySmsMode() || phone.isInEcm()
+                                || (service != null && service.isEmergencyCallPending())) {
                             String errorMessage = "Phone state is not valid. call state = "
                                     + PhoneConstantConversions.convertCallState(phone.getState())
                                     + " isInEmergencySmsMode = " + phone.isInEmergencySmsMode()
@@ -9405,7 +9490,7 @@
                                             + service.isEmergencyCallPending();
                             Log.e(LOG_TAG, errorMessage);
                             thermalMitigationResult =
-                                TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
+                                    TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
                             break;
                         }
                     } else {
@@ -9422,7 +9507,7 @@
                         break;
                     }
                     thermalMitigationResult =
-                        TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+                            TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
                     break;
                 default:
                     throw new IllegalArgumentException("the requested thermalMitigationAction does "
@@ -9571,10 +9656,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);
         }
@@ -9598,11 +9684,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);
         }
@@ -9677,6 +9760,32 @@
     }
 
     /**
+     * Sends a device to device communication message.  Only usable via shell.
+     * @param message message to send.
+     * @param value message value.
+     */
+    @Override
+    public void sendDeviceToDeviceMessage(int message, int value) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "setCarrierSingleRegistrationEnabledOverride");
+        enforceModifyPermission();
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            TelephonyConnectionService service =
+                    TelecomAccountRegistry.getInstance(null).getTelephonyConnectionService();
+            if (service == null) {
+                Rlog.e(LOG_TAG, "sendDeviceToDeviceMessage: not in a call.");
+                return;
+            }
+            service.sendTestDeviceToDeviceMessage(message, value);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+
+    /**
      * Gets the config of RCS VoLTE single registration enabled for the device.
      */
     @Override
@@ -9723,4 +9832,107 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    /**
+     * Remove the EAB contacts from the EAB database.
+     */
+    @Override
+    public int removeContactFromEab(int subId, String contacts) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "removeCapabilitiesFromEab");
+        enforceModifyPermission();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return EabUtil.removeContactFromEab(subId, contacts, getDefaultPhone().getContext());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void setSignalStrengthUpdateRequest(int subId, SignalStrengthUpdateRequest request,
+            String callingPackage) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "setSignalStrengthUpdateRequest");
+
+        final int callingUid = Binder.getCallingUid();
+        // Verify that tha callingPackage belongs to the calling UID
+        mApp.getSystemService(AppOpsManager.class)
+                .checkPackage(callingUid, callingPackage);
+
+        validateSignalStrengthUpdateRequest(request, callingUid);
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Object result = sendRequest(CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                    new Pair<Integer, SignalStrengthUpdateRequest>(callingUid, request), subId);
+
+            if (result instanceof IllegalStateException) {
+                throw (IllegalStateException) result;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void clearSignalStrengthUpdateRequest(int subId, SignalStrengthUpdateRequest request,
+            String callingPackage) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "clearSignalStrengthUpdateRequest");
+
+        final int callingUid = Binder.getCallingUid();
+        // Verify that tha callingPackage belongs to the calling UID
+        mApp.getSystemService(AppOpsManager.class)
+                .checkPackage(callingUid, callingPackage);
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Object result = sendRequest(CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                    new Pair<Integer, SignalStrengthUpdateRequest>(callingUid, request), subId);
+
+            if (result instanceof IllegalStateException) {
+                throw (IllegalStateException) result;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static void validateSignalStrengthUpdateRequest(SignalStrengthUpdateRequest request,
+            int callingUid) {
+        if (callingUid == Process.PHONE_UID || callingUid == Process.SYSTEM_UID) {
+            // phone/system process do not have further restriction on request
+            return;
+        }
+
+        // Applications has restrictions on how to use the request:
+        // Only system caller can set mIsSystemThresholdReportingRequestedWhileIdle
+        if (request.isSystemThresholdReportingRequestedWhileIdle()) {
+            // This is not system caller which has been checked above
+            throw new IllegalArgumentException(
+                    "Only system can set isSystemThresholdReportingRequestedWhileIdle");
+        }
+
+        for (SignalThresholdInfo info : request.getSignalThresholdInfos()) {
+            // Only system caller can set mHysteresisMs/mHysteresisDb/mIsEnabled.
+            if (info.getHysteresisMs() != SignalThresholdInfo.HYSTERESIS_MS_DISABLED
+                    || info.getHysteresisDb() != SignalThresholdInfo.HYSTERESIS_DB_DISABLED
+                    || info.isEnabled()) {
+                throw new IllegalArgumentException(
+                        "Only system can set hide fields in SignalThresholdInfo");
+            }
+
+            // Thresholds length for each RAN need in range. This has been validated in
+            // SignalThresholdInfo#Builder#setThreshold. Here we prevent apps calling hide method
+            // setThresholdUnlimited (e.g. through reflection) with too short or too long thresholds
+            final int[] thresholds = info.getThresholds();
+            Objects.requireNonNull(thresholds);
+            if (thresholds.length < SignalThresholdInfo.getMinimumNumberOfThresholdsAllowed()
+                    || thresholds.length
+                    > SignalThresholdInfo.getMaximumNumberOfThresholdsAllowed()) {
+                throw new IllegalArgumentException(
+                        "thresholds length is out of range: " + thresholds.length);
+            }
+        }
+    }
 }
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 98003bd..7b51eeb 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -36,19 +36,20 @@
 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.Map;
+import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 
 /**
  * Class to monitor RCS Provisioning Status
@@ -67,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 =
@@ -81,6 +80,7 @@
     private final DmaChangedListener mDmaChangedListener;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyRegistryManager mTelephonyRegistryManager;
+    private final RoleManagerAdapter mRoleManager;
 
     private static RcsProvisioningMonitor sInstance;
 
@@ -111,8 +111,6 @@
     };
 
     private final class DmaChangedListener implements OnRoleHoldersChangedListener {
-        private RoleManager mRoleManager;
-
         @Override
         public void onRoleHoldersChanged(String role, UserHandle user) {
             if (RoleManager.ROLE_SMS.equals(role)) {
@@ -123,24 +121,19 @@
         }
 
         public void register() {
-            mRoleManager = mPhone.getSystemService(RoleManager.class);
-            if (mRoleManager != null) {
-                try {
-                    mRoleManager.addOnRoleHoldersChangedListenerAsUser(
-                            mPhone.getMainExecutor(), this, UserHandle.SYSTEM);
-                } catch (RuntimeException e) {
-                    loge("Could not register dma change listener due to " + e);
-                }
+            try {
+                mRoleManager.addOnRoleHoldersChangedListenerAsUser(
+                        mPhone.getMainExecutor(), this, UserHandle.SYSTEM);
+            } catch (RuntimeException e) {
+                loge("Could not register dma change listener due to " + e);
             }
         }
 
         public void unregister() {
-            if (mRoleManager != null) {
-                try {
-                    mRoleManager.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
-                } catch (RuntimeException e) {
-                    loge("Could not unregister dma change listener due to " + e);
-                }
+            try {
+                mRoleManager.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
+            } catch (RuntimeException e) {
+                loge("Could not unregister dma change listener due to " + e);
             }
         }
     }
@@ -189,13 +182,113 @@
         }
     }
 
+    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) {
+    public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager) {
         mPhone = app;
         mHandler = new MyHandler(looper);
         mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
         mSubscriptionManager = mPhone.getSystemService(SubscriptionManager.class);
         mTelephonyRegistryManager = mPhone.getSystemService(TelephonyRegistryManager.class);
+        mRoleManager = roleManager;
         mDmaPackageName = getDmaPackageName();
         logv("DMA is " + mDmaPackageName);
         IntentFilter filter = new IntentFilter();
@@ -217,7 +310,8 @@
             logd("RcsProvisioningMonitor created.");
             HandlerThread handlerThread = new HandlerThread(TAG);
             handlerThread.start();
-            sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper());
+            sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
+                    new RoleManagerAdapterImpl(app));
         }
         return sInstance;
     }
@@ -254,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;
     }
@@ -283,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) {
@@ -293,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(
@@ -305,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;
     }
@@ -315,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;
@@ -325,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);
                 }
             });
         }
@@ -427,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);
     }
@@ -490,8 +628,7 @@
 
     private String getDmaPackageName() {
         try {
-            return CollectionUtils.firstOrNull(mPhone.getSystemService(RoleManager.class)
-                    .getRoleHolders(RoleManager.ROLE_SMS));
+            return CollectionUtils.firstOrNull(mRoleManager.getRoleHolders(RoleManager.ROLE_SMS));
         } catch (RuntimeException e) {
             loge("Could not get dma name due to " + e);
             return null;
@@ -517,4 +654,44 @@
     private static void loge(String msg) {
         Rlog.e(TAG, msg);
     }
+
+    /**
+     * {@link RoleManager} is final so we have to wrap the implementation for testing.
+     */
+    @VisibleForTesting
+    public interface RoleManagerAdapter {
+        /** See {@link RoleManager#getRoleHolders(String)} */
+        List<String> getRoleHolders(String roleName);
+        /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+        void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+                OnRoleHoldersChangedListener listener, UserHandle user);
+        /** See {@link RoleManager#removeOnRoleHoldersChangedListenerAsUser} */
+        void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+                UserHandle user);
+    }
+
+    private static class RoleManagerAdapterImpl implements RoleManagerAdapter {
+        private final RoleManager mRoleManager;
+
+        private RoleManagerAdapterImpl(Context context) {
+            mRoleManager = context.getSystemService(RoleManager.class);
+        }
+
+        @Override
+        public List<String> getRoleHolders(String roleName) {
+            return mRoleManager.getRoleHolders(roleName);
+        }
+
+        @Override
+        public void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+                OnRoleHoldersChangedListener listener, UserHandle user) {
+            mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+        }
+
+        @Override
+        public void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+                UserHandle user) {
+            mRoleManager.removeOnRoleHoldersChangedListenerAsUser(listener, user);
+        }
+    }
 }
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index c64c375..d526e2f 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -16,6 +16,11 @@
 
 package com.android.phone;
 
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_AUDIO_CODEC;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_BATTERY_STATE;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE;
+
 import android.content.Context;
 import android.os.Binder;
 import android.os.PersistableBundle;
@@ -27,11 +32,13 @@
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.feature.ImsFeature;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.d2d.Communicator;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.modules.utils.BasicShellCommandHandler;
@@ -95,6 +102,12 @@
     private static final String SRC_SET_CARRIER_ENABLED = "set-carrier-enabled";
     private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled";
 
+    private static final String D2D_SUBCOMMAND = "d2d";
+    private static final String D2D_SEND = "send";
+
+    private static final String RCS_UCE_COMMAND = "uce";
+    private static final String UCE_REMOVE_EAB_CONTACT = "remove-eab-contact";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -164,6 +177,8 @@
             case IMS_SUBCOMMAND: {
                 return handleImsCommand();
             }
+            case RCS_UCE_COMMAND:
+                return handleRcsUceCommand();
             case NUMBER_VERIFICATION_SUBCOMMAND:
                 return handleNumberVerificationCommand();
             case EMERGENCY_NUMBER_TEST_MODE:
@@ -177,6 +192,8 @@
                 return handleEndBlockSuppressionCommand();
             case GBA_SUBCOMMAND:
                 return handleGbaCommand();
+            case D2D_SUBCOMMAND:
+                return handleD2dCommand();
             case SINGLE_REGISTATION_CONFIG:
                 return handleSingleRegistrationConfigCommand();
             case RESTART_MODEM:
@@ -195,6 +212,8 @@
         pw.println("    Print this help text.");
         pw.println("  ims");
         pw.println("    IMS Commands.");
+        pw.println("  uce");
+        pw.println("    RCS User Capability Exchange Commands.");
         pw.println("  emergency-number-test-mode");
         pw.println("    Emergency Number Test Mode Commands.");
         pw.println("  end-block-suppression");
@@ -210,12 +229,30 @@
         pw.println("  restart-modem");
         pw.println("    Restart modem command.");
         onHelpIms();
+        onHelpUce();
         onHelpEmergencyNumber();
         onHelpEndBlockSupperssion();
         onHelpDataTestMode();
         onHelpCc();
         onHelpGba();
         onHelpSrc();
+        onHelpD2D();
+    }
+
+    private void onHelpD2D() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("D2D Comms Commands:");
+        pw.println("  d2d send TYPE VALUE");
+        pw.println("    Sends a D2D message of specified type and value.");
+        pw.println("    Type: " + MESSAGE_CALL_RADIO_ACCESS_TYPE + " - "
+                + Communicator.messageToString(MESSAGE_CALL_RADIO_ACCESS_TYPE));
+        pw.println("    Type: " + MESSAGE_CALL_AUDIO_CODEC + " - " + Communicator.messageToString(
+                MESSAGE_CALL_AUDIO_CODEC));
+        pw.println("    Type: " + MESSAGE_DEVICE_BATTERY_STATE + " - "
+                        + Communicator.messageToString(
+                        MESSAGE_DEVICE_BATTERY_STATE));
+        pw.println("    Type: " + MESSAGE_DEVICE_NETWORK_COVERAGE + " - "
+                + Communicator.messageToString(MESSAGE_DEVICE_NETWORK_COVERAGE));
     }
 
     private void onHelpIms() {
@@ -254,6 +291,17 @@
         pw.println("    enables or disables handling or network conference event package data.");
     }
 
+    private void onHelpUce() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("User Capability Exchange Commands:");
+        pw.println("  uce remove-eab-contact [-s SLOT_ID] [PHONE_NUMBER]");
+        pw.println("    Remove the EAB contacts from the EAB database.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("      PHONE_NUMBER: The phone numbers to be removed from the EAB databases");
+    }
+
     private void onHelpNumberVerification() {
         PrintWriter pw = getOutPrintWriter();
         pw.println("Number verification commands");
@@ -546,6 +594,64 @@
         return -1;
     }
 
+    private int handleD2dCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpD2D();
+            return 0;
+        }
+
+        switch (arg) {
+            case D2D_SEND: {
+                return handleD2dSendCommand();
+            }
+        }
+
+        return -1;
+    }
+
+    private int handleD2dSendCommand() {
+        PrintWriter errPw = getErrPrintWriter();
+        String opt;
+        int messageType = -1;
+        int messageValue = -1;
+
+
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpD2D();
+            return 0;
+        }
+        try {
+            messageType = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            errPw.println("message type must be a valid integer");
+            return -1;
+        }
+
+        arg = getNextArg();
+        if (arg == null) {
+            onHelpD2D();
+            return 0;
+        }
+        try {
+            messageValue = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            errPw.println("message value must be a valid integer");
+            return -1;
+        }
+        
+        try {
+            mInterface.sendDeviceToDeviceMessage(messageType, messageValue);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "d2d send error: " + e.getMessage());
+            errPw.println("Exception: " + e.getMessage());
+            return -1;
+        }
+
+        return 0;
+    }
+
     // ims set-ims-service
     private int handleImsSetServiceCommand() {
         PrintWriter errPw = getErrPrintWriter();
@@ -1496,6 +1602,45 @@
         return -1;
     }
 
+    private int handleRcsUceCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            Log.w(LOG_TAG, "cannot get uce parameter");
+            return -1;
+        }
+
+        switch (arg) {
+            case UCE_REMOVE_EAB_CONTACT:
+                return handleRemovingEabContactCommand();
+        }
+        return -1;
+    }
+
+    private int handleRemovingEabContactCommand() {
+        int subId = getSubId("uce remove-eab-contact");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String phoneNumber = getNextArgRequired();
+        if (TextUtils.isEmpty(phoneNumber)) {
+            return -1;
+        }
+        int result = 0;
+        try {
+            result = mInterface.removeContactFromEab(subId, phoneNumber);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "uce remove-eab-contact -s " + subId + ", error " + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+
+        if (VDBG) {
+            Log.v(LOG_TAG, "uce remove-eab-contact -s " + subId + ", result: " + result);
+        }
+        return result;
+    }
+
     private int handleSrcSetDeviceEnabledCommand() {
         String enabledStr = getNextArg();
         if (enabledStr == null) {
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index 37212cf..ad3f133 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -122,8 +122,12 @@
                     (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
             boolean isRoaming = tm.isNetworkRoaming(
                     SubscriptionManager.getDefaultVoiceSubscriptionId());
+            boolean alwaysAllowWhileRoaming = isCarrierAllowRttWhenRoaming(
+                    SubscriptionManager.getDefaultVoiceSubscriptionId());
 
-            boolean shouldDisableBecauseRoamingOffWfc = isRoaming && !isOnWfc();
+            boolean shouldDisableBecauseRoamingOffWfc =
+                    (isRoaming && !isOnWfc()) && !alwaysAllowWhileRoaming;
+
             if (shouldDisableBecauseRoamingOffWfc) {
                 mButtonRtt.setSummary(TextUtils.concat(getText(R.string.rtt_mode_summary), "\n",
                         getText(R.string.no_rtt_when_roaming)));
@@ -277,4 +281,13 @@
         return configManager.getConfig().getBoolean(
                 CarrierConfigManager.KEY_TTY_SUPPORTED_BOOL);
     }
+
+    /**
+     * Determines from carrier config whether to always allow RTT while roaming.
+     */
+    private boolean isCarrierAllowRttWhenRoaming(int subId) {
+        PersistableBundle b =
+                PhoneGlobals.getInstance().getCarrierConfigForSubId(subId);
+        return b.getBoolean(CarrierConfigManager.KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL);
+    }
 }
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 016da87..9d4edfd 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -743,6 +743,15 @@
         }
 
         /**
+         * Determines from carrier config whether to always allow RTT while roaming.
+         */
+        private boolean isCarrierAllowRttWhenRoaming() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return b.getBoolean(CarrierConfigManager.KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL);
+        }
+
+        /**
          * Where a device supports instant lettering and call subjects, retrieves the necessary
          * PhoneAccount extras for those features.
          *
@@ -894,11 +903,15 @@
             boolean isRoaming = mTelephonyManager.isNetworkRoaming(mPhone.getSubId());
             boolean isOnWfc = mPhone.getImsRegistrationTech()
                     == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+            boolean alwaysAllowWhileRoaming = isCarrierAllowRttWhenRoaming();
 
-            boolean shouldDisableBecauseRoamingOffWfc = isRoaming && !isOnWfc;
+            boolean shouldDisableBecauseRoamingOffWfc =
+                    (isRoaming && !isOnWfc) && !alwaysAllowWhileRoaming;
+
             Log.i(this, "isRttCurrentlySupported -- regular acct,"
                     + " hasVoiceAvailability: " + hasVoiceAvailability + "\n"
                     + " isRttSupported: " + isRttSupported + "\n"
+                    + " alwaysAllowWhileRoaming: " + alwaysAllowWhileRoaming + "\n"
                     + " isRoaming: " + isRoaming + "\n"
                     + " isOnWfc: " + isOnWfc + "\n");
 
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 3e7f29c..45c00e4 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,77 @@
     }
 
     /**
+     * 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)) {
+            Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+            return;
+        }
+        if (!isImsConnection()) {
+            Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+            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(TelephonyConnection.this, "sendRtpHeaderExtensions: not an ims conn.");
+                }
+                Log.d(TelephonyConnection.this, "sendRtpHeaderExtensions: sending %d messages",
+                        rtpHeaderExtensions.size());
+                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);
+    }
+
+    /**
+     * @return The D2D communication class, or {@code null} if not set up.
+     */
+    public @Nullable Communicator getCommunicator() {
+        return mCommunicator;
+    }
+
+    /**
+     * 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.
+
+        // TODO: Remove this prior to launch.
+        // This is just here for debug purposes; send as a connection event so that it
+        // will be output in the Telecom logs.
+        for (Communicator.Message msg : messages) {
+            sendConnectionEvent("D2D_" + Communicator.messageToString(msg.getType())
+                + "_" + Communicator.valueToString(msg.getType(), msg.getValue()), null);
+        }
+    }
+
+    /**
      * Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
      * operation has started.
      */
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 56012c8..2877326 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -63,6 +63,7 @@
 import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.d2d.Communicator;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
@@ -77,6 +78,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -1078,7 +1080,8 @@
         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
             int dataNetType = phone.getServiceState().getDataNetworkType();
             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
-                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA ||
+                    dataNetType == TelephonyManager.NETWORK_TYPE_NR) {
                 state = phone.getServiceState().getDataRegistrationState();
             }
         }
@@ -2484,6 +2487,31 @@
         conference.addTelephonyConferenceListener(mTelephonyConferenceListener);
     }
 
+    /**
+     * Sends a test device to device message on the active call which supports it.
+     * Used exclusively from the telephony shell command to send a test message.
+     *
+     * @param message the message
+     * @param value the value
+     */
+    public void sendTestDeviceToDeviceMessage(int message, int value) {
+       getAllConnections().stream()
+               .filter(f -> f instanceof TelephonyConnection)
+               .forEach(t -> {
+                        TelephonyConnection tc = (TelephonyConnection) t;
+                        Communicator c = tc.getCommunicator();
+                        if (c == null) {
+                            Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
+                            return;
+                        }
+
+                        c.sendMessages(new HashSet<Communicator.Message>() {{
+                            add(new Communicator.Message(message, value));
+                        }});
+
+       });
+    }
+
     private PhoneAccountHandle adjustAccountHandle(Phone phone,
             PhoneAccountHandle origAccountHandle) {
         int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
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 5035284..02d2f8a 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -24,18 +24,15 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.role.IOnRoleHoldersChangedListener;
-import android.app.role.IRoleManager;
+import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
@@ -46,12 +43,8 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IInterface;
 import android.os.Looper;
 import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Telephony.SimInfo;
 import android.telephony.CarrierConfigManager;
@@ -60,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;
@@ -76,15 +70,12 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.Executor;
 
 /**
@@ -100,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"
@@ -115,7 +106,6 @@
     private static final String DEFAULT_MESSAGING_APP1 = "DMA1";
     private static final String DEFAULT_MESSAGING_APP2 = "DMA2";
 
-    private MockitoSession mSession;
     private RcsProvisioningMonitor mRcsProvisioningMonitor;
     private Handler mHandler;
     private HandlerThread mHandlerThread;
@@ -133,10 +123,9 @@
     private TelephonyRegistryManager mTelephonyRegistryManager;
     @Mock
     private CarrierConfigManager mCarrierConfigManager;
-    private IOnRoleHoldersChangedListener.Stub mRoleHolderChangedListener;
-    private RoleManager mRoleManager;
+    private OnRoleHoldersChangedListener mRoleHolderChangedListener;
     @Mock
-    private IRoleManager.Stub mIRoleManager;
+    private RcsProvisioningMonitor.RoleManagerAdapter mRoleManager;
     @Mock
     private ITelephony.Stub mITelephony;
     @Mock
@@ -147,6 +136,8 @@
     private Resources mResources;
     @Mock
     private PhoneGlobals mPhone;
+    @Mock
+    private IRcsConfigCallback mCallback;
 
     private Executor mExecutor = new Executor() {
         @Override
@@ -189,8 +180,6 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        replaceService(Context.ROLE_SERVICE, mIRoleManager);
-        mRoleManager = new RoleManager(mPhone);
         when(mPhone.getResources()).thenReturn(mResources);
         when(mResources.getBoolean(
                 eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true);
@@ -209,8 +198,6 @@
                 .thenReturn(mSubscriptionManager);
         when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE)))
                 .thenReturn(mTelephonyRegistryManager);
-        when(mPhone.getSystemService(eq(Context.ROLE_SERVICE)))
-                .thenReturn(mRoleManager);
 
         mBundle = new PersistableBundle();
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
@@ -237,15 +224,15 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                mRoleHolderChangedListener = (IOnRoleHoldersChangedListener.Stub)
-                        invocation.getArguments()[0];
+                mRoleHolderChangedListener = (OnRoleHoldersChangedListener)
+                        invocation.getArguments()[1];
                 return null;
             }
-        }).when(mIRoleManager).addOnRoleHoldersChangedListenerAsUser(
-                any(IOnRoleHoldersChangedListener.Stub.class), anyInt());
+        }).when(mRoleManager).addOnRoleHoldersChangedListenerAsUser(any(Executor.class),
+                any(OnRoleHoldersChangedListener.class), any(UserHandle.class));
         List<String> dmas = new ArrayList<>();
         dmas.add(DEFAULT_MESSAGING_APP1);
-        when(mIRoleManager.getRoleHoldersAsUser(any(), anyInt())).thenReturn(dmas);
+        when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas);
 
         mProvider = new SimInfoContentProvider(mPhone);
         mProvider.setCursor(mCursor);
@@ -268,9 +255,6 @@
             mRcsProvisioningMonitor = null;
         }
 
-        if (mSession != null) {
-            mSession.finishMocking();
-        }
         if (mLooper != null) {
             mLooper.destroy();
             mLooper = null;
@@ -452,7 +436,6 @@
         verify(mIImsConfig, times(1)).triggerRcsReconfiguration();
     }
 
-
     @Test
     @SmallTest
     public void testIsRcsVolteSingleRegistrationEnabled() throws Exception {
@@ -482,12 +465,63 @@
         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();
         }
         makeFakeActiveSubIds(subCount);
-        mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper());
+        mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(),
+                mRoleManager);
         mHandler = mRcsProvisioningMonitor.getHandler();
         try {
             mLooper = new TestableLooper(mHandler.getLooper());
@@ -512,27 +546,12 @@
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
     }
 
-    private void updateDefaultMessageApplication(String packageName) throws Exception {
+    private void updateDefaultMessageApplication(String packageName) {
         List<String> dmas = new ArrayList<>();
         dmas.add(packageName);
-        when(mIRoleManager.getRoleHoldersAsUser(any(), anyInt())).thenReturn(dmas);
-        mExecutor.execute(() -> {
-            try {
-                mRoleHolderChangedListener.onRoleHoldersChanged(
-                        RoleManager.ROLE_SMS, UserHandle.USER_ALL);
-            } catch (RemoteException e) {
-                logd("exception to call onRoleHoldersChanged " + e);
-            }
-        });
-    }
-
-    private void replaceService(final String serviceName,
-            final IInterface serviceInstance) throws Exception {
-        IBinder binder = mock(IBinder.class);
-        when(binder.queryLocalInterface(anyString())).thenReturn(serviceInstance);
-        Field field = ServiceManager.class.getDeclaredField("sCache");
-        field.setAccessible(true);
-        ((Map<String, IBinder>) field.get(null)).put(serviceName, binder);
+        when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas);
+        mExecutor.execute(() -> mRoleHolderChangedListener.onRoleHoldersChanged(
+                RoleManager.ROLE_SMS, UserHandle.ALL));
     }
 
     private void processAllMessages() {