Improvement provisioning in AOSP

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

Bug: http://b/202199221
Test: atest
Change-Id: Ifd7030d3c04a036acb82a4eced19b0584ae5afc8
Merged-In: 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);
+    }
+}