RCS Provisioning APIs for Single Registration

Bug: 154864150
Test: atest FrameworksTelephonyTests:com.telephony.ims.RcsConfigTest
Test: atest TeleServiceTests:RcsProvisioningMonitorTest
Test: atest TelephonyProviderTests:TelephonyDatabaseHelperTest
Test: atest CtsTelephonyTestCases:android.telephony.ims.cts.ImsServiceTest
Merged-In: I60026b26758ffe2aa233f1c110e23c24579bd1b4
Change-Id: I60026b26758ffe2aa233f1c110e23c24579bd1b4
diff --git a/res/values/config.xml b/res/values/config.xml
index f69120f..e6c578a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -303,4 +303,7 @@
         by millisecond. -1 - no release, 0 - release immediately,
         positive n - release in n milliseconds -->
     <integer name="config_gba_release_time">0</integer>
+
+    <!-- Whether or not to support RCS VoLTE single registration  -->
+    <bool name="config_rcsVolteSingleRegistrationEnabled">true</bool>
 </resources>
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 509aa57..ce8e9b3 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -360,6 +360,7 @@
                         defaultImsRcsPackage, PhoneFactory.getPhones().length,
                         new ImsFeatureBinderRepository());
                 mImsResolver.initialize();
+                RcsProvisioningMonitor.make(this);
             }
 
             // Start TelephonyDebugService After the default phone is created.
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 1fcf3b6..796efd0 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -107,12 +107,14 @@
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
@@ -9063,15 +9065,19 @@
             isCompressed) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                 mApp, subId, "notifyRcsAutoConfigurationReceived");
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+        if (!isImsAvailableOnDevice()) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
         try {
-            IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
-            if (configBinder == null) {
-                Rlog.e(LOG_TAG, "null result for getImsConfig");
-            } else {
-                configBinder.notifyRcsAutoConfigurationReceived(config, isCompressed);
-            }
-        } catch (RemoteException e) {
-            Rlog.e(LOG_TAG, "fail to getImsConfig " + e.getMessage());
+            RcsProvisioningMonitor.getInstance().updateConfig(subId, config, isCompressed);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
@@ -9497,4 +9503,184 @@
         }
         return instance;
     }
+
+    /**
+     * indicate whether the device and the carrier can support
+     * RCS VoLTE single registration.
+     */
+    @Override
+    public boolean isRcsVolteSingleRegistrationCapable(int subId) {
+        enforceReadPrivilegedPermission("isRcsVolteSingleRegistrationCapable");
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            RcsProvisioningMonitor rpm = RcsProvisioningMonitor.getInstance();
+            if (rpm != null) {
+                return rpm.isRcsVolteSingleRegistrationEnabled(subId);
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Register RCS provisioning callback.
+     */
+    @Override
+    public void registerRcsProvisioningChangedCallback(int subId,
+            IRcsConfigCallback callback) {
+        enforceReadPrivilegedPermission("registerRcsProvisioningChangedCallback");
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+        if (!isImsAvailableOnDevice()) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
+                    .addRcsProvisioningCallbackForSubscription(callback, subId);
+        } catch (ImsException e) {
+            throw new ServiceSpecificException(e.getCode());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Unregister RCS provisioning callback.
+     */
+    @Override
+    public void unregisterRcsProvisioningChangedCallback(int subId,
+            IRcsConfigCallback callback) {
+        enforceReadPrivilegedPermission("unregisterRcsProvisioningChangedCallback");
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+        if (!isImsAvailableOnDevice()) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        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.");
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * trigger RCS reconfiguration.
+     */
+    public void triggerRcsReconfiguration(int subId) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "triggerRcsReconfiguration");
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+        if (!isImsAvailableOnDevice()) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            RcsProvisioningMonitor.getInstance().requestReconfig(subId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Provide the client configuration parameters of the RCS application.
+     */
+    public void setRcsClientConfiguration(int subId, RcsClientConfiguration rcc) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "setRcsClientConfiguration");
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+        if (!isImsAvailableOnDevice()) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+
+        try {
+            IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
+            if (configBinder == null) {
+                Rlog.e(LOG_TAG, "null result for setRcsClientConfiguration");
+            } else {
+                configBinder.setRcsClientConfiguration(rcc);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Overrides the config of RCS VoLTE single registration enabled for the device.
+     */
+    @Override
+    public void setDeviceSingleRegistrationEnabledOverride(String enabledStr) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "setDeviceSingleRegistrationEnabledOverride");
+        enforceModifyPermission();
+
+        Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+                : Boolean.parseBoolean(enabledStr);
+        RcsProvisioningMonitor.getInstance().overrideDeviceSingleRegistrationEnabled(enabled);
+    }
+
+    /**
+     * Gets the config of RCS VoLTE single registration enabled for the device.
+     */
+    @Override
+    public boolean getDeviceSingleRegistrationEnabled() {
+        enforceReadPrivilegedPermission("getDeviceSingleRegistrationEnabled");
+        return RcsProvisioningMonitor.getInstance().getDeviceSingleRegistrationEnabled();
+    }
+
+    /**
+     * Overrides the config of RCS VoLTE single registration enabled for the carrier/subscription.
+     */
+    @Override
+    public boolean setCarrierSingleRegistrationEnabledOverride(int subId, String enabledStr) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "setCarrierSingleRegistrationEnabledOverride");
+        enforceModifyPermission();
+
+        Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+                : Boolean.parseBoolean(enabledStr);
+        return RcsProvisioningMonitor.getInstance().overrideCarrierSingleRegistrationEnabled(
+                subId, enabled);
+    }
+
+    /**
+     * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription.
+     */
+    @Override
+    public boolean getCarrierSingleRegistrationEnabled(int subId) {
+        enforceReadPrivilegedPermission("getCarrierSingleRegistrationEnabled");
+        return RcsProvisioningMonitor.getInstance().getCarrierSingleRegistrationEnabled(subId);
+    }
 }
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
new file mode 100644
index 0000000..8e4d35e
--- /dev/null
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsConfig;
+import android.telephony.ims.aidl.IImsConfig;
+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.concurrent.ConcurrentHashMap;
+
+/**
+ * Class to monitor RCS Provisioning Status
+ */
+public class RcsProvisioningMonitor {
+    private static final String TAG = "RcsProvisioningMonitor";
+    private static final boolean DBG = Build.IS_ENG;
+
+    private static final int EVENT_SUB_CHANGED = 1;
+    private static final int EVENT_DMA_CHANGED = 2;
+    private static final int EVENT_CC_CHANGED  = 3;
+    private static final int EVENT_CONFIG_RECEIVED = 4;
+    private static final int EVENT_RECONFIG_REQUEST = 5;
+    private static final int EVENT_DEVICE_CONFIG_OVERRIDE = 6;
+    private static final int EVENT_CARRIER_CONFIG_OVERRIDE = 7;
+
+    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 =
+            new ConcurrentHashMap<>();
+    private Boolean mDeviceSingleRegistrationEnabledOverride;
+    private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
+            new HashMap<>();
+    private String mDmaPackageName;
+
+    private final CarrierConfigManager mCarrierConfigManager;
+    private final DmaChangedListener mDmaChangedListener;
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyRegistryManager mTelephonyRegistryManager;
+
+    private static RcsProvisioningMonitor sInstance;
+
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
+                mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(
+                    intent.getAction())) {
+                int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                logv("Carrier-config changed for sub : " + subId);
+                if (SubscriptionManager.isValidSubscriptionId(subId)
+                        && !mHandler.hasMessages(EVENT_CC_CHANGED)) {
+                    mHandler.sendEmptyMessage(EVENT_CC_CHANGED);
+                }
+            }
+        }
+    };
+
+    private final class DmaChangedListener implements OnRoleHoldersChangedListener {
+        private RoleManager mRoleManager;
+
+        @Override
+        public void onRoleHoldersChanged(String role, UserHandle user) {
+            if (RoleManager.ROLE_SMS.equals(role)) {
+                logv("default messaging application changed.");
+                String packageName = getDmaPackageName();
+                mHandler.sendEmptyMessage(EVENT_DMA_CHANGED);
+            }
+        }
+
+        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);
+                }
+            }
+        }
+
+        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);
+                }
+            }
+        }
+    }
+
+    private final class MyHandler extends Handler {
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_SUB_CHANGED:
+                    onSubChanged();
+                    break;
+                case EVENT_DMA_CHANGED:
+                    onDefaultMessagingApplicationChanged();
+                    break;
+                case EVENT_CC_CHANGED:
+                    onCarrierConfigChange();
+                    break;
+                case EVENT_CONFIG_RECEIVED:
+                    onConfigReceived(msg.arg1, (byte[]) msg.obj, msg.arg2 == 1);
+                    break;
+                case EVENT_RECONFIG_REQUEST:
+                    onReconfigRequest(msg.arg1);
+                    break;
+                case EVENT_DEVICE_CONFIG_OVERRIDE:
+                    Boolean deviceEnabled = (Boolean) msg.obj;
+                    if (!booleanEquals(deviceEnabled, mDeviceSingleRegistrationEnabledOverride)) {
+                        mDeviceSingleRegistrationEnabledOverride = deviceEnabled;
+                        onCarrierConfigChange();
+                    }
+                    break;
+                case EVENT_CARRIER_CONFIG_OVERRIDE:
+                    Boolean carrierEnabledOverride = (Boolean) msg.obj;
+                    Boolean carrierEnabled = mCarrierSingleRegistrationEnabledOverride.put(
+                            msg.arg1, carrierEnabledOverride);
+                    if (!booleanEquals(carrierEnabledOverride, carrierEnabled)) {
+                        onCarrierConfigChange();
+                    }
+                    break;
+                default:
+                    loge("Unhandled event " + msg.what);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public RcsProvisioningMonitor(PhoneGlobals app, Looper looper) {
+        mPhone = app;
+        mHandler = new MyHandler(looper);
+        mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
+        mSubscriptionManager = mPhone.getSystemService(SubscriptionManager.class);
+        mTelephonyRegistryManager = mPhone.getSystemService(TelephonyRegistryManager.class);
+        mDmaPackageName = getDmaPackageName();
+        logv("DMA is " + mDmaPackageName);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mPhone.registerReceiver(mReceiver, filter);
+        mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+        mDmaChangedListener = new DmaChangedListener();
+        mDmaChangedListener.register();
+        //initialize configs for all active sub
+        onSubChanged();
+    }
+
+    /**
+     * create an instance
+     */
+    public static RcsProvisioningMonitor make(PhoneGlobals app) {
+        if (sInstance == null) {
+            logd("RcsProvisioningMonitor created.");
+            HandlerThread handlerThread = new HandlerThread(TAG);
+            handlerThread.start();
+            sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper());
+        }
+        return sInstance;
+    }
+
+    /**
+     * get the instance
+     */
+    public static RcsProvisioningMonitor getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * destroy the instance
+     */
+    @VisibleForTesting
+    public void destroy() {
+        logd("destroy it.");
+        mDmaChangedListener.unregister();
+        mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+        mPhone.unregisterReceiver(mReceiver);
+        mHandler.getLooper().quit();
+    }
+
+    /**
+     * get the handler
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Gets the config for a subscription
+     */
+    @VisibleForTesting
+    public byte[] getConfig(int subId) {
+        return mConfigs.get(subId);
+    }
+
+    /**
+     * 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;
+        }
+        return false;
+    }
+
+    /**
+     * Called when the new rcs config is received
+     */
+    public void updateConfig(int subId, byte[] config, boolean isCompressed) {
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_CONFIG_RECEIVED, subId, isCompressed ? 1 : 0, config));
+    }
+
+    /**
+     * Called when the application needs rcs re-config
+     */
+    public void requestReconfig(int subId) {
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RECONFIG_REQUEST, subId, 0));
+    }
+
+    /**
+     * override the device config whether single registration is enabled
+     */
+    public void overrideDeviceSingleRegistrationEnabled(Boolean enabled) {
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_DEVICE_CONFIG_OVERRIDE, enabled));
+    }
+
+    /**
+     * Overrides the carrier config whether single registration is enabled
+     */
+    public boolean overrideCarrierSingleRegistrationEnabled(int subId, Boolean enabled) {
+        if (!mSingleRegistrations.containsKey(subId)) {
+            return false;
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_CARRIER_CONFIG_OVERRIDE, subId, 0, enabled));
+        return true;
+    }
+
+    /**
+     * 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;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the carrier config whether single registration is enabled
+     */
+    public boolean getCarrierSingleRegistrationEnabled(int subId) {
+        if (mSingleRegistrations.containsKey(subId)) {
+            return (mSingleRegistrations.get(subId)
+                    & ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE) == 0;
+        }
+        return false;
+    }
+
+    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) -> {
+                if (isAcsUsed(k)) {
+                    logv("acs used, trigger to re-configure.");
+                    notifyRcsAutoConfigurationRemoved(k);
+                    triggerRcsReconfiguration(k);
+                } else {
+                    logv("acs not used, notify.");
+                    notifyRcsAutoConfigurationReceived(k, v, false);
+                }
+            });
+        }
+    }
+
+    private void notifyRcsAutoConfigurationReceived(int subId, byte[] config,
+            boolean isCompressed) {
+        IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
+        if (imsConfig != null) {
+            try {
+                imsConfig.notifyRcsAutoConfigurationReceived(config, isCompressed);
+            } catch (RemoteException e) {
+                loge("fail to notify rcs configuration received!");
+            }
+        } else {
+            logd("getIImsConfig returns null.");
+        }
+    }
+
+    private void notifyRcsAutoConfigurationRemoved(int subId) {
+        IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
+        if (imsConfig != null) {
+            try {
+                imsConfig.notifyRcsAutoConfigurationRemoved();
+            } catch (RemoteException e) {
+                loge("fail to notify rcs configuration removed!");
+            }
+        } else {
+            logd("getIImsConfig returns null.");
+        }
+    }
+
+    private void triggerRcsReconfiguration(int subId) {
+        IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
+        if (imsConfig != null) {
+            try {
+                imsConfig.triggerRcsReconfiguration();
+            } catch (RemoteException e) {
+                loge("fail to trigger rcs reconfiguration!");
+            }
+        } else {
+            logd("getIImsConfig returns null.");
+        }
+    }
+
+    private boolean isAcsUsed(int subId) {
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
+        if (b == null) {
+            return false;
+        }
+        return b.getBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL);
+    }
+
+    private boolean isSingleRegistrationRequiredByCarrier(int subId) {
+        Boolean enabledByOverride = mCarrierSingleRegistrationEnabledOverride.get(subId);
+        if (enabledByOverride != null) {
+            return enabledByOverride;
+        }
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
+        if (b == null) {
+            return false;
+        }
+        return b.getBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL);
+    }
+
+    private int getSingleRegistrationCapableValue(int subId) {
+        boolean isSingleRegistrationEnabledOnDevice =
+                mDeviceSingleRegistrationEnabledOverride != null
+                ? mDeviceSingleRegistrationEnabledOverride
+                : mPhone.getResources().getBoolean(R.bool.config_rcsVolteSingleRegistrationEnabled);
+
+        int value = (isSingleRegistrationEnabledOnDevice ? 0
+                : ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) | (
+                isSingleRegistrationRequiredByCarrier(subId) ? 0
+                : ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE);
+        logv("SingleRegistrationCapableValue : " + value);
+        return value;
+    }
+
+    private void onCarrierConfigChange() {
+        logv("onCarrierConfigChange");
+        mConfigs.forEach((subId, config) -> {
+            int value = getSingleRegistrationCapableValue(subId);
+            if (value != mSingleRegistrations.get(subId)) {
+                mSingleRegistrations.put(subId, value);
+                notifyDmaForSub(subId);
+            }
+        });
+    }
+
+    private void onSubChanged() {
+        final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+        final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mConfigs.keySet());
+
+        for (int i : activeSubs) {
+            subsToBeDeactivated.remove(i);
+            if (!mConfigs.containsKey(i)) {
+                byte[] data = RcsConfig.loadRcsConfigForSub(mPhone, i, false);
+                logv("new config is created for sub : " + i);
+                mConfigs.put(i, data);
+                notifyRcsAutoConfigurationReceived(i, data, false);
+                mSingleRegistrations.put(i, getSingleRegistrationCapableValue(i));
+                notifyDmaForSub(i);
+            }
+        }
+
+        subsToBeDeactivated.forEach(i -> {
+            mConfigs.remove(i);
+            notifyRcsAutoConfigurationRemoved(i);
+        });
+    }
+
+    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);
+        RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
+        notifyRcsAutoConfigurationReceived(subId, config, isCompressed);
+    }
+
+    private void onReconfigRequest(int subId) {
+        logv("onReconfigRequest, subId:" + subId);
+        mConfigs.remove(subId);
+        RcsConfig.updateConfigForSub(mPhone, subId, null, true);
+        notifyRcsAutoConfigurationRemoved(subId);
+        triggerRcsReconfiguration(subId);
+    }
+
+    private void notifyDmaForSub(int subId) {
+        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));
+        logv("notify " + intent);
+        mPhone.sendBroadcast(intent);
+    }
+
+    private IImsConfig getIImsConfig(int subId, int feature) {
+        return mPhone.getImsResolver().getImsConfig(
+                SubscriptionManager.getSlotIndex(subId), feature);
+    }
+
+    private String getDmaPackageName() {
+        try {
+            return CollectionUtils.firstOrNull(mPhone.getSystemService(RoleManager.class)
+                    .getRoleHolders(RoleManager.ROLE_SMS));
+        } catch (RuntimeException e) {
+            loge("Could not get dma name due to " + e);
+            return null;
+        }
+    }
+
+    private static boolean booleanEquals(Boolean val1, Boolean val2) {
+        return (val1 == null && val2 == null)
+                || (Boolean.TRUE.equals(val1) && Boolean.TRUE.equals(val2))
+                || (Boolean.FALSE.equals(val1) && Boolean.FALSE.equals(val2));
+    }
+
+    private static void logv(String msg) {
+        if (DBG) {
+            Rlog.d(TAG, msg);
+        }
+    }
+
+    private static void logd(String msg) {
+        Rlog.d(TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 8d67092..ed719b6 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -87,6 +87,12 @@
     private static final String GBA_SET_RELEASE_TIME = "set-release";
     private static final String GBA_GET_RELEASE_TIME = "get-release";
 
+    private static final String SINGLE_REGISTATION_CONFIG = "src";
+    private static final String SRC_SET_DEVICE_ENABLED = "set-device-enabled";
+    private static final String SRC_GET_DEVICE_ENABLED = "get-device-enabled";
+    private static final String SRC_SET_CARRIER_ENABLED = "set-carrier-enabled";
+    private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -169,6 +175,8 @@
                 return handleEndBlockSuppressionCommand();
             case GBA_SUBCOMMAND:
                 return handleGbaCommand();
+            case SINGLE_REGISTATION_CONFIG:
+                return handleSingleRegistrationConfigCommand();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -193,12 +201,15 @@
         pw.println("    Carrier Config Commands.");
         pw.println("  gba");
         pw.println("    GBA Commands.");
+        pw.println("  src");
+        pw.println("    RCS VoLTE Single Registration Config Commands.");
         onHelpIms();
         onHelpEmergencyNumber();
         onHelpEndBlockSupperssion();
         onHelpDataTestMode();
         onHelpCc();
         onHelpGba();
+        onHelpSrc();
     }
 
     private void onHelpIms() {
@@ -330,6 +341,27 @@
         pw.println("          is specified, it will choose the default voice SIM slot.");
     }
 
+    private void onHelpSrc() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("RCS VoLTE Single Registration Config Commands:");
+        pw.println("  src set-device-enabled true|false|null");
+        pw.println("    Sets the device config for RCS VoLTE single registration to the value.");
+        pw.println("    The value could be true, false, or null(undefined).");
+        pw.println("  src get-device-enabled");
+        pw.println("    Gets the device config for RCS VoLTE single registration.");
+        pw.println("  src set-carrier-enabled [-s SLOT_ID] true|false|null");
+        pw.println("    Sets the carrier config for RCS VoLTE single registration to the value.");
+        pw.println("    The value could be true, false, or null(undefined).");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to set the config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  src get-carrier-enabled [-s SLOT_ID]");
+        pw.println("    Gets the carrier config for RCS VoLTE single registration.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read the config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+    }
+
     private int handleImsCommand() {
         String arg = getNextArg();
         if (arg == null) {
@@ -1418,4 +1450,112 @@
         getOutPrintWriter().println(result);
         return 0;
     }
+
+    private int handleSingleRegistrationConfigCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpSrc();
+            return 0;
+        }
+
+        switch (arg) {
+            case SRC_SET_DEVICE_ENABLED: {
+                return handleSrcSetDeviceEnabledCommand();
+            }
+            case SRC_GET_DEVICE_ENABLED: {
+                return handleSrcGetDeviceEnabledCommand();
+            }
+            case SRC_SET_CARRIER_ENABLED: {
+                return handleSrcSetCarrierEnabledCommand();
+            }
+            case SRC_GET_CARRIER_ENABLED: {
+                return handleSrcGetCarrierEnabledCommand();
+            }
+        }
+
+        return -1;
+    }
+
+    private int handleSrcSetDeviceEnabledCommand() {
+        String enabledStr = getNextArg();
+        if (enabledStr == null) {
+            return -1;
+        }
+
+        try {
+            mInterface.setDeviceSingleRegistrationEnabledOverride(enabledStr);
+            if (VDBG) {
+                Log.v(LOG_TAG, "src set-device-enabled " + enabledStr + ", done");
+            }
+            getOutPrintWriter().println("Done");
+        } catch (NumberFormatException | RemoteException e) {
+            Log.w(LOG_TAG, "src set-device-enabled " + enabledStr + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleSrcGetDeviceEnabledCommand() {
+        boolean result = false;
+        try {
+            result = mInterface.getDeviceSingleRegistrationEnabled();
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "src get-device-enabled, returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
+
+    private int handleSrcSetCarrierEnabledCommand() {
+        //the release time value could be -1
+        int subId = getRemainingArgsCount() > 1 ? getSubId("src set-carrier-enabled")
+                : SubscriptionManager.getDefaultSubscriptionId();
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String enabledStr = getNextArg();
+        if (enabledStr == null) {
+            return -1;
+        }
+
+        try {
+            boolean result =
+                    mInterface.setCarrierSingleRegistrationEnabledOverride(subId, enabledStr);
+            if (VDBG) {
+                Log.v(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
+                        + enabledStr + ", result=" + result);
+            }
+            getOutPrintWriter().println(result);
+        } catch (NumberFormatException | RemoteException e) {
+            Log.w(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
+                    + enabledStr + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleSrcGetCarrierEnabledCommand() {
+        int subId = getSubId("src get-carrier-enabled");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        boolean result = false;
+        try {
+            result = mInterface.getCarrierSingleRegistrationEnabled(subId);
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "src get-carrier-enabled -s " + subId + ", returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 7ed234e..13c0dc8 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -37,6 +37,7 @@
         "mockito-target-minus-junit4",
         "androidx.test.espresso.core",
         "truth-prebuilt",
+	"testables",
     ],
 
     test_suites: [
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
new file mode 100644
index 0000000..7ef9768
--- /dev/null
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+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.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.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+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;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsConfig;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.feature.ImsFeature;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.ims.ImsResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+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;
+
+/**
+ * Unit tests for RcsProvisioningMonitor
+ */
+public class RcsProvisioningMonitorTest {
+    private static final String TAG = "RcsProvisioningMonitorTest";
+    private static final String SAMPLE_CONFIG = "<RCSConfig>\n"
+            + "\t<rcsVolteSingleRegistration>1</rcsVolteSingleRegistration>\n"
+            + "\t<SERVICES>\n"
+            + "\t\t<SupportedRCSProfileVersions>UP_2.0</SupportedRCSProfileVersions>\n"
+            + "\t\t<ChatAuth>1</ChatAuth>\n"
+            + "\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<Ext>\n"
+            + "\t\t\t<DataOff>\n"
+            + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n"
+            + "\t\t\t\t<fileTransferDataOff>1</fileTransferDataOff>\n"
+            + "\t\t\t\t<mmsDataOff>1</mmsDataOff>\n"
+            + "\t\t\t\t<syncDataOff>1</syncDataOff>\n"
+            + "\t\t\t</DataOff>\n"
+            + "\t\t</Ext>\n"
+            + "\t</SERVICES>\n"
+            + "</RCSConfig>";
+    private static final int FAKE_SUB_ID_BASE = 0x0FFFFFF0;
+    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;
+    private TestableLooper mLooper;
+    private PersistableBundle mBundle;
+    private MockContentResolver mContentResolver = new MockContentResolver();
+    private SimInfoContentProvider mProvider;
+    private BroadcastReceiver mReceiver;
+    @Mock
+    private Cursor mCursor;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
+    @Mock
+    private TelephonyRegistryManager mTelephonyRegistryManager;
+    @Mock
+    private CarrierConfigManager mCarrierConfigManager;
+    private IOnRoleHoldersChangedListener.Stub mRoleHolderChangedListener;
+    private RoleManager mRoleManager;
+    @Mock
+    private IRoleManager.Stub mIRoleManager;
+    @Mock
+    private ITelephony.Stub mITelephony;
+    @Mock
+    private ImsResolver mImsResolver;
+    @Mock
+    private IImsConfig.Stub mIImsConfig;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private PhoneGlobals mPhone;
+
+    private Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    private class SimInfoContentProvider extends MockContentProvider {
+        private Cursor mCursor;
+
+        SimInfoContentProvider(Context context) {
+            super(context);
+        }
+
+        public void setCursor(Cursor cursor) {
+            mCursor = cursor;
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection,
+                String[] selectionArgs, String sortOrder) {
+            return mCursor;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values,
+                String selection, String[] selectionArgs) {
+            return 1;
+        }
+    }
+
+    @Before
+    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);
+        when(mPhone.getMainExecutor()).thenReturn(mExecutor);
+        when(mPhone.getSystemServiceName(eq(CarrierConfigManager.class)))
+                .thenReturn(Context.CARRIER_CONFIG_SERVICE);
+        when(mPhone.getSystemServiceName(eq(SubscriptionManager.class)))
+                .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class)))
+                .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE);
+        when(mPhone.getSystemServiceName(eq(RoleManager.class)))
+                .thenReturn(Context.ROLE_SERVICE);
+        when(mPhone.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE)))
+                .thenReturn(mCarrierConfigManager);
+        when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+                .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);
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mReceiver = (BroadcastReceiver) invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mPhone).registerReceiver(any(BroadcastReceiver.class), any());
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mSubChangedListener = (SubscriptionManager.OnSubscriptionsChangedListener)
+                        invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class),
+                any());
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mRoleHolderChangedListener = (IOnRoleHoldersChangedListener.Stub)
+                        invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mIRoleManager).addOnRoleHoldersChangedListenerAsUser(
+                any(IOnRoleHoldersChangedListener.Stub.class), anyInt());
+        List<String> dmas = new ArrayList<>();
+        dmas.add(DEFAULT_MESSAGING_APP1);
+        when(mIRoleManager.getRoleHoldersAsUser(any(), anyInt())).thenReturn(dmas);
+
+        mProvider = new SimInfoContentProvider(mPhone);
+        mProvider.setCursor(mCursor);
+        mContentResolver.addProvider(SimInfo.CONTENT_URI.getAuthority(), mProvider);
+        when(mPhone.getContentResolver()).thenReturn(mContentResolver);
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndexOrThrow(any())).thenReturn(1);
+        when(mCursor.getBlob(anyInt())).thenReturn(
+                RcsConfig.compressGzip(SAMPLE_CONFIG.getBytes()));
+        when(mPhone.getImsResolver()).thenReturn(mImsResolver);
+        when(mImsResolver.getImsConfig(anyInt(), anyInt())).thenReturn(mIImsConfig);
+        mHandlerThread = new HandlerThread("RcsProvisioningMonitorTest");
+        mHandlerThread.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mRcsProvisioningMonitor != null) {
+            mRcsProvisioningMonitor.destroy();
+            mRcsProvisioningMonitor = null;
+        }
+
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+        if (mLooper != null) {
+            mLooper.destroy();
+            mLooper = null;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testInit() throws Exception {
+        createMonitor(3);
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        for (int i = 0; i < 3; i++) {
+            assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+                    mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
+        }
+
+        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture());
+        Intent capturedIntent = captorIntent.getAllValues().get(1);
+        assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
+                capturedIntent.getAction());
+        PhoneGlobals.getInstance().getImsResolver();
+        verify(mPhone, atLeastOnce()).getImsResolver();
+        verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
+    public void testSubInfoChanged() throws Exception {
+        createMonitor(3);
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+        processAllMessages();
+        for (int i = 0; i < 3; i++) {
+            assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+                    mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
+        }
+        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture());
+        Intent capturedIntent = captorIntent.getAllValues().get(1);
+        assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
+                capturedIntent.getAction());
+        verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+
+        makeFakeActiveSubIds(1);
+        mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+        processAllMessages();
+
+        for (int i = 1; i < 3; i++) {
+            assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i));
+        }
+        verify(mIImsConfig, times(2)).notifyRcsAutoConfigurationRemoved();
+    }
+
+    @Test
+    @SmallTest
+    public void testDefaultMessagingApplicationChanged() throws Exception {
+        createMonitor(1);
+        updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, false);
+        processAllMessages();
+        verify(mIImsConfig, atLeastOnce())
+                .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, true);
+        updateDefaultMessageApplication(DEFAULT_MESSAGING_APP1);
+        processAllMessages();
+        verify(mIImsConfig, atLeastOnce()).triggerRcsReconfiguration();
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigChanged() throws Exception {
+        createMonitor(1);
+        when(mResources.getBoolean(
+                eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true);
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture());
+        Intent capturedIntent = captorIntent.getValue();
+        assertEquals(capturedIntent.getAction(),
+                ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+        assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+                ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+        assertEquals(ProvisioningManager.STATUS_CAPABLE,
+                capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture());
+        capturedIntent = captorIntent.getValue();
+        assertEquals(capturedIntent.getAction(),
+                ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+        assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+                ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+        assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE,
+                capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+
+
+        when(mResources.getBoolean(
+                eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(false);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture());
+        capturedIntent = captorIntent.getValue();
+        assertEquals(capturedIntent.getAction(),
+                ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+        assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+                ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+        assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE
+                | ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE,
+                capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateConfig() throws Exception {
+        createMonitor(1);
+        final ArgumentCaptor<byte[]> argumentBytes = ArgumentCaptor.forClass(byte[].class);
+
+        mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
+        processAllMessages();
+
+        verify(mImsResolver, atLeastOnce()).getImsConfig(
+                anyInt(), eq(ImsFeature.FEATURE_RCS));
+        verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationReceived(
+                argumentBytes.capture(), eq(false));
+        assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), argumentBytes.getValue()));
+    }
+
+    @Test
+    @SmallTest
+    public void testRequestReconfig() throws Exception {
+        createMonitor(1);
+
+        mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE);
+        processAllMessages();
+
+        verify(mImsResolver, atLeastOnce()).getImsConfig(
+                anyInt(), eq(ImsFeature.FEATURE_RCS));
+        verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved();
+        verify(mIImsConfig, times(1)).triggerRcsReconfiguration();
+    }
+
+
+    @Test
+    @SmallTest
+    public void testIsRcsVolteSingleRegistrationEnabled() throws Exception {
+        createMonitor(1);
+
+        when(mResources.getBoolean(
+                eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+
+        when(mResources.getBoolean(
+                eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(false);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+    }
+
+    private void createMonitor(int subCount) {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        makeFakeActiveSubIds(subCount);
+        mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper());
+        mHandler = mRcsProvisioningMonitor.getHandler();
+        try {
+            mLooper = new TestableLooper(mHandler.getLooper());
+        } catch (Exception e) {
+            logd("Unable to create looper from handler.");
+        }
+        processAllMessages();
+    }
+
+    private void broadcastCarrierConfigChange(int subId) {
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        mExecutor.execute(() -> {
+            mReceiver.onReceive(mPhone, intent);
+        });
+    }
+
+    private void makeFakeActiveSubIds(int count) {
+        final int[] subIds = new int[count];
+        for (int i = 0; i < count; i++) {
+            subIds[i] = FAKE_SUB_ID_BASE + i;
+        }
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
+    }
+
+    private void updateDefaultMessageApplication(String packageName) throws Exception {
+        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);
+    }
+
+
+    private void processAllMessages() {
+        while (!mLooper.getLooper().getQueue().isIdle()) {
+            mLooper.processAllMessages();
+        }
+    }
+
+    private static void logd(String str) {
+        Log.d(TAG, str);
+    }
+}