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
Change-Id: I60026b26758ffe2aa233f1c110e23c24579bd1b4
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 03d5afe..3ad702a 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 2f37870..296e96b 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -108,12 +108,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;
@@ -9085,15 +9087,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);
         }
     }
 
@@ -9531,4 +9537,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;
+    }
 }