Notify rcs config as needed when RCS service ready, and support test mode

Bug: 180503768
Bug: 181054754
Test: atest TeleServiceTests:com.android.phone.RcsProvisioningMonitorTest
Merged-In: Icef61a850c679334a8279f0e28e5110f867b75c1
Change-Id: Icef61a850c679334a8279f0e28e5110f867b75c1
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 111a38c..eac13bb 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -9820,6 +9820,28 @@
     }
 
     /**
+     * Enables or disables the test mode for RCS VoLTE single registration.
+     */
+    @Override
+    public void setRcsSingleRegistrationTestModeEnabled(boolean enabled) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "setRcsSingleRegistrationTestModeEnabled");
+
+        RcsProvisioningMonitor.getInstance().setTestModeEnabled(enabled);
+    }
+
+    /**
+     * Gets the test mode for RCS VoLTE single registration.
+     */
+    @Override
+    public boolean getRcsSingleRegistrationTestModeEnabled() {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "getRcsSingleRegistrationTestModeEnabled");
+
+        return RcsProvisioningMonitor.getInstance().getTestModeEnabled();
+    }
+
+    /**
      * Overrides the config of RCS VoLTE single registration enabled for the device.
      */
     @Override
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 79310ef..13dddec 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -38,15 +38,20 @@
 import android.telephony.ims.RcsConfig;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IRcsConfigCallback;
-import android.telephony.ims.feature.ImsFeature;
 import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.SparseArray;
 
+import com.android.ims.FeatureConnector;
+import com.android.ims.FeatureUpdates;
+import com.android.ims.RcsFeatureManager;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.HandlerExecutor;
 import com.android.internal.util.CollectionUtils;
 import com.android.telephony.Rlog;
 
+import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -66,6 +71,7 @@
     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 static final int EVENT_RESET = 8;
 
     private final PhoneGlobals mPhone;
     private final Handler mHandler;
@@ -76,12 +82,15 @@
     private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
             new HashMap<>();
     private String mDmaPackageName;
+    private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
+    private volatile boolean mTestModeEnabled;
 
     private final CarrierConfigManager mCarrierConfigManager;
     private final DmaChangedListener mDmaChangedListener;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyRegistryManager mTelephonyRegistryManager;
     private final RoleManagerAdapter mRoleManager;
+    private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
 
     private static RcsProvisioningMonitor sInstance;
 
@@ -146,6 +155,7 @@
 
         @Override
         public void handleMessage(Message msg) {
+            logv("handleMessage: " + msg);
             switch (msg.what) {
                 case EVENT_SUB_CHANGED:
                     onSubChanged();
@@ -177,6 +187,9 @@
                         onCarrierConfigChange();
                     }
                     break;
+                case EVENT_RESET:
+                    reset();
+                    break;
                 default:
                     loge("Unhandled event " + msg.what);
             }
@@ -187,13 +200,20 @@
         private int mSubId;
         private volatile int mSingleRegistrationCapability;
         private volatile byte[] mConfig;
-        private HashSet<IRcsConfigCallback> mRcsConfigCallbacks;
+        private ArraySet<IRcsConfigCallback> mRcsConfigCallbacks;
+        private IImsConfig mIImsConfig;
+        private boolean mHasReconfigRequest;
 
         RcsProvisioningInfo(int subId, int singleRegistrationCapability, byte[] config) {
             mSubId = subId;
             mSingleRegistrationCapability = singleRegistrationCapability;
             mConfig = config;
-            mRcsConfigCallbacks = new HashSet<>();
+            mRcsConfigCallbacks = new ArraySet<>();
+            registerRcsFeatureListener(this);
+        }
+
+        int getSubId() {
+            return mSubId;
         }
 
         void setSingleRegistrationCapability(int singleRegistrationCapability) {
@@ -205,7 +225,14 @@
         }
 
         void setConfig(byte[] config) {
-            mConfig = config;
+            if (!Arrays.equals(mConfig, config)) {
+                mConfig = config;
+                if (mConfig != null) {
+                    notifyRcsAutoConfigurationReceived();
+                } else {
+                    notifyRcsAutoConfigurationRemoved();
+                }
+            }
         }
 
         byte[] getConfig() {
@@ -213,15 +240,14 @@
         }
 
         boolean addRcsConfigCallback(IRcsConfigCallback cb) {
-            IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
-            if (imsConfig == null) {
+            if (mIImsConfig == null) {
                 logd("fail to addRcsConfigCallback as imsConfig is null");
                 return false;
             }
 
             synchronized (mRcsConfigCallbacks) {
                 try {
-                    imsConfig.addRcsConfigCallback(cb);
+                    mIImsConfig.addRcsConfigCallback(cb);
                 } catch (RemoteException e) {
                     loge("fail to addRcsConfigCallback due to " + e);
                     return false;
@@ -233,12 +259,11 @@
 
         boolean removeRcsConfigCallback(IRcsConfigCallback cb) {
             boolean result = true;
-            IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
 
             synchronized (mRcsConfigCallbacks) {
-                if (imsConfig != null) {
+                if (mIImsConfig != null) {
                     try {
-                        imsConfig.removeRcsConfigCallback(cb);
+                        mIImsConfig.removeRcsConfigCallback(cb);
                     } catch (RemoteException e) {
                         loge("fail to removeRcsConfigCallback due to " + e);
                     }
@@ -257,16 +282,89 @@
             return result;
         }
 
+        void triggerRcsReconfiguration() {
+            if (mIImsConfig != null) {
+                try {
+                    logv("triggerRcsReconfiguration for sub:" + mSubId);
+                    mIImsConfig.triggerRcsReconfiguration();
+                    mHasReconfigRequest = false;
+                } catch (RemoteException e) {
+                    loge("triggerRcsReconfiguration failed due to " + e);
+                }
+            } else {
+                logd("triggerRcsReconfiguration failed due to IImsConfig null.");
+                mHasReconfigRequest = true;
+            }
+        }
+
+        void destroy() {
+            unregisterRcsFeatureListener(this);
+            clear();
+            mIImsConfig = null;
+            mRcsConfigCallbacks = null;
+        }
+
         void clear() {
             setConfig(null);
+            clearCallbacks();
+        }
+
+        void onRcsStatusChanged(IImsConfig binder) {
+            logv("onRcsStatusChanged for sub:" + mSubId + ", IImsConfig?" + binder);
+            if (mIImsConfig != binder) {
+                mIImsConfig = binder;
+                if (mIImsConfig != null) {
+                    if (mHasReconfigRequest) {
+                        triggerRcsReconfiguration();
+                    } else {
+                        notifyRcsAutoConfigurationReceived();
+                    }
+                } else {
+                    // clear callbacks if rcs disconnected
+                    clearCallbacks();
+                }
+            }
+        }
+
+        private void notifyRcsAutoConfigurationReceived() {
+            if (mConfig == null) {
+                logd("Rcs config is null for sub : " + mSubId);
+                return;
+            }
+
+            if (mIImsConfig != null) {
+                try {
+                    logv("notifyRcsAutoConfigurationReceived for sub:" + mSubId);
+                    mIImsConfig.notifyRcsAutoConfigurationReceived(mConfig, false);
+                } catch (RemoteException e) {
+                    loge("notifyRcsAutoConfigurationReceived failed due to " + e);
+                }
+            } else {
+                logd("notifyRcsAutoConfigurationReceived failed due to IImsConfig null.");
+            }
+        }
+
+        private void notifyRcsAutoConfigurationRemoved() {
+            if (mIImsConfig != null) {
+                try {
+                    logv("notifyRcsAutoConfigurationRemoved for sub:" + mSubId);
+                    mIImsConfig.notifyRcsAutoConfigurationRemoved();
+                } catch (RemoteException e) {
+                    loge("notifyRcsAutoConfigurationRemoved failed due to " + e);
+                }
+            } else {
+                logd("notifyRcsAutoConfigurationRemoved failed due to IImsConfig null.");
+            }
+        }
+
+        private void clearCallbacks() {
             synchronized (mRcsConfigCallbacks) {
-                IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
                 Iterator<IRcsConfigCallback> it = mRcsConfigCallbacks.iterator();
                 while (it.hasNext()) {
                     IRcsConfigCallback cb = it.next();
-                    if (imsConfig != null) {
+                    if (mIImsConfig != null) {
                         try {
-                            imsConfig.removeRcsConfigCallback(cb);
+                            mIImsConfig.removeRcsConfigCallback(cb);
                         } catch (RemoteException e) {
                             loge("fail to removeRcsConfigCallback due to " + e);
                         }
@@ -283,7 +381,61 @@
     }
 
     @VisibleForTesting
-    public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager) {
+    public interface FeatureConnectorFactory<U extends FeatureUpdates> {
+        /**
+         * @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates}
+         * and slot index.
+         */
+        FeatureConnector<U> create(Context context, int slotIndex,
+                FeatureConnector.Listener<U> listener, Executor executor, String logPrefix);
+    }
+
+    private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
+        private final ArraySet<RcsProvisioningInfo> mRcsProvisioningInfos = new ArraySet<>();
+        private RcsFeatureManager mRcsFeatureManager;
+        private FeatureConnector<RcsFeatureManager> mConnector;
+
+        RcsFeatureListener(int slotId) {
+            mConnector = mFeatureFactory.create(
+                    mPhone, slotId, this, new HandlerExecutor(mHandler), TAG);
+            mConnector.connect();
+        }
+
+        void destroy() {
+            mConnector.disconnect();
+            mConnector = null;
+            mRcsFeatureManager = null;
+            mRcsProvisioningInfos.clear();
+        }
+
+        void addRcsProvisioningInfo(RcsProvisioningInfo info) {
+            if (!mRcsProvisioningInfos.contains(info)) {
+                mRcsProvisioningInfos.add(info);
+                info.onRcsStatusChanged(mRcsFeatureManager == null ? null
+                        : mRcsFeatureManager.getConfig());
+            }
+        }
+
+        void removeRcsProvisioningInfo(RcsProvisioningInfo info) {
+            mRcsProvisioningInfos.remove(info);
+        }
+
+        @Override
+        public void connectionReady(RcsFeatureManager manager) {
+            mRcsFeatureManager = manager;
+            mRcsProvisioningInfos.forEach(v -> v.onRcsStatusChanged(manager.getConfig()));
+        }
+
+        @Override
+        public void connectionUnavailable(int reason) {
+            mRcsFeatureManager = null;
+            mRcsProvisioningInfos.forEach(v -> v.onRcsStatusChanged(null));
+        }
+    }
+
+    @VisibleForTesting
+    public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager,
+            FeatureConnectorFactory<RcsFeatureManager> factory) {
         mPhone = app;
         mHandler = new MyHandler(looper);
         mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
@@ -292,15 +444,9 @@
         mRoleManager = roleManager;
         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();
+        mFeatureFactory = factory;
+        init();
     }
 
     /**
@@ -312,7 +458,7 @@
             HandlerThread handlerThread = new HandlerThread(TAG);
             handlerThread.start();
             sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
-                    new RoleManagerAdapterImpl(app));
+                    new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
         }
         return sInstance;
     }
@@ -324,15 +470,44 @@
         return sInstance;
     }
 
+    private void init() {
+        logd("init.");
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mPhone.registerReceiver(mReceiver, filter);
+        mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+        mDmaChangedListener.register();
+        //initialize configs for all active sub
+        onSubChanged();
+    }
+
+    private void release() {
+        logd("release.");
+        mDmaChangedListener.unregister();
+        mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+        mPhone.unregisterReceiver(mReceiver);
+        for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+            mRcsFeatureListeners.valueAt(i).destroy();
+        }
+        mRcsFeatureListeners.clear();
+        mRcsProvisioningInfos.forEach((k, v)->v.destroy());
+        mRcsProvisioningInfos.clear();
+        mCarrierSingleRegistrationEnabledOverride.clear();
+    }
+
+    private void reset() {
+        release();
+        init();
+    }
+
     /**
      * destroy the instance
      */
     @VisibleForTesting
     public void destroy() {
         logd("destroy it.");
-        mDmaChangedListener.unregister();
-        mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
-        mPhone.unregisterReceiver(mReceiver);
+        release();
         mHandler.getLooper().quit();
     }
 
@@ -410,6 +585,27 @@
     }
 
     /**
+     * Enables or disables test mode.
+     *
+     * <p> If test mode is enabled, any rcs config change will not update the database.
+     */
+    public void setTestModeEnabled(boolean enabled) {
+        logv("setTestModeEnabled as " + enabled);
+        if (mTestModeEnabled != enabled) {
+            mTestModeEnabled = enabled;
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_RESET));
+        }
+    }
+
+
+    /**
+     * Returns whether the test mode is enabled.
+     */
+    public boolean getTestModeEnabled() {
+        return mTestModeEnabled;
+    }
+
+    /**
      * override the device config whether single registration is enabled
      */
     public void overrideDeviceSingleRegistrationEnabled(Boolean enabled) {
@@ -462,61 +658,29 @@
                 v.clear();
                 if (isAcsUsed(k)) {
                     logv("acs used, trigger to re-configure.");
-                    notifyRcsAutoConfigurationRemoved(k);
-                    triggerRcsReconfiguration(k);
+                    updateConfigForSub(k, null, true);
+                    v.triggerRcsReconfiguration();
                 } else {
+                    logv("acs not used, set cached config and notify.");
                     v.setConfig(cachedConfig);
-                    logv("acs not used, notify.");
-                    notifyRcsAutoConfigurationReceived(k, v.getConfig(), false);
                 }
             });
         }
     }
 
-    private void notifyRcsAutoConfigurationReceived(int subId, byte[] config,
-            boolean isCompressed) {
-        if (config == null) {
-            logd("Rcs config is null for sub : " + subId);
-            return;
-        }
-
-        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 updateConfigForSub(int subId, byte[] config, boolean isCompressed) {
+        logv("updateConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+        if (!mTestModeEnabled) {
+            RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
         }
     }
 
-    private void notifyRcsAutoConfigurationRemoved(int subId) {
-        RcsConfig.updateConfigForSub(mPhone, subId, null, true);
-        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 byte[] loadConfigForSub(int subId) {
+        logv("loadConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+        if (!mTestModeEnabled) {
+            return RcsConfig.loadRcsConfigForSub(mPhone, subId, false);
         }
-    }
-
-    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.");
-        }
+        return null;
     }
 
     private boolean isAcsUsed(int subId) {
@@ -568,26 +732,25 @@
 
     private void onSubChanged() {
         final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
-        final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mRcsProvisioningInfos.keySet());
+        final ArraySet<Integer> subsToBeDeactivated =
+                new ArraySet<>(mRcsProvisioningInfos.keySet());
 
         for (int i : activeSubs) {
             subsToBeDeactivated.remove(i);
             if (!mRcsProvisioningInfos.containsKey(i)) {
-                byte[] data = RcsConfig.loadRcsConfigForSub(mPhone, i, false);
+                byte[] data = loadConfigForSub(i);
                 int capability = getSingleRegistrationCapableValue(i);
                 logv("new info is created for sub : " + i + ", single registration capability :"
                         + capability + ", rcs config : " + data);
                 mRcsProvisioningInfos.put(i, new RcsProvisioningInfo(i, capability, data));
-                notifyRcsAutoConfigurationReceived(i, data, false);
                 notifyDmaForSub(i, capability);
             }
         }
 
         subsToBeDeactivated.forEach(i -> {
             RcsProvisioningInfo info = mRcsProvisioningInfos.remove(i);
-            notifyRcsAutoConfigurationRemoved(i);
             if (info != null) {
-                info.clear();
+                info.destroy();
             }
         });
     }
@@ -596,11 +759,8 @@
         logv("onConfigReceived, subId:" + subId + ", config:"
                 + config + ", isCompressed:" + isCompressed);
         RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
-        if (info != null) {
-            info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
-        }
-        RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
-        notifyRcsAutoConfigurationReceived(subId, config, isCompressed);
+        info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
+        updateConfigForSub(subId, config, isCompressed);
     }
 
     private void onReconfigRequest(int subId) {
@@ -608,9 +768,10 @@
         RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
         if (info != null) {
             info.setConfig(null);
+            // clear rcs config stored in db
+            updateConfigForSub(subId, null, true);
+            info.triggerRcsReconfiguration();
         }
-        notifyRcsAutoConfigurationRemoved(subId);
-        triggerRcsReconfiguration(subId);
     }
 
     private void notifyDmaForSub(int subId, int capability) {
@@ -620,7 +781,14 @@
         intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
         intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
         logv("notify " + intent);
-        mPhone.sendBroadcast(intent);
+        // Only send permission to the default sms app if it has the correct permissions
+        // except test mode enabled
+        if (!mTestModeEnabled) {
+            //mPhone.sendBroadcast(intent, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+            mPhone.sendBroadcast(intent);
+        } else {
+            mPhone.sendBroadcast(intent);
+        }
     }
 
     private IImsConfig getIImsConfig(int subId, int feature) {
@@ -637,6 +805,24 @@
         }
     }
 
+    void registerRcsFeatureListener(RcsProvisioningInfo info) {
+        int slotId = SubscriptionManager.getSlotIndex(info.getSubId());
+        RcsFeatureListener cb = mRcsFeatureListeners.get(slotId);
+        if (cb == null) {
+            cb = new RcsFeatureListener(slotId);
+            mRcsFeatureListeners.put(slotId, cb);
+        }
+        cb.addRcsProvisioningInfo(info);
+    }
+
+    void unregisterRcsFeatureListener(RcsProvisioningInfo info) {
+        int slotId = SubscriptionManager.getSlotIndex(info.getSubId());
+        RcsFeatureListener cb = mRcsFeatureListeners.get(slotId);
+        if (cb != null) {
+            cb.removeRcsProvisioningInfo(info);
+        }
+    }
+
     private static boolean booleanEquals(Boolean val1, Boolean val2) {
         return (val1 == null && val2 == null)
                 || (Boolean.TRUE.equals(val1) && Boolean.TRUE.equals(val2))
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 8acfd1d..74d6b57 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -102,6 +102,8 @@
     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";
+    private static final String SRC_SET_TEST_ENABLED = "set-test-enabled";
+    private static final String SRC_GET_TEST_ENABLED = "get-test-enabled";
 
     private static final String D2D_SUBCOMMAND = "d2d";
     private static final String D2D_SEND = "send";
@@ -417,6 +419,11 @@
     private void onHelpSrc() {
         PrintWriter pw = getOutPrintWriter();
         pw.println("RCS VoLTE Single Registration Config Commands:");
+        pw.println("  src set-test-enabled true|false");
+        pw.println("    Sets the test mode enabled for RCS VoLTE single registration.");
+        pw.println("    The value could be true, false, or null(undefined).");
+        pw.println("  src get-test-enabled");
+        pw.println("    Gets the test mode for RCS VoLTE single registration.");
         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).");
@@ -1618,6 +1625,12 @@
         }
 
         switch (arg) {
+            case SRC_SET_TEST_ENABLED: {
+                return handleSrcSetTestEnabledCommand();
+            }
+            case SRC_GET_TEST_ENABLED: {
+                return handleSrcGetTestEnabledCommand();
+            }
             case SRC_SET_DEVICE_ENABLED: {
                 return handleSrcSetDeviceEnabledCommand();
             }
@@ -1737,6 +1750,40 @@
         return 0;
     }
 
+    private int handleSrcSetTestEnabledCommand() {
+        String enabledStr = getNextArg();
+        if (enabledStr == null) {
+            return -1;
+        }
+
+        try {
+            mInterface.setRcsSingleRegistrationTestModeEnabled(Boolean.parseBoolean(enabledStr));
+            if (VDBG) {
+                Log.v(LOG_TAG, "src set-test-enabled " + enabledStr + ", done");
+            }
+            getOutPrintWriter().println("Done");
+        } catch (NumberFormatException | RemoteException e) {
+            Log.w(LOG_TAG, "src set-test-enabled " + enabledStr + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleSrcGetTestEnabledCommand() {
+        boolean result = false;
+        try {
+            result = mInterface.getRcsSingleRegistrationTestModeEnabled();
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "src get-test-enabled, returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
+
     private int handleSrcSetDeviceEnabledCommand() {
         String enabledStr = getNextArg();
         if (enabledStr == null) {
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 6c36c2c..d72b348 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -55,20 +55,21 @@
 import android.telephony.ims.RcsConfig;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IRcsConfigCallback;
-import android.telephony.ims.feature.ImsFeature;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import com.android.ims.FeatureConnector;
+import com.android.ims.RcsFeatureManager;
 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.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -130,7 +131,13 @@
     @Mock
     private ITelephony.Stub mITelephony;
     @Mock
-    private ImsResolver mImsResolver;
+    private RcsFeatureManager mFeatureManager;
+    @Mock
+    private RcsProvisioningMonitor.FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
+    @Mock
+    private FeatureConnector<RcsFeatureManager> mFeatureConnector;
+    @Captor
+    ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mConnectorListener;
     @Mock
     private IImsConfig.Stub mIImsConfig;
     @Mock
@@ -246,8 +253,7 @@
         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();
     }
@@ -268,8 +274,9 @@
     @Test
     @SmallTest
     public void testInitWithSavedConfig() throws Exception {
-        createMonitor(3);
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        createMonitor(3);
+
         for (int i = 0; i < 3; i++) {
             assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
                     mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
@@ -279,8 +286,6 @@
         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());
     }
 
@@ -293,6 +298,7 @@
 
         verify(mPhone, times(3)).sendBroadcast(captorIntent.capture());
         Intent capturedIntent = captorIntent.getAllValues().get(1);
+
         assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
                 capturedIntent.getAction());
         //Should not notify null config
@@ -304,8 +310,7 @@
     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)));
@@ -354,7 +359,7 @@
         byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE);
 
         assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), configCached));
-        verify(mIImsConfig, never()).notifyRcsAutoConfigurationRemoved();
+        verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved();
         // The api should be called 2 times, one happens when monitor is initilized,
         // Another happens when DMS is changed.
         verify(mIImsConfig, times(2))
@@ -419,8 +424,6 @@
         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()));
@@ -434,8 +437,6 @@
         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();
     }
@@ -519,19 +520,79 @@
         verify(mCallback, times(1)).onRemoved();
     }
 
-    private void createMonitor(int subCount) {
+    @Test
+    @SmallTest
+    public void testRcsConnectedAndDisconnected() throws Exception {
+        createMonitor(1);
+        mRcsProvisioningMonitor.registerRcsProvisioningChangedCallback(
+                FAKE_SUB_ID_BASE, mCallback);
+
+        verify(mIImsConfig, times(1))
+                .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+
+        mConnectorListener.getValue().connectionUnavailable(0);
+
+        verify(mCallback, times(1)).onRemoved();
+    }
+
+    @Test
+    @SmallTest
+    public void testTestModeEnabledAndDisabled() throws Exception {
+        when(mCursor.getBlob(anyInt())).thenReturn(null);
+        createMonitor(1);
+
+        verify(mCursor, times(1)).getBlob(anyInt());
+
+        mRcsProvisioningMonitor.setTestModeEnabled(true);
+        processAllMessages();
+
+        //should not query db in test mode
+        verify(mCursor, times(1)).getBlob(anyInt());
+        assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
+        processAllMessages();
+
+        //config cahced in monitor should be updated, but db should not
+        assertNull(mProvider.getContentValues());
+        assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+                mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE)));
+
+        //verify if monitor goes back to normal mode
+        mRcsProvisioningMonitor.setTestModeEnabled(false);
+        processAllMessages();
+
+        verify(mCursor, times(2)).getBlob(anyInt());
+        assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
+        processAllMessages();
+
+        assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+                mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE)));
+        assertTrue(Arrays.equals(RcsConfig.compressGzip(SAMPLE_CONFIG.getBytes()),
+                (byte[]) mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG)));
+    }
+
+    private void createMonitor(int subCount) throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
         makeFakeActiveSubIds(subCount);
+        when(mFeatureFactory.create(any(), anyInt(), mConnectorListener.capture(), any(), any()))
+                .thenReturn(mFeatureConnector);
+        when(mFeatureManager.getConfig()).thenReturn(mIImsConfig);
         mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(),
-                mRoleManager);
+                mRoleManager, mFeatureFactory);
         mHandler = mRcsProvisioningMonitor.getHandler();
         try {
             mLooper = new TestableLooper(mHandler.getLooper());
         } catch (Exception e) {
             logd("Unable to create looper from handler.");
         }
+        mConnectorListener.getValue().connectionReady(mFeatureManager);
+
+        verify(mFeatureConnector, atLeastOnce()).connect();
     }
 
     private void broadcastCarrierConfigChange(int subId) {