Improvement provisioning in AOSP

Improvement Ims provisioning in AOSP to store and handle provisioning data.

Bug: http://b/202199221
Test: atest
Change-Id: Ifd7030d3c04a036acb82a4eced19b0584ae5afc8
diff --git a/src/com/android/phone/ImsProvisioningController.java b/src/com/android/phone/ImsProvisioningController.java
new file mode 100644
index 0000000..b42f57f
--- /dev/null
+++ b/src/com/android/phone/ImsProvisioningController.java
@@ -0,0 +1,1427 @@
+/*
+ * Copyright (C) 2021 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 android.telephony.ims.ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE;
+import static android.telephony.ims.ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.PROVISIONING_VALUE_DISABLED;
+import static android.telephony.ims.ProvisioningManager.PROVISIONING_VALUE_ENABLED;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
+import static android.telephony.ims.feature.RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_MAX;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.aidl.IFeatureProvisioningCallback;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
+import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
+import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.SparseArray;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.HandlerExecutor;
+import com.android.telephony.Rlog;
+
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides APIs for MMTEL and RCS provisioning status. This class handles provisioning status and
+ * notifies the status changing for each capability
+ * {{@link MmTelCapabilities.MmTelCapability} for MMTel services}
+ * {{@link RcsImsCapabilities.RcsImsCapabilityFlag} for RCS services}
+ */
+public class ImsProvisioningController {
+    private static final String TAG = "ImsProvisioningController";
+    private static final int INVALID_VALUE = -1;
+
+    private static final int EVENT_SUB_CHANGED = 1;
+    private static final int EVENT_PROVISIONING_CAPABILITY_CHANGED = 2;
+
+    // Provisioning Keys that are handled via AOSP cache and not sent to the ImsService
+    private static final int[] LOCAL_IMS_CONFIG_KEYS = {
+            KEY_VOLTE_PROVISIONING_STATUS,
+            KEY_VT_PROVISIONING_STATUS,
+            KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE,
+            KEY_EAB_PROVISIONING_STATUS
+    };
+    private static final int[] LOCAL_RADIO_TECHS = {
+            REGISTRATION_TECH_LTE,
+            REGISTRATION_TECH_IWLAN,
+            REGISTRATION_TECH_CROSS_SIM,
+            REGISTRATION_TECH_NR
+    };
+
+    private static final int MMTEL_CAPABILITY_MIN = MmTelCapabilities.CAPABILITY_TYPE_NONE;
+    private static final int MMTEL_CAPABILITY_MAX = MmTelCapabilities.CAPABILITY_TYPE_MAX;
+
+    private static final int RCS_CAPABILITY_MIN = RcsImsCapabilities.CAPABILITY_TYPE_NONE;
+    private static final int RCS_CAPABILITY_MAX = RcsImsCapabilities.CAPABILITY_TYPE_MAX;
+
+    private static final int[] LOCAL_MMTEL_CAPABILITY = {
+            CAPABILITY_TYPE_VOICE,
+            CAPABILITY_TYPE_VIDEO,
+            CAPABILITY_TYPE_UT,
+            CAPABILITY_TYPE_SMS,
+            CAPABILITY_TYPE_CALL_COMPOSER
+    };
+
+    /**
+     * Create a FeatureConnector for this class to use to connect to an ImsManager.
+     */
+    @VisibleForTesting
+    public interface MmTelFeatureConnector {
+        /**
+         * Create a FeatureConnector for this class to use to connect to an ImsManager.
+         * @param listener will receive ImsManager instance.
+         * @param executor that the Listener callbacks will be called on.
+         * @return A FeatureConnector
+         */
+        FeatureConnector<ImsManager> create(Context context, int slotId,
+                String logPrefix, FeatureConnector.Listener<ImsManager> listener,
+                Executor executor);
+    }
+
+    /**
+     * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+     */
+    @VisibleForTesting
+    public interface RcsFeatureConnector {
+        /**
+         * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+         * @param listener will receive RcsFeatureManager instance.
+         * @param executor that the Listener callbacks will be called on.
+         * @return A FeatureConnector
+         */
+        FeatureConnector<RcsFeatureManager> create(Context context, int slotId,
+                FeatureConnector.Listener<RcsFeatureManager> listener,
+                Executor executor, String logPrefix);
+    }
+
+    private static ImsProvisioningController sInstance;
+
+    private final PhoneGlobals mApp;
+    private final Handler mHandler;
+    private final CarrierConfigManager mCarrierConfigManager;
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyRegistryManager mTelephonyRegistryManager;
+    private final MmTelFeatureConnector mMmTelFeatureConnector;
+    private final RcsFeatureConnector mRcsFeatureConnector;
+
+    // maps a slotId to a list of MmTelFeatureListeners
+    private final SparseArray<MmTelFeatureListener> mMmTelFeatureListenersSlotMap =
+            new SparseArray<>();
+    // maps a slotId to a list of RcsFeatureListeners
+    private final SparseArray<RcsFeatureListener> mRcsFeatureListenersSlotMap =
+            new SparseArray<>();
+    // map a slotId to a list of ProvisioningCallbackManager
+    private final SparseArray<ProvisioningCallbackManager> mProvisioningCallbackManagersSlotMap =
+            new SparseArray<>();
+    private final ImsProvisioningLoader mImsProvisioningLoader;
+
+    private int mNumSlot;
+
+    /**
+     * This class contains the provisioning status to notify changes.
+     * {{@link MmTelCapabilities.MmTelCapability} for MMTel services}
+     * {{@link RcsImsCapabilities.RcsImsCapabilityFlag} for RCS services}
+     * {{@link ImsRegistrationImplBase.ImsRegistrationTech} for Registration tech}
+     */
+    private static final class FeatureProvisioningData {
+        public final int mCapability;
+        public final int mTech;
+        public final boolean mProvisioned;
+        public final boolean mIsMmTel;
+
+        FeatureProvisioningData(int capability, int tech, boolean provisioned, boolean isMmTel) {
+            mCapability = capability;
+            mTech = tech;
+            mProvisioned = provisioned;
+            mIsMmTel = isMmTel;
+        }
+    }
+
+    private final class MessageHandler extends Handler {
+        private static final String LOG_PREFIX = "Handler";
+        MessageHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_SUB_CHANGED:
+                    onSubscriptionsChanged();
+                    break;
+                case EVENT_PROVISIONING_CAPABILITY_CHANGED:
+                    try {
+                        mProvisioningCallbackManagersSlotMap.get(msg.arg1)
+                                .notifyProvisioningCapabilityChanged(
+                                        (FeatureProvisioningData) msg.obj);
+                    } catch (NullPointerException e) {
+                        logw(LOG_PREFIX, msg.arg1,
+                                "can not find callback manager message" + msg.what);
+                    }
+                    break;
+                default:
+                    log("unknown message " + msg);
+                    break;
+            }
+        }
+    }
+
+    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 class ProvisioningCallbackManager {
+        private static final String LOG_PREFIX = "ProvisioningCallbackManager";
+        private RemoteCallbackList<IFeatureProvisioningCallback> mIFeatureProvisioningCallbackList;
+        private int mSubId;
+        private int mSlotId;
+
+        ProvisioningCallbackManager(int slotId) {
+            mIFeatureProvisioningCallbackList =
+                    new RemoteCallbackList<IFeatureProvisioningCallback>();
+            mSlotId = slotId;
+            mSubId = getSubId(slotId);
+            log(LOG_PREFIX, mSlotId, "ProvisioningCallbackManager create");
+        }
+
+        public void clear() {
+            log(LOG_PREFIX, mSlotId, "ProvisioningCallbackManager clear ");
+
+            mIFeatureProvisioningCallbackList.kill();
+
+            // All registered callbacks are unregistered, and the list is disabled
+            // need to create again
+            mIFeatureProvisioningCallbackList =
+                    new RemoteCallbackList<IFeatureProvisioningCallback>();
+        }
+
+        public void registerCallback(IFeatureProvisioningCallback localCallback) {
+            if (!mIFeatureProvisioningCallbackList.register(localCallback, (Object) mSubId)) {
+                log(LOG_PREFIX, mSlotId, "registration callback fail");
+            }
+        }
+
+        public void unregisterCallback(IFeatureProvisioningCallback localCallback) {
+            mIFeatureProvisioningCallbackList.unregister(localCallback);
+        }
+
+        public void setSubId(int subId) {
+            if (mSubId == subId) {
+                log(LOG_PREFIX, mSlotId, "subId is not changed ");
+                return;
+            }
+
+            mSubId = subId;
+            mSlotId = getSlotId(subId);
+
+            // subId changed means the registered callbacks are not available.
+            clear();
+        }
+
+        public boolean hasCallblacks() {
+            int size = mIFeatureProvisioningCallbackList.beginBroadcast();
+            mIFeatureProvisioningCallbackList.finishBroadcast();
+
+            return (size > 0);
+        }
+
+        public void notifyProvisioningCapabilityChanged(FeatureProvisioningData data) {
+            int size = mIFeatureProvisioningCallbackList.beginBroadcast();
+            for (int index = 0; index < size; index++) {
+                try {
+                    IFeatureProvisioningCallback imsFeatureProvisioningCallback =
+                            mIFeatureProvisioningCallbackList.getBroadcastItem(index);
+
+                    // MMTEL
+                    if (data.mIsMmTel
+                            && Arrays.stream(LOCAL_MMTEL_CAPABILITY)
+                            .anyMatch(value -> value == data.mCapability)) {
+                        imsFeatureProvisioningCallback.onFeatureProvisioningChanged(
+                                data.mCapability, data.mTech, data.mProvisioned);
+                        logi(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : "
+                                + "onFeatureProvisioningChanged"
+                                + " capability " + data.mCapability
+                                + " tech "  + data.mTech
+                                + " isProvisioned " + data.mProvisioned);
+                    } else if (data.mCapability == CAPABILITY_TYPE_PRESENCE_UCE) {
+                        imsFeatureProvisioningCallback.onRcsFeatureProvisioningChanged(
+                                data.mCapability, data.mTech, data.mProvisioned);
+                        logi(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : "
+                                + "onRcsFeatureProvisioningChanged"
+                                + " capability " + data.mCapability
+                                + " tech "  + data.mTech
+                                + " isProvisioned " + data.mProvisioned);
+                    } else {
+                        loge(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : "
+                                + "unknown capability "
+                                + data.mCapability);
+                    }
+                } catch (RemoteException e) {
+                    loge(LOG_PREFIX, mSlotId,
+                            "notifyProvisioningChanged: callback #" + index + " failed");
+                }
+            }
+            mIFeatureProvisioningCallbackList.finishBroadcast();
+        }
+    }
+
+    private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
+        private static final String LOG_PREFIX = "MmTelFeatureListener";
+        private FeatureConnector<ImsManager> mConnector;
+        private ImsManager mImsManager;
+        private boolean mReady = false;
+        // stores whether the initial provisioning key value should be notified to ImsService
+        private boolean mRequiredNotify = false;
+        private int mSubId;
+        private int mSlotId;
+
+        MmTelFeatureListener(int slotId) {
+            log(LOG_PREFIX, slotId, "created");
+
+            mSlotId = slotId;
+            mSubId = getSubId(slotId);
+            mConnector = mMmTelFeatureConnector.create(
+                    mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
+            mConnector.connect();
+        }
+
+        public void setSubId(int subId) {
+            if (mRequiredNotify && mReady) {
+                mRequiredNotify = false;
+                setInitialProvisioningKeys(subId);
+            }
+            if (mSubId == subId) {
+                log(LOG_PREFIX, mSlotId, "subId is not changed");
+                return;
+            }
+
+            mSubId = subId;
+            mSlotId = getSlotId(subId);
+        }
+
+        public void destroy() {
+            log("destroy");
+            mConnector.disconnect();
+            mConnector = null;
+            mReady = false;
+            mImsManager = null;
+        }
+
+        public @Nullable ImsManager getImsManager() {
+            return mImsManager;
+        }
+
+        @Override
+        public void connectionReady(ImsManager manager, int subId) {
+            log(LOG_PREFIX, mSlotId, "connection ready");
+            mReady = true;
+            mImsManager = manager;
+
+            onMmTelAvailable();
+        }
+
+        @Override
+        public void connectionUnavailable(int reason) {
+            log(LOG_PREFIX, mSlotId, "connection unavailable " + reason);
+
+            mReady = false;
+            mImsManager = null;
+
+            // keep the callback for other reason
+            if (reason == FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED) {
+                onMmTelUnavailable();
+            }
+        }
+
+        public int setProvisioningValue(int key, int value) {
+            int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED;
+
+            if (!mReady) {
+                loge(LOG_PREFIX, mSlotId, "service is Unavailable");
+                return retVal;
+            }
+            try {
+                // getConfigInterface() will return not null or throw the ImsException
+                // need not null checking
+                ImsConfig imsConfig = getImsConfig(mImsManager);
+                retVal = imsConfig.setConfig(key, value);
+                log(LOG_PREFIX, mSlotId, "setConfig called with key " + key + " value " + value);
+            } catch (ImsException e) {
+                logw(LOG_PREFIX, mSlotId,
+                        "setConfig operation failed for key =" + key
+                        + ", value =" + value + ". Exception:" + e.getMessage());
+            }
+            return retVal;
+        }
+
+        public int getProvisioningValue(int key) {
+            if (!mReady) {
+                loge(LOG_PREFIX, mSlotId, "service is Unavailable");
+                return INVALID_VALUE;
+            }
+
+            int retValue = INVALID_VALUE;
+            try {
+                // getConfigInterface() will return not null or throw the ImsException
+                // need not null checking
+                ImsConfig imsConfig = getImsConfig(mImsManager);
+                retValue = imsConfig.getConfigInt(key);
+            } catch (ImsException e) {
+                logw(LOG_PREFIX, mSlotId,
+                        "getConfig operation failed for key =" + key
+                        + ", value =" + retValue + ". Exception:" + e.getMessage());
+            }
+            return retValue;
+        }
+
+        public void onMmTelAvailable() {
+            log(LOG_PREFIX, mSlotId, "onMmTelAvailable");
+
+            if (isValidSubId(mSubId)) {
+                mRequiredNotify = false;
+
+                // notify provisioning key value to ImsService
+                setInitialProvisioningKeys(mSubId);
+            } else {
+                // wait until subId is valid
+                mRequiredNotify = true;
+            }
+        }
+
+        public void onMmTelUnavailable() {
+            log(LOG_PREFIX, mSlotId, "onMmTelUnavailable");
+
+            try {
+                // delete all callbacks reference from ProvisioningManager
+                mProvisioningCallbackManagersSlotMap.get(getSlotId(mSubId)).clear();
+            } catch (NullPointerException e) {
+                logw(LOG_PREFIX, getSlotId(mSubId), "can not find callback manager to clear");
+            }
+        }
+
+        private void setInitialProvisioningKeys(int subId) {
+            boolean required;
+            int value = ImsProvisioningLoader.STATUS_NOT_SET;
+
+            // updating KEY_VOLTE_PROVISIONING_STATUS
+            required = isProvisioningRequired(subId, CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE,
+                    /*isMmTel*/true);
+            log(LOG_PREFIX, mSlotId,
+                    "setInitialProvisioningKeys provisioning required(voice, lte) " + required);
+            if (required) {
+                value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL,
+                        CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE);
+                if (value != ImsProvisioningLoader.STATUS_NOT_SET) {
+                    value = (value == ImsProvisioningLoader.STATUS_PROVISIONED)
+                            ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED;
+                    setProvisioningValue(KEY_VOLTE_PROVISIONING_STATUS, value);
+                }
+            }
+
+            // updating KEY_VT_PROVISIONING_STATUS
+            required = isProvisioningRequired(subId, CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE,
+                    /*isMmTel*/true);
+            log(LOG_PREFIX, mSlotId,
+                    "setInitialProvisioningKeys provisioning required(video, lte) " + required);
+            if (required) {
+                value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL,
+                        CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE);
+                if (value != ImsProvisioningLoader.STATUS_NOT_SET) {
+                    value = (value == ImsProvisioningLoader.STATUS_PROVISIONED)
+                            ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED;
+                    setProvisioningValue(KEY_VT_PROVISIONING_STATUS, value);
+                }
+            }
+
+            // updating KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE
+            required = isProvisioningRequired(subId, CAPABILITY_TYPE_VOICE,
+                    REGISTRATION_TECH_IWLAN, /*isMmTel*/true);
+            log(LOG_PREFIX, mSlotId,
+                    "setInitialProvisioningKeys provisioning required(voice, iwlan) " + required);
+            if (required) {
+                value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL,
+                        CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN);
+                if (value != ImsProvisioningLoader.STATUS_NOT_SET) {
+                    value = (value == ImsProvisioningLoader.STATUS_PROVISIONED)
+                            ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED;
+                    setProvisioningValue(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, value);
+                }
+            }
+        }
+    }
+
+    private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
+        private static final String LOG_PREFIX = "RcsFeatureListener";
+        private FeatureConnector<RcsFeatureManager> mConnector;
+        private RcsFeatureManager mRcsFeatureManager;
+        private boolean mReady = false;
+        // stores whether the initial provisioning key value should be notified to ImsService
+        private boolean mRequiredNotify = false;
+        private int mSubId;
+        private int mSlotId;
+
+        RcsFeatureListener(int slotId) {
+            log(LOG_PREFIX, slotId, "created");
+
+            mSlotId = slotId;
+            mSubId = getSubId(slotId);
+            mConnector = mRcsFeatureConnector.create(
+                    mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
+            mConnector.connect();
+        }
+
+        public void setSubId(int subId) {
+            if (mRequiredNotify && mReady) {
+                mRequiredNotify = false;
+                setInitialProvisioningKeys(subId);
+            }
+            if (mSubId == subId) {
+                log(LOG_PREFIX, mSlotId, "subId is not changed");
+                return;
+            }
+
+            mSubId = subId;
+            mSlotId = getSlotId(subId);
+        }
+
+        public void destroy() {
+            log(LOG_PREFIX, mSlotId, "destroy");
+            mConnector.disconnect();
+            mConnector = null;
+            mReady = false;
+            mRcsFeatureManager = null;
+        }
+
+        @Override
+        public void connectionReady(RcsFeatureManager manager, int subId) {
+            log(LOG_PREFIX, mSlotId, "connection ready");
+            mReady = true;
+            mRcsFeatureManager = manager;
+
+            onRcsAvailable();
+        }
+
+        @Override
+        public void connectionUnavailable(int reason) {
+            log(LOG_PREFIX, mSlotId, "connection unavailable");
+            mReady = false;
+            mRcsFeatureManager = null;
+
+            // keep the callback for other reason
+            if (reason == FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED) {
+                onRcsUnavailable();
+            }
+        }
+
+        public int setProvisioningValue(int key, int value) {
+            int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED;
+
+            if (!mReady) {
+                loge(LOG_PREFIX, mSlotId, "service is Unavailable");
+                return retVal;
+            }
+
+            try {
+                // getConfigInterface() will return not null or throw the ImsException
+                // need not null checking
+                ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig());
+                retVal = imsConfig.setConfig(key, value);
+                log(LOG_PREFIX, mSlotId, "setConfig called with key " + key + " value " + value);
+            } catch (ImsException e) {
+                logw(LOG_PREFIX, mSlotId,
+                        "setConfig operation failed for key =" + key
+                        + ", value =" + value + ". Exception:" + e.getMessage());
+            }
+            return retVal;
+        }
+
+        public int getProvisioningValue(int key) {
+            if (!mReady) {
+                loge(LOG_PREFIX, mSlotId, "service is Unavailable");
+                return INVALID_VALUE;
+            }
+
+            int retValue = INVALID_VALUE;
+            try {
+                // getConfigInterface() will return not null or throw the ImsException
+                // need not null checking
+                ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig());
+                retValue = imsConfig.getConfigInt(key);
+            } catch (ImsException e) {
+                logw(LOG_PREFIX, mSlotId,
+                        "getConfig operation failed for key =" + key
+                        + ", value =" + retValue + ". Exception:" + e.getMessage());
+            }
+            return retValue;
+        }
+
+        public void onRcsAvailable() {
+            log(LOG_PREFIX, mSlotId, "onRcsAvailable");
+
+            if (isValidSubId(mSubId)) {
+                mRequiredNotify = false;
+
+                // notify provisioning key value to ImsService
+                setInitialProvisioningKeys(mSubId);
+            } else {
+                // wait until subId is valid
+                mRequiredNotify = true;
+            }
+        }
+
+        public void onRcsUnavailable() {
+            log(LOG_PREFIX, mSlotId, "onRcsUnavailable");
+
+            try {
+                // delete all callbacks reference from ProvisioningManager
+                mProvisioningCallbackManagersSlotMap.get(getSlotId(mSubId)).clear();
+            } catch (NullPointerException e) {
+                logw(LOG_PREFIX, getSlotId(mSubId), "can not find callback manager to clear");
+            }
+        }
+
+        private void setInitialProvisioningKeys(int subId) {
+            boolean required;
+            int value = ImsProvisioningLoader.STATUS_NOT_SET;
+
+            // KEY_EAB_PROVISIONING_STATUS
+            int capability = CAPABILITY_TYPE_PRESENCE_UCE;
+            // Assume that all radio techs have the same provisioning value
+            int tech = REGISTRATION_TECH_LTE;
+
+            required = isProvisioningRequired(subId, capability, tech, /*isMmTel*/false);
+            if (required) {
+                value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS,
+                        capability, tech);
+                if (value != ImsProvisioningLoader.STATUS_NOT_SET) {
+                    value = (value == ImsProvisioningLoader.STATUS_PROVISIONED)
+                            ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED;
+                    setProvisioningValue(KEY_EAB_PROVISIONING_STATUS, value);
+                }
+            }
+        }
+    }
+
+    /**
+     * Do NOT use this directly, instead use {@link #getInstance()}.
+     */
+    @VisibleForTesting
+    public ImsProvisioningController(PhoneGlobals app, int numSlot, Looper looper,
+            MmTelFeatureConnector mmTelFeatureConnector, RcsFeatureConnector rcsFeatureConnector,
+            ImsProvisioningLoader imsProvisioningLoader) {
+        log("ImsProvisioningController");
+        mApp = app;
+        mNumSlot = numSlot;
+        mHandler = new MessageHandler(looper);
+        mMmTelFeatureConnector = mmTelFeatureConnector;
+        mRcsFeatureConnector = rcsFeatureConnector;
+        mCarrierConfigManager = mApp.getSystemService(CarrierConfigManager.class);
+        mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
+        mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
+        mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+        mImsProvisioningLoader = imsProvisioningLoader;
+
+        initialize(numSlot);
+    }
+
+    private void initialize(int numSlot) {
+        for (int i = 0; i < numSlot; i++) {
+            MmTelFeatureListener m = new MmTelFeatureListener(i);
+            mMmTelFeatureListenersSlotMap.put(i, m);
+
+            RcsFeatureListener r = new RcsFeatureListener(i);
+            mRcsFeatureListenersSlotMap.put(i, r);
+
+            ProvisioningCallbackManager p = new ProvisioningCallbackManager(i);
+            mProvisioningCallbackManagersSlotMap.put(i, p);
+        }
+    }
+
+    /**
+     * destroy the instance
+     */
+    @VisibleForTesting
+    public void destroy() {
+        log("destroy");
+
+        mHandler.getLooper().quit();
+
+        mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+
+        for (int i = 0; i < mMmTelFeatureListenersSlotMap.size(); i++) {
+            mMmTelFeatureListenersSlotMap.get(i).destroy();
+        }
+        mMmTelFeatureListenersSlotMap.clear();
+
+        for (int i = 0; i < mRcsFeatureListenersSlotMap.size(); i++) {
+            mRcsFeatureListenersSlotMap.get(i).destroy();
+        }
+        mRcsFeatureListenersSlotMap.clear();
+
+        for (int i = 0; i < mProvisioningCallbackManagersSlotMap.size(); i++) {
+            mProvisioningCallbackManagersSlotMap.get(i).clear();
+        }
+    }
+
+    /**
+     * create an instance
+     */
+    @VisibleForTesting
+    public static ImsProvisioningController make(PhoneGlobals app, int numSlot) {
+        synchronized (ImsProvisioningController.class) {
+            if (sInstance == null) {
+                Rlog.i(TAG, "ImsProvisioningController created");
+                HandlerThread handlerThread = new HandlerThread(TAG);
+                handlerThread.start();
+                sInstance = new ImsProvisioningController(app, numSlot, handlerThread.getLooper(),
+                        ImsManager::getConnector, RcsFeatureManager::getConnector,
+                        new ImsProvisioningLoader(app));
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Gets a ImsProvisioningController instance
+     */
+    @VisibleForTesting
+    public static ImsProvisioningController getInstance() {
+        synchronized (ImsProvisioningController.class) {
+            return sInstance;
+        }
+    }
+
+    /**
+     * Register IFeatureProvisioningCallback from ProvisioningManager
+     */
+
+    @VisibleForTesting
+    public void addFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("provisioning callback can't be null");
+        }
+        int slotId = getSlotId(subId);
+        if (slotId < 0 || slotId >= mNumSlot) {
+            throw new IllegalArgumentException("subscription id is not available");
+        }
+
+        try {
+            mProvisioningCallbackManagersSlotMap.get(slotId).registerCallback(callback);
+            log("Feature Provisioning Callback registered.");
+        } catch (NullPointerException e) {
+            logw("can not access callback manager to add callback");
+        }
+    }
+
+    /**
+     * Remove IFeatureProvisioningCallback
+     */
+    @VisibleForTesting
+    public void removeFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("provisioning callback can't be null");
+        }
+
+        int slotId = getSlotId(subId);
+        if (slotId < 0 || slotId >= mNumSlot) {
+            throw new IllegalArgumentException("subscription id is not available");
+        }
+
+        try {
+            mProvisioningCallbackManagersSlotMap.get(slotId).unregisterCallback(callback);
+            log("Feature Provisioning Callback removed.");
+        } catch (NullPointerException e) {
+            logw("can not access callback manager to remove callback");
+        }
+    }
+
+    /**
+     * return the boolean whether MmTel capability is required provisiong or not
+     */
+    @VisibleForTesting
+    public boolean isImsProvisioningRequiredForCapability(int subId, int capability, int tech) {
+        // check subId
+        int slotId = getSlotId(subId);
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) {
+            loge("Fail to retrieve slotId from subId");
+            throw new IllegalArgumentException("subscribe id is invalid");
+        }
+
+        // check valid capability
+        if (!(MMTEL_CAPABILITY_MIN < capability && capability < MMTEL_CAPABILITY_MAX)) {
+            throw new IllegalArgumentException("MmTel capability '" + capability + "' is invalid");
+        }
+
+        // check valid radio tech
+        if (!(REGISTRATION_TECH_NONE < tech && tech < REGISTRATION_TECH_MAX)) {
+            log("Ims not matched radio tech " + tech);
+            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
+        }
+
+        boolean retVal = isProvisioningRequired(subId, capability, tech, /*isMmTel*/true);
+
+        log("isImsProvisioningRequiredForCapability capability " + capability
+                + " tech " + tech + " return value " + retVal);
+
+        return retVal;
+    }
+
+    /**
+     * return the boolean whether RCS capability is required provisiong or not
+     */
+    @VisibleForTesting
+    public boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech) {
+        // check slotId and Phone object
+        int slotId = getSlotId(subId);
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) {
+            loge("Fail to retrieve slotId from subId");
+            throw new IllegalArgumentException("subscribe id is invalid");
+        }
+
+        // check valid capability
+        if (!(RCS_CAPABILITY_MIN < capability && capability < RCS_CAPABILITY_MAX)) {
+            throw new IllegalArgumentException("Rcs capability '" + capability + "' is invalid");
+        }
+
+        // check valid radio tech
+        if (!(REGISTRATION_TECH_NONE < tech && tech < REGISTRATION_TECH_MAX)) {
+            log("Rcs not matched radio tech " + tech);
+            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
+        }
+
+        boolean retVal = isProvisioningRequired(subId, capability, tech, /*isMmTel*/false);
+
+        log("isRcsProvisioningRequiredForCapability capability " + capability
+                + " tech " + tech + " return value " + retVal);
+
+        return retVal;
+    }
+
+    /**
+     * return the provisioning status for MmTel capability in specific radio tech
+     */
+    @VisibleForTesting
+    public boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech) {
+        boolean mmTelProvisioned = isImsProvisioningRequiredForCapability(subId, capability, tech);
+        if (!mmTelProvisioned) { // provisioning not required
+            log("getImsProvisioningStatusForCapability : not required "
+                    + " capability " + capability + " tech " + tech);
+            return true;
+        }
+
+        // read value from ImsProvisioningLoader
+        int result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL,
+                capability, tech);
+        if (result == ImsProvisioningLoader.STATUS_NOT_SET) {
+            // not set means initial value
+            // read data from vendor ImsService and store that in ImsProvisioningLoader
+            result = getValueFromImsService(subId, capability, tech);
+            mmTelProvisioned = getBoolValue(result);
+            if (result != ProvisioningManager.PROVISIONING_RESULT_UNKNOWN) {
+                setAndNotifyMmTelProvisioningValue(subId, capability, tech, mmTelProvisioned);
+            }
+        } else {
+            mmTelProvisioned = getBoolValue(result);
+        }
+
+        log("getImsProvisioningStatusForCapability : "
+                + " capability " + capability
+                + " tech " + tech
+                + " result " + mmTelProvisioned);
+        return mmTelProvisioned;
+    }
+
+    /**
+     * set MmTel provisioning status in specific tech
+     */
+    @VisibleForTesting
+    public void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
+            boolean isProvisioned) {
+        boolean mmTelProvisioned = isImsProvisioningRequiredForCapability(subId, capability, tech);
+        if (!mmTelProvisioned) { // provisioning not required
+            log("setImsProvisioningStatusForCapability : not required "
+                    + " capability " + capability + " tech " + tech);
+            return;
+        }
+
+        // write value to ImsProvisioningLoader
+        boolean isChanged = setAndNotifyMmTelProvisioningValue(subId, capability, tech,
+                isProvisioned);
+        if (!isChanged) {
+            log("status not changed mmtel capability " + capability + " tech " + tech);
+            return;
+        }
+
+        int slotId = getSlotId(subId);
+        // find matched key from capability and tech
+        int value = getIntValue(isProvisioned);
+        int key = getKeyFromCapability(capability, tech);
+        if (key != INVALID_VALUE) {
+            log("setImsProvisioningStatusForCapability : matched key " + key);
+            try {
+                // set key and value to vendor ImsService for MmTel
+                mMmTelFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value);
+
+                // notify provisioning status changed to ImsManager
+                updateImsServiceConfig(subId);
+            } catch (NullPointerException e) {
+                loge("can not access MmTelFeatureListener with capability " + capability);
+            }
+        }
+    }
+
+    /**
+     * return the provisioning status for RCS capability in specific radio tech
+     */
+    @VisibleForTesting
+    public boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech) {
+        boolean rcsProvisioned = isRcsProvisioningRequiredForCapability(subId, capability, tech);
+        if (!rcsProvisioned) { // provisioning not required
+            log("getRcsProvisioningStatusForCapability : not required"
+                    + " capability " + capability + " tech " + tech);
+            return true;
+        }
+
+        // read data from ImsProvisioningLoader
+        int result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS,
+                capability, tech);
+        if (result == ImsProvisioningLoader.STATUS_NOT_SET) {
+            // not set means initial value
+            // read data from vendor ImsService and store that in ImsProvisioningLoader
+            result = getRcsValueFromImsService(subId, capability);
+            rcsProvisioned = getBoolValue(result);
+            if (result != ProvisioningManager.PROVISIONING_RESULT_UNKNOWN) {
+                setAndNotifyRcsProvisioningValueForAllTech(subId, capability, rcsProvisioned);
+            }
+        } else {
+            rcsProvisioned = getBoolValue(result);
+        }
+
+        log("getRcsProvisioningStatusForCapability : "
+                + " capability " + capability
+                + " tech " + tech
+                + " result " + rcsProvisioned);
+        return rcsProvisioned;
+    }
+
+    /**
+     * set RCS provisioning status in specific tech
+     */
+    @VisibleForTesting
+    public void setRcsProvisioningStatusForCapability(int subId, int capability, int tech,
+            boolean isProvisioned) {
+        boolean rcsProvisioned = isRcsProvisioningRequiredForCapability(subId, capability, tech);
+        if (!rcsProvisioned) { // provisioning not required
+            log("set rcs provisioning status but not required");
+            return;
+        }
+
+        // write status using ImsProvisioningLoader
+        boolean isChanged = setAndNotifyRcsProvisioningValue(subId, capability, tech,
+                isProvisioned);
+        if (!isChanged) {
+            log("status not changed rcs capability " + capability + " tech " + tech);
+            return;
+        }
+
+        int slotId = getSlotId(subId);
+        int key =  ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+        int value = getIntValue(isProvisioned);
+        try {
+            // set key and value to vendor ImsService for Rcs
+            mRcsFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value);
+        } catch (NullPointerException e) {
+            loge("can not access RcsFeatureListener with capability " + capability);
+        }
+    }
+
+    /**
+     * set RCS provisioning status in specific key and value
+     * @param key integer key, defined as one of
+     * {@link ProvisioningManager#KEY_VOLTE_PROVISIONING_STATUS}
+     * {@link ProvisioningManager#KEY_VT_PROVISIONING_STATUS}
+     * {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE}
+     * {@link ProvisioningManager#KEY_EAB_PROVISIONING_STATUS}
+     * @param value in Integer format.
+     * @return the result of setting the configuration value, defined as one of
+     * {@link ImsConfigImplBase#CONFIG_RESULT_FAILED} or
+     * {@link ImsConfigImplBase#CONFIG_RESULT_SUCCESS} or
+     */
+    @VisibleForTesting
+    public int setProvisioningValue(int subId, int key, int value) {
+        log("setProvisioningValue");
+
+        int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED;
+        // check key value
+        if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == key)) {
+            log("not matched key " + key);
+            return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
+        }
+
+        // check subId
+        int slotId = getSlotId(subId);
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) {
+            loge("Fail to retrieve slotId from subId");
+            return ImsConfigImplBase.CONFIG_RESULT_FAILED;
+        }
+
+        try {
+            if (key == KEY_EAB_PROVISIONING_STATUS) {
+                // set key and value to vendor ImsService for Rcs
+                retVal = mRcsFeatureListenersSlotMap.get(slotId)
+                        .setProvisioningValue(key, value);
+            } else {
+                // set key and value to vendor ImsService for MmTel
+                retVal = mMmTelFeatureListenersSlotMap.get(slotId)
+                        .setProvisioningValue(key, value);
+            }
+        } catch (NullPointerException e) {
+            loge("can not access FeatureListener to set provisioning value");
+            return ImsConfigImplBase.CONFIG_RESULT_FAILED;
+        }
+
+        // update and notify provisioning status changed capability and tech from key
+        updateCapabilityTechFromKey(subId, key, value);
+
+        if (key != KEY_EAB_PROVISIONING_STATUS) {
+            // notify provisioning status changed to ImsManager
+            updateImsServiceConfig(subId);
+        }
+
+        return retVal;
+    }
+
+    /**
+     * get RCS provisioning status in specific key and value
+     * @param key integer key, defined as one of
+     * {@link ProvisioningManager#KEY_VOLTE_PROVISIONING_STATUS}
+     * {@link ProvisioningManager#KEY_VT_PROVISIONING_STATUS}
+     * {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE}
+     * {@link ProvisioningManager#KEY_EAB_PROVISIONING_STATUS}
+     * @return the result of setting the configuration value, defined as one of
+     * {@link ImsConfigImplBase#CONFIG_RESULT_FAILED} or
+     * {@link ImsConfigImplBase#CONFIG_RESULT_SUCCESS} or
+     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN}
+     */
+    @VisibleForTesting
+    public int getProvisioningValue(int subId, int key) {
+        log("getProvisioningValue");
+
+        // check key value
+        if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == key)) {
+            log("not matched key " + key);
+            return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
+        }
+
+        // check subId
+        int slotId = getSlotId(subId);
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) {
+            loge("Fail to retrieve slotId from subId");
+            return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
+        }
+
+        // check data from ImsProvisioningLoader
+        int capability = getCapabilityFromKey(key);
+        int tech = getTechFromKey(key);
+        int result;
+        if (capability != INVALID_VALUE && tech != INVALID_VALUE) {
+            if (key == KEY_EAB_PROVISIONING_STATUS) {
+                result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS,
+                        capability, tech);
+            } else {
+                result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL,
+                        capability, tech);
+            }
+            if (result != ImsProvisioningLoader.STATUS_NOT_SET) {
+                return result;
+            }
+        }
+
+        // get data from ImsService, update it in ImsProvisioningLoader
+        if (key == KEY_EAB_PROVISIONING_STATUS) {
+            result = getRcsValueFromImsService(subId, capability);
+            if (result == ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) {
+                logw("getProvisioningValue : fail to get data from ImsService capability"
+                        + capability);
+                return result;
+            }
+            setAndNotifyRcsProvisioningValueForAllTech(subId, capability, getBoolValue(result));
+            return result;
+        } else {
+            result = getValueFromImsService(subId, capability, tech);
+            if (result == ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) {
+                logw("getProvisioningValue : fail to get data from ImsService capability"
+                        + capability);
+                return result;
+            }
+            setAndNotifyMmTelProvisioningValue(subId, capability, tech, getBoolValue(result));
+            return result;
+        }
+    }
+
+    /**
+     * get the handler
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    private boolean isProvisioningRequired(int subId, int capability, int tech, boolean isMmTel) {
+        String[] dataArray;
+        if (isMmTel) {
+            dataArray = getMmTelStringArrayFromCarrierConfig(subId);
+        } else {
+            dataArray = getRcsStringArrayFromCarrierConfig(subId);
+        }
+        if (dataArray == null) {
+            logw("isProvisioningRequired : retrieve data from carrier config failed");
+
+            // KEY_MMTEL/RCS_REQUIRES_PROVISIONING_STRING_ARRAY is not exist in CarrierConfig that
+            // means provisioning is not required
+            return false;
+        }
+
+        // create String with capability and tech
+        String comp = capability + "," + tech;
+
+        // compare with carrier config
+        for (String data : dataArray) {
+            // existing same String {capability,tech} means provisioning required
+            if (data.replaceAll("\\s", "").equals(comp)) {
+                return true;
+            }
+        }
+
+        log("isProvisioningRequired : not matched capability " + capability + " tech " + tech);
+        return false;
+    }
+
+    @VisibleForTesting
+    protected String[] getMmTelStringArrayFromCarrierConfig(int subId) {
+        PersistableBundle imsCarrierConfigs = mCarrierConfigManager.getConfigByComponentForSubId(
+                CarrierConfigManager.Ims.KEY_PREFIX, subId);
+        return imsCarrierConfigs.getStringArray(
+                CarrierConfigManager.Ims.KEY_MMTEL_REQUIRES_PROVISIONING_STRING_ARRAY);
+    }
+
+    @VisibleForTesting
+    protected String[] getRcsStringArrayFromCarrierConfig(int subId) {
+        PersistableBundle imsCarrierConfigs = mCarrierConfigManager.getConfigByComponentForSubId(
+                CarrierConfigManager.Ims.KEY_PREFIX, subId);
+        return imsCarrierConfigs.getStringArray(
+                CarrierConfigManager.Ims.KEY_RCS_REQUIRES_PROVISIONING_STRING_ARRAY);
+    }
+
+    private int getValueFromImsService(int subId, int capability, int tech) {
+        int config = ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
+
+        // operation is based on capability
+        switch (capability) {
+            case CAPABILITY_TYPE_VOICE:
+                int item = (tech == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)
+                        ? ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE
+                        : ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
+                // read data from vendor ImsService
+                config = mMmTelFeatureListenersSlotMap.get(getSlotId(subId))
+                        .getProvisioningValue(item);
+                break;
+            case CAPABILITY_TYPE_VIDEO:
+                // read data from vendor ImsService
+                config = mMmTelFeatureListenersSlotMap.get(getSlotId(subId))
+                        .getProvisioningValue(ProvisioningManager.KEY_VT_PROVISIONING_STATUS);
+                break;
+            default:
+                log("Capability " + capability + " has been provisioning");
+                break;
+        }
+
+        return config;
+    }
+
+    private int getRcsValueFromImsService(int subId, int capability) {
+        int config = ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
+
+        if (capability == CAPABILITY_TYPE_PRESENCE_UCE) {
+            try {
+                config = mRcsFeatureListenersSlotMap.get(getSlotId(subId))
+                        .getProvisioningValue(ProvisioningManager.KEY_EAB_PROVISIONING_STATUS);
+            } catch (NullPointerException e) {
+                logw("can not access RcsFeatureListener");
+            }
+        } else {
+            log("Capability " + capability + " has been provisioning");
+        }
+
+        return config;
+    }
+
+    private void onSubscriptionsChanged() {
+        for (int index = 0; index < mMmTelFeatureListenersSlotMap.size(); index++) {
+            MmTelFeatureListener m = mMmTelFeatureListenersSlotMap.get(index);
+            m.setSubId(getSubId(index));
+        }
+        for (int index = 0; index < mRcsFeatureListenersSlotMap.size(); index++) {
+            RcsFeatureListener r = mRcsFeatureListenersSlotMap.get(index);
+            r.setSubId(getSubId(index));
+        }
+        for (int index = 0; index < mProvisioningCallbackManagersSlotMap.size(); index++) {
+            ProvisioningCallbackManager m = mProvisioningCallbackManagersSlotMap.get(index);
+            m.setSubId(getSubId(index));
+        }
+    }
+
+    private void  updateCapabilityTechFromKey(int subId, int key, int value) {
+        boolean isProvisioned = getBoolValue(value);
+        int capability = getCapabilityFromKey(key);
+        int tech = getTechFromKey(key);
+
+        if (capability == INVALID_VALUE || tech == INVALID_VALUE) {
+            logw("updateCapabilityTechFromKey : unknown key " + key);
+            return;
+        }
+
+        if (key == KEY_VOLTE_PROVISIONING_STATUS
+                || key == KEY_VT_PROVISIONING_STATUS
+                || key == KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE) {
+            setAndNotifyMmTelProvisioningValue(subId, capability, tech, isProvisioned);
+        }
+        if (key == KEY_EAB_PROVISIONING_STATUS) {
+            setAndNotifyRcsProvisioningValueForAllTech(subId, capability, isProvisioned);
+        }
+    }
+
+    private int getCapabilityFromKey(int key) {
+        int capability;
+        switch (key) {
+            case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
+                // intentional fallthrough
+            case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE:
+                capability = CAPABILITY_TYPE_VOICE;
+                break;
+            case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
+                capability = CAPABILITY_TYPE_VIDEO;
+                break;
+            case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
+                // default CAPABILITY_TYPE_PRESENCE_UCE used for KEY_EAB_PROVISIONING_STATUS
+                capability = CAPABILITY_TYPE_PRESENCE_UCE;
+                break;
+            default:
+                capability = INVALID_VALUE;
+                break;
+        }
+        return capability;
+    }
+
+    private int getTechFromKey(int key) {
+        int tech;
+        switch (key) {
+            case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE:
+                tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+                break;
+            case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
+                // intentional fallthrough
+            case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
+                // intentional fallthrough
+            case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
+                tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+                break;
+            default:
+                tech = INVALID_VALUE;
+                break;
+        }
+        return tech;
+    }
+
+    private int getKeyFromCapability(int capability, int tech) {
+        int key = INVALID_VALUE;
+        if (capability == CAPABILITY_TYPE_VOICE && tech == REGISTRATION_TECH_IWLAN) {
+            key = ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE;
+        } else if (capability == CAPABILITY_TYPE_VOICE && tech == REGISTRATION_TECH_LTE) {
+            key = ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
+        } else if (capability == CAPABILITY_TYPE_VIDEO && tech == REGISTRATION_TECH_LTE) {
+            key = ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
+        }
+
+        return key;
+    }
+
+    protected int getSubId(int slotId) {
+        final int[] subIds = mSubscriptionManager.getSubscriptionIds(slotId);
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        if (subIds != null && subIds.length >= 1) {
+            subId = subIds[0];
+        }
+
+        return subId;
+    }
+
+    protected int getSlotId(int subId) {
+        return mSubscriptionManager.getPhoneId(subId);
+    }
+
+    protected ImsConfig getImsConfig(ImsManager imsManager) throws ImsException {
+        return imsManager.getConfigInterface();
+    }
+
+    protected ImsConfig getImsConfig(IImsConfig iImsConfig) {
+        return new ImsConfig(iImsConfig);
+    }
+
+    private int getIntValue(boolean isProvisioned) {
+        return isProvisioned ? ProvisioningManager.PROVISIONING_VALUE_ENABLED
+                : ProvisioningManager.PROVISIONING_VALUE_DISABLED;
+    }
+
+    private boolean getBoolValue(int value) {
+        return value == ProvisioningManager.PROVISIONING_VALUE_ENABLED ? true : false;
+    }
+
+    private boolean setAndNotifyMmTelProvisioningValue(int subId, int capability, int tech,
+            boolean isProvisioned) {
+        boolean changed = mImsProvisioningLoader.setProvisioningStatus(subId, FEATURE_MMTEL,
+                capability, tech, isProvisioned);
+        // notify MmTel capability changed
+        if (changed) {
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_PROVISIONING_CAPABILITY_CHANGED,
+                    getSlotId(subId), 0, (Object) new FeatureProvisioningData(
+                            capability, tech, isProvisioned, /*isMmTel*/true)));
+        }
+
+        return changed;
+    }
+
+    private boolean setAndNotifyRcsProvisioningValue(int subId, int capability, int tech,
+            boolean isProvisioned) {
+        boolean isChanged = mImsProvisioningLoader.setProvisioningStatus(subId, FEATURE_RCS,
+                capability, tech, isProvisioned);
+
+        if (isChanged) {
+            int slotId = getSlotId(subId);
+
+            // notify RCS capability changed
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_PROVISIONING_CAPABILITY_CHANGED,
+                    slotId, 0, (Object) new FeatureProvisioningData(
+                            capability, tech, isProvisioned, /*isMmtel*/false)));
+        }
+
+        return isChanged;
+    }
+
+    private boolean setAndNotifyRcsProvisioningValueForAllTech(int subId, int capability,
+            boolean isProvisioned) {
+        boolean isChanged = false;
+
+        for (int tech : LOCAL_RADIO_TECHS) {
+            isChanged |= setAndNotifyRcsProvisioningValue(subId, capability, tech, isProvisioned);
+        }
+
+        return isChanged;
+    }
+
+    private void updateImsServiceConfig(int subId) {
+        try {
+            ImsManager imsManager = mMmTelFeatureListenersSlotMap.get(getSlotId(subId))
+                    .getImsManager();
+            imsManager.updateImsServiceConfig();
+            log("updateImsServiceConfig");
+        } catch (NullPointerException e) {
+            loge("updateImsServiceConfig : ImsService not ready");
+        }
+    }
+
+    protected boolean isValidSubId(int subId) {
+        int slotId = getSlotId(subId);
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void log(String prefix, int slotId, String s) {
+        Rlog.d(TAG, prefix + "[" + slotId + "] " + s);
+    }
+
+    private void logi(String prefix, int slotId, String s) {
+        Rlog.i(TAG, prefix + "[" + slotId + "] " + s);
+    }
+
+    private void logw(String s) {
+        Rlog.w(TAG, s);
+    }
+
+    private void logw(String prefix, int slotId, String s) {
+        Rlog.w(TAG, prefix + "[" + slotId + "] " + s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+    private void loge(String prefix, int slotId, String s) {
+        Rlog.e(TAG, prefix + "[" + slotId + "] " + s);
+    }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index e7cb28c..a4f405b 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -73,7 +73,6 @@
 import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
-import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.util.IndentingPrintWriter;
@@ -161,6 +160,7 @@
     public PhoneInterfaceManager phoneMgr;
     public ImsRcsController imsRcsController;
     public ImsStateCallbackController mImsStateCallbackController;
+    public ImsProvisioningController mImsProvisioningController;
     CarrierConfigLoader configLoader;
 
     private Phone phoneInEcm;
@@ -470,6 +470,8 @@
                         PhoneFactory.getPhones().length);
                 mTelephonyRcsService.initialize();
                 imsRcsController.setRcsService(mTelephonyRcsService);
+                mImsProvisioningController =
+                        ImsProvisioningController.make(this, PhoneFactory.getPhones().length);
             }
 
             configLoader = CarrierConfigLoader.init(this);
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 4da4004..bcbbeac 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -124,6 +124,7 @@
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IFeatureProvisioningCallback;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
@@ -4595,6 +4596,7 @@
     @Override
     public void registerImsProvisioningChangedCallback(int subId, IImsConfigCallback callback) {
         enforceReadPrivilegedPermission("registerImsProvisioningChangedCallback");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             if (!isImsAvailableOnDevice()) {
@@ -4615,6 +4617,7 @@
     @Override
     public void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback) {
         enforceReadPrivilegedPermission("unregisterImsProvisioningChangedCallback");
+
         final long identity = Binder.clearCallingIdentity();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
@@ -4632,6 +4635,39 @@
         }
     }
 
+    @Override
+    public void registerFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback) {
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "registerFeatureProvisioningChangedCallback");
+
+        final long identity = Binder.clearCallingIdentity();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+
+        ImsProvisioningController.getInstance()
+                .addFeatureProvisioningChangedCallback(subId, callback);
+
+        Binder.restoreCallingIdentity(identity);
+    }
+
+    @Override
+    public void unregisterFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback) {
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "unregisterFeatureProvisioningChangedCallback");
+
+        final long identity = Binder.clearCallingIdentity();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+        }
+
+        ImsProvisioningController.getInstance()
+                .removeFeatureProvisioningChangedCallback(subId, callback);
+
+        Binder.restoreCallingIdentity(identity);
+    }
 
     private void checkModifyPhoneStatePermission(int subId, String message) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
@@ -4658,60 +4694,30 @@
     }
 
     @Override
-    public void setRcsProvisioningStatusForCapability(int subId, int capability,
+    public void setRcsProvisioningStatusForCapability(int subId, int capability, int tech,
             boolean isProvisioned) {
         checkModifyPhoneStatePermission(subId, "setRcsProvisioningStatusForCapability");
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            if (!isImsProvisioningRequired(subId, capability, false)) {
-                return;
-            }
-
-            // this capability requires provisioning, route to the correct API.
-            ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
-            switch (capability) {
-                case RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE:
-                case RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE:
-                    ims.setEabProvisioned(isProvisioned);
-                    break;
-                default: {
-                    throw new IllegalArgumentException("Tried to set provisioning for "
-                            + "rcs capability '" + capability + "', which does not require "
-                            + "provisioning.");
-                }
-            }
+            ImsProvisioningController.getInstance()
+                    .setRcsProvisioningStatusForCapability(subId, capability, tech, isProvisioned);
+            return;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-
     }
 
 
     @Override
-    public boolean getRcsProvisioningStatusForCapability(int subId, int capability) {
-        enforceReadPrivilegedPermission("getRcsProvisioningStatusForCapability");
+    public boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech) {
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getRcsProvisioningStatusForCapability");
+
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            if (!isImsProvisioningRequired(subId, capability, false)) {
-                return true;
-            }
-
-            ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
-            switch (capability) {
-                case RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE:
-                case RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE:
-                    return ims.isEabProvisionedOnDevice();
-
-                default: {
-                    throw new IllegalArgumentException("Tried to get rcs provisioning for "
-                            + "capability '" + capability + "', which does not require "
-                            + "provisioning.");
-                }
-            }
-
+            return ImsProvisioningController.getInstance()
+                    .getRcsProvisioningStatusForCapability(subId, capability, tech);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -4720,66 +4726,12 @@
     @Override
     public void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
             boolean isProvisioned) {
-        if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
-        }
         checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
+
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            if (!isImsProvisioningRequired(subId, capability, true)) {
-                return;
-            }
-            if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
-                    || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                loge("setImsProvisioningStatusForCapability: called for technology that does "
-                        + "not support provisioning - " + tech);
-                return;
-            }
-
-            // this capability requires provisioning, route to the correct API.
-            ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
-            switch (capability) {
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE: {
-                    if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-                        ims.setVolteProvisioned(isProvisioned);
-                    } else if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
-                        ims.setWfcProvisioned(isProvisioned);
-                    }
-                    break;
-                }
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO: {
-                    // There is currently no difference in VT provisioning type.
-                    ims.setVtProvisioned(isProvisioned);
-                    break;
-                }
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT: {
-                    // There is no "deprecated" UT provisioning mechanism through ImsConfig, so
-                    // change the capability of the feature instead if needed.
-                    if (isMmTelCapabilityProvisionedInCache(subId, capability, tech)
-                            == isProvisioned) {
-                        // No change in provisioning.
-                        return;
-                    }
-                    cacheMmTelCapabilityProvisioning(subId, capability, tech, isProvisioned);
-                    try {
-                        ims.changeMmTelCapability(isProvisioned, capability, tech);
-                    } catch (com.android.ims.ImsException e) {
-                        loge("setImsProvisioningStatusForCapability: couldn't change UT capability"
-                                + ", Exception" + e.getMessage());
-                    }
-                    break;
-                }
-                default: {
-                    throw new IllegalArgumentException("Tried to set provisioning for "
-                            + "MmTel capability '" + capability + "', which does not require "
-                            + "provisioning. ");
-                }
-            }
-
+            ImsProvisioningController.getInstance()
+                    .setImsProvisioningStatusForCapability(subId, capability, tech, isProvisioned);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -4787,54 +4739,13 @@
 
     @Override
     public boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech) {
-        if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
-        }
-        enforceReadPrivilegedPermission("getProvisioningStatusForCapability");
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getProvisioningStatusForCapability");
+
         final long identity = Binder.clearCallingIdentity();
         try {
-            // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
-            if (!isImsProvisioningRequired(subId, capability, true)) {
-                return true;
-            }
-
-            if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
-                    || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                loge("getImsProvisioningStatusForCapability: called for technology that does "
-                        + "not support provisioning - " + tech);
-                return true;
-            }
-
-            ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
-            switch (capability) {
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE: {
-                    if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-                        return ims.isVolteProvisionedOnDevice();
-                    } else if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
-                        return ims.isWfcProvisionedOnDevice();
-                    }
-                    // This should never happen, since we are checking tech above to make sure it
-                    // is either LTE or IWLAN.
-                    throw new IllegalArgumentException("Invalid radio technology for voice "
-                            + "capability.");
-                }
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO: {
-                    // There is currently no difference in VT provisioning type.
-                    return ims.isVtProvisionedOnDevice();
-                }
-                case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT: {
-                    // There is no "deprecated" UT provisioning mechanism, so get from shared prefs.
-                    return isMmTelCapabilityProvisionedInCache(subId, capability, tech);
-                }
-                default: {
-                    throw new IllegalArgumentException(
-                            "Tried to get provisioning for MmTel capability '" + capability
-                                    + "', which does not require provisioning.");
-                }
-            }
+            return ImsProvisioningController.getInstance()
+                    .getImsProvisioningStatusForCapability(subId, capability, tech);
 
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -4842,61 +4753,31 @@
     }
 
     @Override
-    public boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech) {
-        if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
+    public boolean isProvisioningRequiredForCapability(int subId, int capability, int tech) {
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isProvisioningRequiredForCapability");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return ImsProvisioningController.getInstance()
+                    .isImsProvisioningRequiredForCapability(subId, capability, tech);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-        enforceReadPrivilegedPermission("isMmTelCapabilityProvisionedInCache");
-        int provisionedBits = getMmTelCapabilityProvisioningBitfield(subId, tech);
-        return (provisionedBits & capability) > 0;
     }
 
     @Override
-    public void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech,
-            boolean isProvisioned) {
-        if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
-                && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-            throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
-        }
-        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
-                "setProvisioningStatusForCapability");
-        int provisionedBits = getMmTelCapabilityProvisioningBitfield(subId, tech);
-        // If the current provisioning status for capability already matches isProvisioned,
-        // do nothing.
-        if (((provisionedBits & capability) > 0) == isProvisioned) {
-            return;
-        }
-        if (isProvisioned) {
-            setMmTelCapabilityProvisioningBitfield(subId, tech, (provisionedBits | capability));
-        } else {
-            setMmTelCapabilityProvisioningBitfield(subId, tech, (provisionedBits & ~capability));
-        }
-    }
+    public boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech) {
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "isProvisioningRequiredForCapability");
 
-    /**
-     * @return the bitfield containing the MmTel provisioning for the provided subscription and
-     * technology. The bitfield should mirror the bitfield defined by
-     * {@link MmTelFeature.MmTelCapabilities.MmTelCapability}.
-     */
-    private int getMmTelCapabilityProvisioningBitfield(int subId, int tech) {
-        String key = getMmTelProvisioningKey(subId, tech);
-        // Default is no capabilities are provisioned.
-        return mTelephonySharedPreferences.getInt(key, 0 /*default*/);
-    }
-
-    /**
-     * Sets the MmTel capability provisioning bitfield (defined by
-     *     {@link MmTelFeature.MmTelCapabilities.MmTelCapability}) for the subscription and
-     *     technology specified.
-     *
-     * Note: This is a synchronous command and should not be called on UI thread.
-     */
-    private void setMmTelCapabilityProvisioningBitfield(int subId, int tech, int newField) {
-        final SharedPreferences.Editor editor = mTelephonySharedPreferences.edit();
-        String key = getMmTelProvisioningKey(subId, tech);
-        editor.putInt(key, newField);
-        editor.commit();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return ImsProvisioningController.getInstance()
+                    .isRcsProvisioningRequiredForCapability(subId, capability, tech);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     private static String getMmTelProvisioningKey(int subId, int tech) {
@@ -4969,7 +4850,9 @@
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid Subscription id '" + subId + "'");
         }
-        enforceReadPrivilegedPermission("getImsProvisioningInt");
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getImsProvisioningInt");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -4979,6 +4862,12 @@
                         + subId + "' for key:" + key);
                 return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
             }
+
+            int retVal = ImsProvisioningController.getInstance().getProvisioningValue(subId, key);
+            if (retVal != ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) {
+                return retVal;
+            }
+
             return ImsManager.getInstance(mApp, slotId).getConfigInt(key);
         } catch (com.android.ims.ImsException e) {
             Log.w(LOG_TAG, "getImsProvisioningInt: ImsService is not available for subscription '"
@@ -4994,7 +4883,9 @@
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid Subscription id '" + subId + "'");
         }
-        enforceReadPrivilegedPermission("getImsProvisioningString");
+        TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                mApp, subId, "getImsProvisioningString");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5021,6 +4912,7 @@
         }
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
                 "setImsProvisioningInt");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5030,6 +4922,13 @@
                         + subId + "' for key:" + key);
                 return ImsConfigImplBase.CONFIG_RESULT_FAILED;
             }
+
+            int retVal = ImsProvisioningController.getInstance()
+                    .setProvisioningValue(subId, key, value);
+            if (retVal != ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) {
+                return retVal;
+            }
+
             return ImsManager.getInstance(mApp, slotId).setConfig(key, value);
         } catch (com.android.ims.ImsException | RemoteException e) {
             Log.w(LOG_TAG, "setImsProvisioningInt: ImsService unavailable for sub '" + subId
diff --git a/tests/src/com/android/phone/ImsProvisioningControllerTest.java b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
new file mode 100644
index 0000000..0ad9b60
--- /dev/null
+++ b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
@@ -0,0 +1,1632 @@
+/*
+ * Copyright 2021 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 android.telephony.ims.ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE;
+import static android.telephony.ims.ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
+import static android.telephony.ims.ProvisioningManager.PROVISIONING_VALUE_ENABLED;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
+import static android.telephony.ims.feature.RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.aidl.IFeatureProvisioningCallback;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
+import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
+import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+
+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;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests for ImsProvisioningContorller
+ */
+public class ImsProvisioningControllerTest {
+    private static final String TAG = "ImsProvisioningControllerTest";
+    private static final int[] MMTEL_CAPAS = new int[]{
+            CAPABILITY_TYPE_VOICE,
+            CAPABILITY_TYPE_VIDEO,
+            CAPABILITY_TYPE_UT,
+            CAPABILITY_TYPE_SMS,
+            CAPABILITY_TYPE_CALL_COMPOSER
+    };
+    private static final int MMTEL_CAPA_INVALID = 0;
+
+    private static final int RCS_CAPA_INVALID = RcsImsCapabilities.CAPABILITY_TYPE_NONE;
+
+    private static final int[] RADIO_TECHS = new int[]{
+            REGISTRATION_TECH_LTE,
+            REGISTRATION_TECH_IWLAN,
+            REGISTRATION_TECH_CROSS_SIM,
+            REGISTRATION_TECH_NR
+    };
+    private static final int RADIO_TECH_INVALID = ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+
+    @Mock
+    Context mContext;
+
+    @Mock
+    PhoneGlobals mPhone;
+    @Mock
+    CarrierConfigManager mCarrierConfigManager;
+    private PersistableBundle mPersistableBundle0;
+    private PersistableBundle mPersistableBundle1;
+    @Mock
+    SubscriptionManager mSubscriptionManager;
+    @Mock
+    TelephonyRegistryManager mTelephonyRegistryManager;
+    @Mock
+    ImsProvisioningLoader mImsProvisioningLoader;
+
+    @Mock
+    ImsManager mImsManager;
+    @Mock
+    ImsConfig mImsConfig;
+    @Mock
+    ImsProvisioningController.MmTelFeatureConnector mMmTelFeatureConnector;
+    @Mock
+    FeatureConnector<ImsManager> mMmTelFeatureConnector0;
+    @Mock
+    FeatureConnector<ImsManager> mMmTelFeatureConnector1;
+    @Captor
+    ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListener0;
+    @Captor
+    ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListener1;
+
+    @Mock
+    RcsFeatureManager mRcsFeatureManager;
+    @Mock
+    ImsProvisioningController.RcsFeatureConnector mRcsFeatureConnector;
+    @Mock
+    FeatureConnector<RcsFeatureManager> mRcsFeatureConnector0;
+    @Mock
+    FeatureConnector<RcsFeatureManager> mRcsFeatureConnector1;
+    @Captor
+    ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListener0;
+    @Captor
+    ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListener1;
+
+    @Mock
+    IFeatureProvisioningCallback mIFeatureProvisioningCallback0;
+    @Mock
+    IFeatureProvisioningCallback mIFeatureProvisioningCallback1;
+
+    @Mock
+    IBinder mIbinder0;
+    @Mock
+    IBinder mIbinder1;
+
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
+
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+    private TestableLooper mLooper;
+
+    TestImsProvisioningController mTestImsProvisioningController;
+
+    int mPhoneId0 = 0;
+    int mPhoneId1 = 1;
+    int mSubId0 = 1234;
+    int mSubId1 = 5678;
+
+    String[] mMmTelStringArray;
+    String[] mRcsStringArray;
+    int[][] mMmTelProvisioningStorage;
+    int[][] mRcsProvisioningStorage;
+    int[][] mImsConfigStorage;
+
+    private class TestImsProvisioningController extends ImsProvisioningController {
+        boolean mIsValidSubId = true;
+
+        TestImsProvisioningController() {
+            super(mPhone, 2, mHandlerThread.getLooper(),
+                    mMmTelFeatureConnector, mRcsFeatureConnector,
+                    mImsProvisioningLoader);
+        }
+
+        protected int getSubId(int slotId) {
+            return (slotId == mPhoneId0) ? mSubId0 : mSubId1;
+        }
+
+        protected int getSlotId(int subId) {
+            return (subId == mSubId0) ? mPhoneId0 : mPhoneId1;
+        }
+
+        protected ImsConfig getImsConfig(ImsManager imsManager) {
+            return mImsConfig;
+        }
+
+        protected ImsConfig getImsConfig(IImsConfig iImsConfig) {
+            return mImsConfig;
+        }
+
+        protected String[] getMmTelStringArrayFromCarrierConfig(int subId) {
+            return mMmTelStringArray;
+        }
+
+        protected String[] getRcsStringArrayFromCarrierConfig(int subId) {
+            return mRcsStringArray;
+        }
+
+        protected boolean isValidSubId(int subId) {
+            return mIsValidSubId;
+        }
+
+        public void setValidSubId(boolean valid) {
+            mIsValidSubId = valid;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("setUp");
+        MockitoAnnotations.initMocks(this);
+
+        when(mPhone.getSystemServiceName(eq(CarrierConfigManager.class)))
+                .thenReturn(Context.CARRIER_CONFIG_SERVICE);
+        when(mPhone.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE)))
+                .thenReturn(mCarrierConfigManager);
+
+        mPersistableBundle0 = new PersistableBundle();
+        mPersistableBundle1 = new PersistableBundle();
+
+        when(mCarrierConfigManager.getConfigForSubId(eq(mSubId0)))
+                .thenReturn(mPersistableBundle0);
+        when(mCarrierConfigManager.getConfigForSubId(eq(mSubId1)))
+                .thenReturn(mPersistableBundle1);
+
+        when(mPhone.getSystemServiceName(eq(SubscriptionManager.class)))
+                .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+                .thenReturn(mSubscriptionManager);
+
+
+        when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class)))
+                .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE);
+        when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE)))
+                .thenReturn(mTelephonyRegistryManager);
+        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());
+
+        mHandlerThread = new HandlerThread("ImsStateCallbackControllerTest");
+        mHandlerThread.start();
+
+        initializeDefaultData();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd("tearDown");
+        if (mTestImsProvisioningController != null) {
+            mTestImsProvisioningController.destroy();
+            mTestImsProvisioningController = null;
+        }
+
+        if (mLooper != null) {
+            mLooper.destroy();
+            mLooper = null;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void addFeatureProvisioningChangedCallback_withCallback() throws Exception {
+        createImsProvisioningController();
+
+        // add callback with valid obj
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+        // add callback with invalid obj
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(mSubId0, null);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+        // remove callback with valid obj
+        try {
+            mTestImsProvisioningController.removeFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+        // remove callback with invalid obj
+        try {
+            mTestImsProvisioningController.removeFeatureProvisioningChangedCallback(mSubId0, null);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void connectionReady_MmTelFeatureListener() throws Exception {
+        createImsProvisioningController();
+
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId1, mIFeatureProvisioningCallback1);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+
+        // change subId to be invalid
+        mTestImsProvisioningController.setValidSubId(false);
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+
+        // setConfig not called, wait until subId is valid
+        verify(mImsConfig, times(0)).setConfig(anyInt(), anyInt());
+
+        // change subId
+        mSubChangedListener.onSubscriptionsChanged();
+        processAllMessages();
+
+        int[] keys = {
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE,
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS,
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS};
+
+        // verify # of read data times from storage : # of MmTel storage length
+        verify(mImsProvisioningLoader, atLeast(keys.length))
+                .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), anyInt(), anyInt());
+
+        for (int index = 0; index < keys.length; index++) {
+            // verify function call vendor interface
+            verify(mImsConfig, times(1)).setConfig(eq(keys[index]), anyInt());
+        }
+
+        // verify other interactions
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+    }
+
+    @Test
+    @SmallTest
+    public void connectionReady_RcsFeatureListener() throws Exception {
+        createImsProvisioningController();
+
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId1, mIFeatureProvisioningCallback1);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // verify # of read data times from storage : # of Rcs storage length
+        verify(mImsProvisioningLoader, times(1))
+                .getProvisioningStatus(eq(mSubId0), eq(FEATURE_RCS), anyInt(), anyInt());
+
+        int key = ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+        // verify function call vendor interface
+        verify(mImsConfig, times(1)).setConfig(eq(key), anyInt());
+
+        // verify other interactions
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+    }
+
+    @Test
+    @SmallTest
+    public void isImsProvisioningRequiredForCapability_withInvalidCapabilityTech()
+            throws Exception {
+        createImsProvisioningController();
+
+        // invalid Capa. and valid Radio tech - IllegalArgumentException OK
+        try {
+            mTestImsProvisioningController.isImsProvisioningRequiredForCapability(
+                    mSubId0, MMTEL_CAPA_INVALID, RADIO_TECHS[0]);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+
+        // valid Capa. and invalid Radio tech - IllegalArumentException OK
+        try {
+            mTestImsProvisioningController.isImsProvisioningRequiredForCapability(
+                    mSubId0, MMTEL_CAPAS[0], RADIO_TECH_INVALID);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void isImsProvisioningRequiredForCapability_withValidCapabilityTech() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability
+        // voice, all tech
+        // video, all tech
+        // UT, all tech
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1", "1,2", "1,3",
+                "2,0", "2,1", "2,2", "2,3",
+                "4,0", "4,1", "4,2", "4,3"
+        };
+
+        boolean[][] expectedRequired = new boolean[][] {
+                {true, true, true, true},
+                {true, true, true, true},
+                {true, true, true, true},
+                {false, false, false, false},
+                {false, false, false, false}
+        };
+
+        boolean isRequired;
+        for (int i = 0; i < MMTEL_CAPAS.length; i++) {
+            for (int j = 0; j < RADIO_TECHS.length; j++) {
+                isRequired = mTestImsProvisioningController
+                .isImsProvisioningRequiredForCapability(
+                        mSubId0, MMTEL_CAPAS[i], RADIO_TECHS[j]);
+                assertEquals(expectedRequired[i][j], isRequired);
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void isRcsProvisioningRequiredForCapability_withInvalidCapabilityTech()
+            throws Exception {
+        createImsProvisioningController();
+
+        // invalid Capa. and valid Radio tech - IllegalArgumentException OK
+        try {
+            mTestImsProvisioningController.isRcsProvisioningRequiredForCapability(
+                    mSubId0, RCS_CAPA_INVALID, RADIO_TECHS[0]);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+
+        // valid Capa. and invalid Radio tech - IllegalArumentException OK
+        try {
+            mTestImsProvisioningController.isRcsProvisioningRequiredForCapability(
+                    mSubId0, CAPABILITY_TYPE_PRESENCE_UCE, RADIO_TECH_INVALID);
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void isRcsProvisioningRequiredForCapability_withValidCapabilityTech() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability : PRESENCE, tech : all
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+
+        boolean[] expectedRequired = new boolean[] {true, true, true, true};
+
+        boolean isRequired;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            isRequired = mTestImsProvisioningController
+            .isRcsProvisioningRequiredForCapability(
+                    mSubId0, CAPABILITY_TYPE_PRESENCE_UCE, RADIO_TECHS[i]);
+            assertEquals(expectedRequired[i], isRequired);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void getImsProvisioningRequiredForCapability_withVoiceVideoUt() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability
+        // voice, all tech
+        // video, all tech
+        // UT, all tech
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1", "1,2", "1,3",
+                "2,0", "2,1", "2,2", "2,3",
+                "4,0", "4,1", "4,2", "4,3"
+        };
+        // provisioning Status
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_IWLAN, 0},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_IWLAN, 0},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_CROSS_SIM, 0},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_NR, 0},
+        };
+
+        boolean[] expectedVoiceProvisioningStatus = new boolean[] {false, true, true, true};
+        boolean[] expectedVideoProvisioningStatus = new boolean[] {false, false, true, true};
+        boolean[] expectedUtProvisioningStatus = new boolean[] {false, false, false, false};
+
+        boolean provisioned = false;
+        int capability = CAPABILITY_TYPE_VOICE;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisioned = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value
+            assertEquals(expectedVoiceProvisioningStatus[i], provisioned);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1))
+                    .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]));
+        }
+
+        capability = CAPABILITY_TYPE_VIDEO;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisioned = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value
+            assertEquals(expectedVideoProvisioningStatus[i], provisioned);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1))
+                    .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]));
+        }
+
+        capability = CAPABILITY_TYPE_UT;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisioned = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value
+            assertEquals(expectedUtProvisioningStatus[i], provisioned);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1))
+                    .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]));
+        }
+
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void getImsProvisioningRequiredForCapability_withNotSet() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        processAllMessages();
+
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // voice, LTE, IWLAN
+        // video, LTE
+        mMmTelStringArray = new String[]{
+                "1,0", "1,1",
+                "2,0"
+        };
+        // provisioning StatusP, all of provisioning status is not provisioned
+        mMmTelProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, -1}
+        };
+        // provisioning status in vendor ImsService
+        mImsConfigStorage = new int[][] {
+                {KEY_VOLTE_PROVISIONING_STATUS, 1},
+                {KEY_VT_PROVISIONING_STATUS, 0},
+                {KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, 1},
+        };
+
+        boolean provisioned;
+
+        // for KEY_VOLTE_PROVISIONING_STATUS
+        int capability = CAPABILITY_TYPE_VOICE;
+        int tech = REGISTRATION_TECH_LTE;
+        provisioned = mTestImsProvisioningController
+                .getImsProvisioningStatusForCapability(mSubId0, capability, tech);
+
+        // verify return value default false - not provisioned
+        assertEquals(true, provisioned);
+
+        verify(mImsProvisioningLoader, times(1))
+                .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability), eq(tech));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsConfig, times(1)).getConfigInt(eq(KEY_VOLTE_PROVISIONING_STATUS));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(1))
+                .setProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability), eq(tech),
+                        eq(provisioned));
+
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // for KEY_VT_PROVISIONING_STATUS
+        capability = CAPABILITY_TYPE_VIDEO;
+        tech = REGISTRATION_TECH_LTE;
+        provisioned = mTestImsProvisioningController
+                .getImsProvisioningStatusForCapability(mSubId0, capability, tech);
+
+        // verify return value default false - not provisioned
+        assertEquals(false, provisioned);
+
+        verify(mImsProvisioningLoader, times(1))
+                .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability), eq(tech));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsConfig, times(1)).getConfigInt(eq(KEY_VT_PROVISIONING_STATUS));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(1))
+                .setProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability), eq(tech),
+                        eq(provisioned));
+
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void getRcsProvisioningRequiredForCapability_withPresence() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability
+        // PRESENCE, all tech
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status
+        mRcsProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, 1}
+        };
+
+        boolean[] expectedPresenceProvisioningStatus = new boolean[] {true, true, true, true};
+
+        boolean provisioned = false;
+        int capability = CAPABILITY_TYPE_PRESENCE_UCE;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisioned = mTestImsProvisioningController
+                    .getRcsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value
+            assertEquals(expectedPresenceProvisioningStatus[i], provisioned);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(
+                    eq(mSubId0), eq(FEATURE_RCS), eq(capability), eq(RADIO_TECHS[i]));
+        }
+
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void getRcsProvisioningRequiredForCapability_withNotSet() throws Exception {
+        createImsProvisioningController();
+
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // PRESENCE, LTE, IWLAN
+        mRcsStringArray = new String[]{
+                "2,0", "2,1"
+        };
+        // provisioning Status, all of provisioning status is not provisioned
+        mRcsProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, -1}
+        };
+        // provisioning status in vendor ImsService
+        mImsConfigStorage = new int[][] {
+                {KEY_EAB_PROVISIONING_STATUS, 1}
+        };
+
+        boolean provisioned;
+
+        // for KEY_EAB_PROVISIONING_STATUS
+        int capability = CAPABILITY_TYPE_PRESENCE_UCE;
+        int tech = REGISTRATION_TECH_LTE;
+        provisioned = mTestImsProvisioningController
+                .getRcsProvisioningStatusForCapability(mSubId0, capability, tech);
+
+        // verify return value default false - not provisioned
+        assertEquals(true, provisioned);
+
+        verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capability), eq(tech));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsConfig, times(1)).getConfigInt(eq(KEY_EAB_PROVISIONING_STATUS));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(RADIO_TECHS.length)).setProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capability), anyInt(), eq(provisioned));
+
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void setImsProvisioningRequiredForCapability_withVoice() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        processAllMessages();
+
+        // register callbacks
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // voice, all tech
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1", "1,2", "1,3"
+        };
+        // provisioning Status, all of provisioning status is not provisioned
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, 0},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_CROSS_SIM, 0},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_NR, 0}
+        };
+
+        boolean provisionedFirst = false;
+        boolean provisionedSecond = false;
+        int capability = CAPABILITY_TYPE_VOICE;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisionedFirst = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value default false - not provisioned
+            assertEquals(false, provisionedFirst);
+
+            mTestImsProvisioningController.setImsProvisioningStatusForCapability(
+                    mSubId0, capability, RADIO_TECHS[i], !provisionedFirst);
+            processAllMessages();
+
+            provisionedSecond = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value default false - provisioned
+            assertEquals(!provisionedFirst, provisionedSecond);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(2))
+                    .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]));
+            verify(mImsProvisioningLoader, times(1))
+                    .setProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]), eq(provisionedSecond));
+
+            // verify weather Callback is called or not
+            verify(mIFeatureProvisioningCallback0, times(1))
+                    .onFeatureProvisioningChanged(eq(capability), eq(RADIO_TECHS[i]),
+                            eq(provisionedSecond));
+        }
+
+        // verify weather ImsConfig is called or not
+        verify(mImsConfig, times(1)).setConfig(
+                eq(KEY_VOLTE_PROVISIONING_STATUS), eq(PROVISIONING_VALUE_ENABLED));
+        verify(mImsConfig, times(1)).setConfig(
+                eq(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE), eq(PROVISIONING_VALUE_ENABLED));
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void setImsProvisioningRequiredForCapability_withVideo() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        processAllMessages();
+
+        // register callbacks
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // video, all tech
+        mMmTelStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not provisioned
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_IWLAN, 0},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_CROSS_SIM, 0},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_NR, 0}
+        };
+
+        boolean provisionedFirst = false;
+        boolean provisionedSecond = false;
+        int capability = CAPABILITY_TYPE_VIDEO;
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            // get provisioning status
+            provisionedFirst = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value default false - not provisioned
+            assertEquals(false, provisionedFirst);
+
+            mTestImsProvisioningController.setImsProvisioningStatusForCapability(
+                    mSubId0, capability, RADIO_TECHS[i], !provisionedFirst);
+            processAllMessages();
+
+            provisionedSecond = mTestImsProvisioningController
+                    .getImsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value default false - provisioned
+            assertEquals(!provisionedFirst, provisionedSecond);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(2))
+                    .getProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]));
+            verify(mImsProvisioningLoader, times(1))
+                    .setProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capability),
+                            eq(RADIO_TECHS[i]), eq(provisionedSecond));
+
+            // verify weather Callback is called or not
+            verify(mIFeatureProvisioningCallback0, times(1))
+                    .onFeatureProvisioningChanged(eq(capability), eq(RADIO_TECHS[i]),
+                            eq(provisionedSecond));
+        }
+
+        // verify weather ImsConfig is called or not
+        verify(mImsConfig, times(1)).setConfig(
+                eq(KEY_VT_PROVISIONING_STATUS), eq(PROVISIONING_VALUE_ENABLED));
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+
+    @Test
+    @SmallTest
+    public void setRcsProvisioningRequiredForCapability_withPresence() throws Exception {
+        createImsProvisioningController();
+
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // register callbacks
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // PRESENCE, all tech
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not provisioned
+        mRcsProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, 0},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, 0},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, 0},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, 0}
+        };
+
+        boolean provisionedFirst;
+        boolean provisionedSecond;
+        int capability = CAPABILITY_TYPE_PRESENCE_UCE;
+
+        // get provisioning status
+        provisionedFirst = mTestImsProvisioningController
+                .getRcsProvisioningStatusForCapability(mSubId0, capability, REGISTRATION_TECH_LTE);
+
+        // verify return value default false - not provisioned
+        assertEquals(false, provisionedFirst);
+
+        mTestImsProvisioningController.setRcsProvisioningStatusForCapability(
+                mSubId0, capability, REGISTRATION_TECH_LTE, !provisionedFirst);
+        processAllMessages();
+
+        provisionedSecond = mTestImsProvisioningController
+                .getRcsProvisioningStatusForCapability(mSubId0, capability, REGISTRATION_TECH_LTE);
+
+        // verify return value default false - provisioned
+        assertEquals(!provisionedFirst, provisionedSecond);
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(2)).getProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capability), eq(REGISTRATION_TECH_LTE));
+        // verify setProvisioningStatus is called RADIO_TECHS.length times for all tech or not
+        verify(mImsProvisioningLoader, times(1)).setProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capability), anyInt(), eq(provisionedSecond));
+
+        // verify weather Callback is called RADIO_TECHS.length times for all tech or not
+        verify(mIFeatureProvisioningCallback0, times(1))
+                .onRcsFeatureProvisioningChanged(eq(capability), eq(REGISTRATION_TECH_LTE),
+                        eq(provisionedSecond));
+
+        // verify weather ImsConfig is called or not
+        verify(mImsConfig, times(1)).setConfig(
+                eq(KEY_EAB_PROVISIONING_STATUS), eq(PROVISIONING_VALUE_ENABLED));
+
+        // verify reset
+        clearInvocations(mImsProvisioningLoader);
+
+        // only CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE - provisioned
+        boolean[] expected = {true, false, false, false};
+        for (int i = 0; i < RADIO_TECHS.length; i++) {
+            provisionedSecond = mTestImsProvisioningController
+                    .getRcsProvisioningStatusForCapability(mSubId0, capability, RADIO_TECHS[i]);
+
+            // verify return value
+            assertEquals(expected[i], provisionedSecond);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(
+                    eq(mSubId0), eq(FEATURE_RCS), eq(capability), eq(RADIO_TECHS[i]));
+        }
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void setProvisioningValue_withMmTelKey() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        processAllMessages();
+
+        // add callback with valid obj
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // voice, all tech
+        // video, all tech
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1", "1,2", "1,3",
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_CROSS_SIM, -1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_NR, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_CROSS_SIM, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_NR, -1}
+        };
+
+        // MmTel valid
+        int[] keys = {
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS,
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS,
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE
+        };
+        int[] capas = {
+                MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                MmTelCapabilities.CAPABILITY_TYPE_VOICE
+        };
+        int[] techs = {
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+        };
+
+        int result;
+        for (int i = 0; i < keys.length; i++) {
+            clearInvocations(mIFeatureProvisioningCallback0);
+            result = mTestImsProvisioningController.setProvisioningValue(
+                    mSubId0, keys[i], PROVISIONING_VALUE_ENABLED);
+            processAllMessages();
+
+            // check return value
+            assertEquals(ImsConfig.OperationStatusConstants.SUCCESS, result);
+
+            // check weather to save
+            verify(mImsProvisioningLoader, times(1)).setProvisioningStatus(
+                    eq(mSubId0), eq(FEATURE_MMTEL), eq(capas[i]), eq(techs[i]), eq(true));
+
+            verify(mIFeatureProvisioningCallback0, times(1))
+                    .onFeatureProvisioningChanged(eq(capas[i]), eq(techs[i]), eq(true));
+
+            verify(mImsConfig, times(1)).setConfig(eq(keys[i]), eq(PROVISIONING_VALUE_ENABLED));
+        }
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void setProvisioningValue_withRcsKey() throws Exception {
+        createImsProvisioningController();
+
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // add callback with valid obj
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // presence, all tech
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mRcsProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, -1}
+        };
+
+        int key = KEY_EAB_PROVISIONING_STATUS;
+        int capa = CAPABILITY_TYPE_PRESENCE_UCE;
+
+        int result = mTestImsProvisioningController.setProvisioningValue(
+                    mSubId0, key, PROVISIONING_VALUE_ENABLED);
+        processAllMessages();
+
+        // check return value
+        assertEquals(ImsConfig.OperationStatusConstants.SUCCESS, result);
+
+        // check weather to save, for all techs 4 times
+        verify(mImsProvisioningLoader, times(RADIO_TECHS.length)).setProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capa), anyInt(), eq(true));
+
+        verify(mIFeatureProvisioningCallback0, times(RADIO_TECHS.length))
+                .onRcsFeatureProvisioningChanged(eq(capa), anyInt(), eq(true));
+
+        verify(mImsConfig, times(1)).setConfig(eq(key), eq(PROVISIONING_VALUE_ENABLED));
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void setProvisioningValue_withInvalidKey() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // add callback with valid obj
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // invalid key
+        int[] keys = {
+                ProvisioningManager.KEY_SIP_SESSION_TIMER_SEC,
+                ProvisioningManager.KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC,
+                ProvisioningManager.KEY_TF_TIMER_VALUE_MS
+        };
+        for (int key : keys) {
+            int result = mTestImsProvisioningController.setProvisioningValue(
+                    mSubId0, key, PROVISIONING_VALUE_ENABLED);
+            processAllMessages();
+
+            // check return value
+            assertEquals(ImsConfigImplBase.CONFIG_RESULT_UNKNOWN, result);
+        }
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void getProvisioningValue_withValidKey() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // add callback with valid obj
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // voice, LTE, IWLAN
+        // video, LTE
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1",
+                "2,0"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, 1}
+        };
+
+        // provisioning required capability
+        // presence, all tech
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mRcsProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, 1}
+        };
+
+        // MmTel keys
+        int[] keys = {
+                KEY_VOLTE_PROVISIONING_STATUS,
+                KEY_VT_PROVISIONING_STATUS,
+                KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE,
+        };
+        int[] capas = {
+                CAPABILITY_TYPE_VOICE,
+                CAPABILITY_TYPE_VIDEO,
+                CAPABILITY_TYPE_VOICE
+        };
+        int[] techs = {
+                REGISTRATION_TECH_LTE,
+                REGISTRATION_TECH_LTE,
+                REGISTRATION_TECH_IWLAN
+        };
+        for (int i = 0; i < keys.length; i++) {
+            int result = mTestImsProvisioningController.getProvisioningValue(mSubId0, keys[i]);
+            processAllMessages();
+
+            // check return value
+            assertEquals(PROVISIONING_VALUE_ENABLED, result);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(eq(mSubId0),
+                    eq(FEATURE_MMTEL), eq(capas[i]), eq(techs[i]));
+        }
+        clearInvocations(mImsProvisioningLoader);
+
+        // Rcs keys
+        int key = KEY_EAB_PROVISIONING_STATUS;
+        int capa = CAPABILITY_TYPE_PRESENCE_UCE;
+
+        int result = mTestImsProvisioningController.getProvisioningValue(mSubId0, key);
+        processAllMessages();
+
+        // check return value
+        assertEquals(PROVISIONING_VALUE_ENABLED, result);
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capa), anyInt());
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    @Test
+    @SmallTest
+    public void getProvisioningValue_withNotSet() throws Exception {
+        createImsProvisioningController();
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+        processAllMessages();
+
+        // add callback with valid obj
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId0, mIFeatureProvisioningCallback0);
+        mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                mSubId1, mIFeatureProvisioningCallback1);
+
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mIFeatureProvisioningCallback1);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // provisioning required capability
+        // voice, LTE, IWLAN
+        // video, LTE
+        mMmTelStringArray = new String[] {
+                "1,0", "1,1",
+                "2,0"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mMmTelProvisioningStorage = new int[][] {
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, -1}
+        };
+
+        // provisioning required capability
+        // presence, all tech
+        mRcsStringArray = new String[] {
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        // provisioning Status, all of provisioning status is not set
+        mRcsProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, -1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, -1}
+        };
+        // provisioning status in ImsService
+        mImsConfigStorage = new int[][] {
+                {KEY_VOLTE_PROVISIONING_STATUS, 1},
+                {KEY_VT_PROVISIONING_STATUS, 1},
+                {KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, 1},
+                {KEY_EAB_PROVISIONING_STATUS, 1}
+        };
+
+        // MmTel keys
+        int[] keys = {
+                KEY_VOLTE_PROVISIONING_STATUS,
+                KEY_VT_PROVISIONING_STATUS,
+                KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE,
+        };
+        int[] capas = {
+                CAPABILITY_TYPE_VOICE,
+                CAPABILITY_TYPE_VIDEO,
+                CAPABILITY_TYPE_VOICE
+        };
+        int[] techs = {
+                REGISTRATION_TECH_LTE,
+                REGISTRATION_TECH_LTE,
+                REGISTRATION_TECH_IWLAN
+        };
+        for (int i = 0; i < keys.length; i++) {
+            int result = mTestImsProvisioningController.getProvisioningValue(mSubId0, keys[i]);
+            processAllMessages();
+
+            // check return value
+            assertEquals(PROVISIONING_VALUE_ENABLED, result);
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(eq(mSubId0),
+                    eq(FEATURE_MMTEL), eq(capas[i]), eq(techs[i]));
+
+            // verify weather ImsConfig is called or not
+            verify(mImsConfig, times(1)).getConfigInt(eq(keys[i]));
+
+            // verify weather ImsProvisioningLoader is called or not
+            verify(mImsProvisioningLoader, times(1)).setProvisioningStatus(eq(mSubId0),
+                    eq(FEATURE_MMTEL), eq(capas[i]), eq(techs[i]), eq(true));
+
+            // verify weather callback is called or not
+            verify(mIFeatureProvisioningCallback0, times(1)).onFeatureProvisioningChanged(
+                    eq(capas[i]), eq(techs[i]), eq(true));
+        }
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // Rcs keys
+        int key = KEY_EAB_PROVISIONING_STATUS;
+        int capa = CAPABILITY_TYPE_PRESENCE_UCE;
+
+        int result = mTestImsProvisioningController.getProvisioningValue(mSubId0, key);
+        processAllMessages();
+
+        // check return value
+        assertEquals(PROVISIONING_VALUE_ENABLED, result);
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(1)).getProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capa), anyInt());
+
+        // verify weather ImsConfig is called or not
+        verify(mImsConfig, times(1)).getConfigInt(eq(key));
+
+        // verify weather ImsProvisioningLoader is called or not
+        verify(mImsProvisioningLoader, times(RADIO_TECHS.length)).setProvisioningStatus(
+                eq(mSubId0), eq(FEATURE_RCS), eq(capa), anyInt(), eq(true));
+
+        // verify weather callback is called or not
+        verify(mIFeatureProvisioningCallback0, times(RADIO_TECHS.length))
+                .onRcsFeatureProvisioningChanged(eq(capa), anyInt(), eq(true));
+
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback1);
+        verifyNoMoreInteractions(mImsConfig);
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+    }
+
+    private void createImsProvisioningController() throws Exception {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        when(mMmTelFeatureConnector
+                .create(any(), eq(0), any(), mMmTelConnectorListener0.capture(), any()))
+                .thenReturn(mMmTelFeatureConnector0);
+        when(mMmTelFeatureConnector
+                .create(any(), eq(1), any(), mMmTelConnectorListener1.capture(), any()))
+                .thenReturn(mMmTelFeatureConnector1);
+
+        when(mRcsFeatureConnector
+                .create(any(), eq(0), mRcsConnectorListener0.capture(), any(), any()))
+                .thenReturn(mRcsFeatureConnector0);
+        when(mRcsFeatureConnector
+                .create(any(), eq(1), mRcsConnectorListener1.capture(), any(), any()))
+                .thenReturn(mRcsFeatureConnector1);
+
+        when(mImsConfig.getConfigInt(anyInt()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[0]);
+                    return getImsConfigValue(i);
+                });
+        when(mImsConfig.setConfig(anyInt(), anyInt()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[0]);
+                    int j = (Integer) (invocation.getArguments()[1]);
+                    return setImsConfigValue(i, j);
+                });
+
+        when(mImsProvisioningLoader.getProvisioningStatus(anyInt(), eq(FEATURE_MMTEL), anyInt(),
+                anyInt()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[2]);
+                    int j = (Integer) (invocation.getArguments()[3]);
+                    return getProvisionedValue(i, j);
+                });
+        when(mImsProvisioningLoader.getProvisioningStatus(anyInt(), eq(FEATURE_RCS), anyInt(),
+                anyInt()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[2]);
+                    int j = (Integer) (invocation.getArguments()[3]);
+                    return getRcsProvisionedValue(i, j);
+                });
+        when(mImsProvisioningLoader
+                .setProvisioningStatus(anyInt(), eq(FEATURE_MMTEL), anyInt(), anyInt(),
+                        anyBoolean()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[2]);
+                    int j = (Integer) (invocation.getArguments()[3]);
+                    int k = (Boolean) (invocation.getArguments()[4]) ? 1 : 0;
+                    return setProvisionedValue(i, j, k);
+                });
+        when(mImsProvisioningLoader
+                .setProvisioningStatus(anyInt(), eq(FEATURE_RCS), anyInt(), anyInt(),
+                        anyBoolean()))
+                .thenAnswer(invocation -> {
+                    int i = (Integer) (invocation.getArguments()[2]);
+                    int j = (Integer) (invocation.getArguments()[3]);
+                    int k = (Boolean) (invocation.getArguments()[4]) ? 1 : 0;
+                    return setRcsProvisionedValue(i, j, k);
+                });
+
+        when(mIFeatureProvisioningCallback0.asBinder()).thenReturn(mIbinder0);
+        when(mIFeatureProvisioningCallback1.asBinder()).thenReturn(mIbinder1);
+
+        doNothing().when(mIFeatureProvisioningCallback0)
+                .onFeatureProvisioningChanged(anyInt(), anyInt(), anyBoolean());
+        doNothing().when(mIFeatureProvisioningCallback0)
+                .onRcsFeatureProvisioningChanged(anyInt(), anyInt(), anyBoolean());
+        doNothing().when(mIFeatureProvisioningCallback1)
+                .onFeatureProvisioningChanged(anyInt(), anyInt(), anyBoolean());
+        doNothing().when(mIFeatureProvisioningCallback1)
+                .onRcsFeatureProvisioningChanged(anyInt(), anyInt(), anyBoolean());
+
+        mTestImsProvisioningController = new TestImsProvisioningController();
+
+        mHandler = mTestImsProvisioningController.getHandler();
+        try {
+            mLooper = new TestableLooper(mHandler.getLooper());
+        } catch (Exception e) {
+            logd("create looper from handler failed");
+        }
+
+        verify(mRcsFeatureConnector0, atLeastOnce()).connect();
+        verify(mMmTelFeatureConnector0, atLeastOnce()).connect();
+
+        verify(mRcsFeatureConnector1, atLeastOnce()).connect();
+        verify(mMmTelFeatureConnector1, atLeastOnce()).connect();
+    }
+
+    private void initializeDefaultData() throws Exception {
+        mMmTelStringArray = new String[]{
+                "1,0", "1,1", "1,2", "1,3",
+                "2,0", "2,1", "2,2", "2,3",
+                "4,0", "4,1", "4,2", "4,3",
+                "8,0", "8,1", "8,2", "8,3",
+                "16,0", "16,1", "16,2", "16,3"
+        };
+        mMmTelProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_UT, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_SMS, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_SMS, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_SMS, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_SMS, REGISTRATION_TECH_NR, 1},
+                {CAPABILITY_TYPE_CALL_COMPOSER, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_CALL_COMPOSER, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_CALL_COMPOSER, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_CALL_COMPOSER, REGISTRATION_TECH_NR, 1}
+        };
+        mRcsStringArray = new String[]{
+                "1,0", "1,1", "1,2", "1,3",
+                "2,0", "2,1", "2,2", "2,3"
+        };
+        mRcsProvisioningStorage = new int[][]{
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_IWLAN, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_CROSS_SIM, 1},
+                {CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_NR, 1}
+        };
+
+        mImsConfigStorage = new int[][] {
+                {KEY_VOLTE_PROVISIONING_STATUS, 1},
+                {KEY_VT_PROVISIONING_STATUS, 1},
+                {KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, 1},
+                {KEY_EAB_PROVISIONING_STATUS, 1}
+        };
+    }
+
+    private int getProvisionedValue(int i, int j) {
+        for (int[] data : mMmTelProvisioningStorage) {
+            if (data[0] == i && data[1] == j) {
+                return data[2];
+            }
+        }
+        return 0;
+    }
+
+    private int getRcsProvisionedValue(int i, int j) {
+        for (int[] data : mRcsProvisioningStorage) {
+            if (data[0] == i && data[1] == j) {
+                return data[2];
+            }
+        }
+        return 0;
+    }
+
+    private boolean setProvisionedValue(int i, int j, int k) {
+        boolean retVal = false;
+        for (int[] data : mMmTelProvisioningStorage) {
+            if (data[0] == i && data[1] == j) {
+                if (data[2] != k) {
+                    data[2] = k;
+                    return true;
+                }
+                return false;
+            }
+        }
+        return retVal;
+    }
+
+    private boolean setRcsProvisionedValue(int i, int j, int k) {
+        boolean retVal = false;
+        for (int[] data : mRcsProvisioningStorage) {
+            if (data[0] == i && data[1] == j) {
+                if (data[2] != k) {
+                    data[2] = k;
+                    return true;
+                }
+                return false;
+            }
+        }
+        return retVal;
+    }
+
+    private int getImsConfigValue(int i) {
+        for (int[] data : mImsConfigStorage) {
+            if (data[0] == i) {
+                return data[1];
+            }
+        }
+        return -1;
+    }
+
+    private int setImsConfigValue(int i, int j) {
+        for (int[] data : mImsConfigStorage) {
+            if (data[0] == i) {
+                data[1] = j;
+                return ImsConfig.OperationStatusConstants.SUCCESS;
+            }
+        }
+        return ImsConfig.OperationStatusConstants.SUCCESS;
+    }
+
+    private void processAllMessages() {
+        while (!mLooper.getLooper().getQueue().isIdle()) {
+            mLooper.processAllMessages();
+        }
+    }
+
+    private static void logd(String str) {
+        Log.d(TAG, str);
+    }
+}