Merge "To support Carrier config bundle for MmTel and Rcs"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e444ac2..4f74b5c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -203,6 +203,7 @@
     <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     <!-- Needed to block messages. -->
     <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
     <!-- Needed for emergency contact notification. -->
diff --git a/src/com/android/phone/CallWaitingSwitchPreference.java b/src/com/android/phone/CallWaitingSwitchPreference.java
index 01dd3b2..00407f3 100644
--- a/src/com/android/phone/CallWaitingSwitchPreference.java
+++ b/src/com/android/phone/CallWaitingSwitchPreference.java
@@ -1,33 +1,40 @@
 package com.android.phone;
 
 import static com.android.phone.TimeConsumingPreferenceActivity.EXCEPTION_ERROR;
+import static com.android.phone.TimeConsumingPreferenceActivity.FDN_CHECK_FAILURE;
 import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
 
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.preference.SwitchPreference;
+import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
 
-import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 public class CallWaitingSwitchPreference extends SwitchPreference {
     private static final String LOG_TAG = "CallWaitingSwitchPreference";
+    private static final int DELAY_MILLIS_FOR_USSD = 1000;
     private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
 
     private final MyHandler mHandler = new MyHandler();
     private Phone mPhone;
     private TimeConsumingPreferenceListener mTcpListener;
-    private Executor mExecutor;
+    private ScheduledExecutorService mExecutor;
     private TelephonyManager mTelephonyManager;
     private boolean mIsDuringUpdateProcess = false;
     private int mUpdateStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
     private int mQueryStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+    private boolean mUssdMode = false;
 
     public CallWaitingSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -45,9 +52,14 @@
             TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone) {
         mPhone = phone;
         mTcpListener = listener;
-        mExecutor = Executors.newSingleThreadExecutor();
+        mExecutor = Executors.newSingleThreadScheduledExecutor();
         mTelephonyManager = getContext().getSystemService(
                 TelephonyManager.class).createForSubscriptionId(phone.getSubId());
+        CarrierConfigManager configManager = getContext().getSystemService(
+                CarrierConfigManager.class);
+        PersistableBundle bundle = configManager.getConfigForSubId(phone.getSubId());
+        mUssdMode = (bundle != null) ? bundle.getBoolean(
+                CarrierConfigManager.KEY_USE_CALL_WAITING_USSD_BOOL, false) : false;
 
         if (!skipReading) {
             Log.d(LOG_TAG, "init getCallWaitingStatus");
@@ -67,7 +79,23 @@
     private void updateStatusCallBack(int result) {
         Log.d(LOG_TAG, "updateStatusCallBack: CW state " + result + ", and re get");
         mUpdateStatus = result;
-        mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
+        if (mUssdMode) {
+            Log.d(LOG_TAG, "updateStatusCallBack: USSD mode needs to wait 1s since Framework"
+                    + " has the limitation");
+            Consumer<Integer> resultListener = this::queryStatusCallBack;
+            try {
+                mExecutor.schedule(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTelephonyManager.getCallWaitingStatus(mExecutor, resultListener);
+                    }
+                }, DELAY_MILLIS_FOR_USSD, TimeUnit.MILLISECONDS);
+            } catch (Exception e) {
+                Log.d(LOG_TAG, "Exception while waiting: " + e);
+            }
+        } else {
+            mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
+        }
     }
 
     @Override
@@ -101,17 +129,27 @@
                 }
             }
 
-            if (mIsDuringUpdateProcess && (
-                    mUpdateStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
-                            || mUpdateStatus
-                            == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR)) {
-                Log.d(LOG_TAG, "handleSetCallWaitingResponse: Exception");
-                if (mTcpListener != null) {
-                    mTcpListener.onError(CallWaitingSwitchPreference.this, EXCEPTION_ERROR);
+            if (mQueryStatus != TelephonyManager.CALL_WAITING_STATUS_ENABLED
+                    && mQueryStatus != TelephonyManager.CALL_WAITING_STATUS_DISABLED
+                    && mQueryStatus != TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR) {
+                Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception:" + mQueryStatus);
+                int error = EXCEPTION_ERROR;
+                switch (mQueryStatus) {
+                    case TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE:
+                        error = FDN_CHECK_FAILURE;
+                        break;
+                    default:
+                        error = EXCEPTION_ERROR;
+                        break;
                 }
-            } else if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
-                    || mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR) {
-                Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception");
+                if (mTcpListener != null) {
+                    mTcpListener.onError(CallWaitingSwitchPreference.this, error);
+                }
+            } else if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR
+                    || (mIsDuringUpdateProcess && (
+                    mUpdateStatus != TelephonyManager.CALL_WAITING_STATUS_ENABLED
+                            && mUpdateStatus != TelephonyManager.CALL_WAITING_STATUS_DISABLED))) {
+                Log.d(LOG_TAG, "handleSetCallWaitingResponse: Exception");
                 if (mTcpListener != null) {
                     mTcpListener.onError(CallWaitingSwitchPreference.this, RESPONSE_ERROR);
                 }
diff --git a/src/com/android/phone/ImsProvisioningLoader.java b/src/com/android/phone/ImsProvisioningLoader.java
new file mode 100644
index 0000000..a9aac26
--- /dev/null
+++ b/src/com/android/phone/ImsProvisioningLoader.java
@@ -0,0 +1,304 @@
+/*
+ * 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 android.content.Context;
+import android.content.SharedPreferences;
+import android.os.PersistableBundle;
+import android.preference.PreferenceManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Provides a function to set/get Ims feature provisioning status in storage.
+ */
+public class ImsProvisioningLoader {
+    private static final String LOG_TAG = ImsProvisioningLoader.class.getSimpleName();
+
+    public static final int STATUS_NOT_SET = -1;
+    public static final int STATUS_NOT_PROVISIONED =
+            ProvisioningManager.PROVISIONING_VALUE_DISABLED;
+    public static final int STATUS_PROVISIONED =
+            ProvisioningManager.PROVISIONING_VALUE_ENABLED;
+
+    public static final int IMS_FEATURE_MMTEL = ImsFeature.FEATURE_MMTEL;
+    public static final int IMS_FEATURE_RCS = ImsFeature.FEATURE_RCS;
+
+    private static final String PROVISIONING_FILE_NAME_PREF = "imsprovisioningstatus_";
+    private static final String PREF_PROVISION_IMS_MMTEL_PREFIX = "provision_ims_mmtel_";
+
+    private Context mContext;
+    private SharedPreferences mTelephonySharedPreferences;
+    // key : sub Id, value : read from sub Id's xml and it's in-memory cache
+    private SparseArray<PersistableBundle> mSubIdBundleArray = new SparseArray<>();
+    private final Object mLock = new Object();
+
+    public ImsProvisioningLoader(Context context) {
+        mContext = context;
+        mTelephonySharedPreferences =
+                PreferenceManager.getDefaultSharedPreferences(context);
+    }
+
+    /**
+     * Get Ims feature provisioned status in storage
+     */
+    public int getProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature,
+            int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+        initCache(subId);
+        return getImsProvisioningStatus(subId, imsFeature, tech,
+                capability);
+    }
+
+    /**
+     * Set Ims feature provisioned status in storage
+     */
+    public boolean setProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature,
+            int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+            boolean isProvisioned) {
+        initCache(subId);
+        return setImsFeatureProvisioning(subId, imsFeature, tech, capability,
+                isProvisioned);
+    }
+
+    private boolean isFileExist(int subId) {
+        File file = new File(mContext.getFilesDir(), getFileName(subId));
+        return file.exists();
+    }
+
+    private void initCache(int subId) {
+        synchronized (mLock) {
+            PersistableBundle subIdBundle = mSubIdBundleArray.get(subId, null);
+            if (subIdBundle != null) {
+                // initCache() has already been called for the subId
+                return;
+            }
+            if (isFileExist(subId)) {
+                subIdBundle = readSubIdBundleFromXml(subId);
+            } else {
+                // It should read the MMTEL capability cache as part of shared prefs and migrate
+                // over any configs for UT.
+                final int[] regTech = {ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                        ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                        ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                        ImsRegistrationImplBase.REGISTRATION_TECH_NR};
+                subIdBundle = new PersistableBundle();
+                for (int tech : regTech) {
+                    int UtProvisioningStatus = getUTProvisioningStatus(subId, tech);
+                    if (STATUS_PROVISIONED == UtProvisioningStatus) {
+                        setProvisioningStatusToSubIdBundle(ImsFeature.FEATURE_MMTEL, tech,
+                                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT, subIdBundle,
+                                UtProvisioningStatus);
+                    }
+                }
+                saveSubIdBundleToXml(subId, subIdBundle);
+            }
+            mSubIdBundleArray.put(subId, subIdBundle);
+        }
+    }
+
+    private int getImsProvisioningStatus(int subId, int imsFeature, int tech, int capability) {
+        PersistableBundle subIdBundle = null;
+        synchronized (mLock) {
+            subIdBundle = mSubIdBundleArray.get(subId, null);
+        }
+
+        return getProvisioningStatusFromSubIdBundle(imsFeature, tech,
+                capability, subIdBundle);
+    }
+
+    private boolean setImsFeatureProvisioning(int subId, int imsFeature, int tech, int capability,
+            boolean isProvisioned) {
+        synchronized (mLock) {
+            int preValue = getImsProvisioningStatus(subId, imsFeature, tech, capability);
+            int newValue = isProvisioned ? STATUS_PROVISIONED : STATUS_NOT_PROVISIONED;
+            if (preValue == newValue) {
+                logd("already stored provisioning status " + isProvisioned + " ImsFeature "
+                        + imsFeature + " tech " + tech + " capa " + capability);
+                return false;
+            }
+
+            PersistableBundle subIdBundle = mSubIdBundleArray.get(subId, null);
+            setProvisioningStatusToSubIdBundle(imsFeature, tech, capability, subIdBundle,
+                    newValue);
+            saveSubIdBundleToXml(subId, subIdBundle);
+        }
+        return true;
+    }
+
+    private int getProvisioningStatusFromSubIdBundle(int imsFeature, int tech,
+            int capability, PersistableBundle subIdBundle) {
+        // If it doesn't exist in xml, return STATUS_NOT_SET
+        if (subIdBundle == null || subIdBundle.isEmpty()) {
+            logd("xml is empty");
+            return STATUS_NOT_SET;
+        }
+
+        PersistableBundle regTechBundle = subIdBundle.getPersistableBundle(
+                String.valueOf(imsFeature));
+        if (regTechBundle == null) {
+            logd("ImsFeature " + imsFeature + " is not exist in xml");
+            return STATUS_NOT_SET;
+        }
+
+        PersistableBundle capabilityBundle = regTechBundle.getPersistableBundle(
+                String.valueOf(tech));
+        if (capabilityBundle == null) {
+            logd("RegistrationTech " + tech + " is not exist in xml");
+            return STATUS_NOT_SET;
+        }
+
+        return getIntValueFromBundle(String.valueOf(capability), capabilityBundle);
+    }
+
+    private void setProvisioningStatusToSubIdBundle(int imsFeature, int tech,
+            int capability, PersistableBundle subIdBundle, int newStatus) {
+        PersistableBundle regTechBundle = subIdBundle.getPersistableBundle(
+                String.valueOf(imsFeature));
+        if (regTechBundle == null) {
+            regTechBundle = new PersistableBundle();
+            subIdBundle.putPersistableBundle(String.valueOf(imsFeature), regTechBundle);
+        }
+
+        PersistableBundle capabilityBundle = regTechBundle.getPersistableBundle(
+                String.valueOf(tech));
+        if (capabilityBundle == null) {
+            capabilityBundle = new PersistableBundle();
+            regTechBundle.putPersistableBundle(String.valueOf(tech), capabilityBundle);
+        }
+
+        capabilityBundle.putInt(String.valueOf(capability), newStatus);
+    }
+
+    // Default value is STATUS_NOT_SET
+    private int getIntValueFromBundle(String key, PersistableBundle bundle) {
+        return bundle.getInt(key, STATUS_NOT_SET);
+    }
+
+    // Return subIdBundle from imsprovisioningstatus_{subId}.xml
+    private PersistableBundle readSubIdBundleFromXml(int subId) {
+        String fileName = getFileName(subId);
+
+        PersistableBundle subIdBundles = new PersistableBundle();
+        File file = null;
+        FileInputStream inFile = null;
+        synchronized (mLock) {
+            try {
+                file = new File(mContext.getFilesDir(), fileName);
+                inFile = new FileInputStream(file);
+                subIdBundles = PersistableBundle.readFromStream(inFile);
+                inFile.close();
+            } catch (FileNotFoundException e) {
+                logd(e.toString());
+            } catch (IOException e) {
+                loge(e.toString());
+            } catch (RuntimeException e) {
+                loge(e.toString());
+            }
+        }
+
+        return subIdBundles;
+    }
+
+    private void saveSubIdBundleToXml(int subId, PersistableBundle subIdBundle) {
+        String fileName = getFileName(subId);
+
+        if (subIdBundle == null || subIdBundle.isEmpty()) {
+            logd("subIdBundle is empty");
+            return;
+        }
+
+        FileOutputStream outFile = null;
+        synchronized (mLock) {
+            try {
+                outFile = new FileOutputStream(new File(mContext.getFilesDir(), fileName));
+                subIdBundle.writeToStream(outFile);
+                outFile.flush();
+                outFile.close();
+            } catch (IOException e) {
+                loge(e.toString());
+            } catch (RuntimeException e) {
+                loge(e.toString());
+            }
+        }
+    }
+
+    private int getUTProvisioningStatus(int subId, int tech) {
+        return getMmTelCapabilityProvisioningBitfield(subId, tech) > 0 ? STATUS_PROVISIONED
+                : STATUS_NOT_SET;
+    }
+
+    /**
+     * @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*/);
+    }
+
+    private String getMmTelProvisioningKey(int subId, int tech) {
+        // Resulting key is provision_ims_mmtel_{subId}_{tech}
+        return PREF_PROVISION_IMS_MMTEL_PREFIX + subId + "_" + tech;
+    }
+
+    private String getFileName(int subId) {
+        // Resulting name is imsprovisioningstatus_{subId}.xml
+        return PROVISIONING_FILE_NAME_PREF + subId + ".xml";
+    }
+
+    @VisibleForTesting
+    void clear() {
+        synchronized (mLock) {
+            mSubIdBundleArray.clear();
+        }
+    }
+
+    @VisibleForTesting
+    void setProvisioningToXml(int subId, PersistableBundle subIdBundle,
+            String[] infoArray) {
+        for (String info : infoArray) {
+            String[] paramArray = info.split(",");
+            setProvisioningStatusToSubIdBundle(Integer.valueOf(paramArray[0]),
+                    Integer.valueOf(paramArray[1]), Integer.valueOf(paramArray[2]),
+                    subIdBundle, Integer.valueOf(paramArray[3]));
+        }
+        saveSubIdBundleToXml(subId, subIdBundle);
+    }
+
+    private void loge(String contents) {
+        Log.e(LOG_TAG, contents);
+    }
+
+    private void logd(String contents) {
+        Log.d(LOG_TAG, contents);
+    }
+
+}
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index 4e2407c..a7caab0 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -293,8 +293,9 @@
 
         @Override
         public void connectionReady(ImsManager manager, int subId) {
-            logd(mLogPrefix + "connectionReady");
+            logd(mLogPrefix + "connectionReady " + subId);
 
+            mSubId = subId;
             mState = STATE_READY;
             mReason = AVAILABLE;
             mHasConfig = true;
@@ -435,8 +436,9 @@
 
         @Override
         public void connectionReady(RcsFeatureManager manager, int subId) {
-            logd(mLogPrefix + "connectionReady");
+            logd(mLogPrefix + "connectionReady " + subId);
 
+            mSubId = subId;
             mState = STATE_READY;
             mReason = AVAILABLE;
             mHasConfig = true;
@@ -834,19 +836,21 @@
 
         if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
             for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
-                MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
-                if (l.mSubId == wrapper.mSubId
-                        && !l.notifyState(wrapper)) {
-                    mWrappers.remove(wrapper.mBinder);
+                if (wrapper.mSubId == getSubId(i)) {
+                    MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
+                    if (!l.notifyState(wrapper)) {
+                        mWrappers.remove(wrapper.mBinder);
+                    }
                     break;
                 }
             }
         } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
             for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
-                RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
-                if (l.mSubId == wrapper.mSubId
-                        && !l.notifyState(wrapper)) {
-                    mWrappers.remove(wrapper.mBinder);
+                if (wrapper.mSubId == getSubId(i)) {
+                    RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
+                    if (!l.notifyState(wrapper)) {
+                        mWrappers.remove(wrapper.mBinder);
+                    }
                     break;
                 }
             }
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index bcbbeac..56029d5 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -150,6 +150,7 @@
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CarrierPrivilegesTracker;
 import com.android.internal.telephony.CarrierResolver;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.CommandException;
@@ -1270,7 +1271,7 @@
                     ar = (AsyncResult) msg.obj;
                     request = (MainThreadRequest) ar.userObj;
                     Consumer<Integer> callback = (Consumer<Integer>) request.argument;
-                    int callForwardingStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+                    int callWaitingStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
                     if (ar.exception == null && ar.result != null) {
                         int[] callForwardResults = (int[]) ar.result;
                         // Service Class is a bit mask per 3gpp 27.007.
@@ -1278,11 +1279,11 @@
                         if (callForwardResults.length > 1
                                 && ((callForwardResults[1]
                                 & CommandsInterface.SERVICE_CLASS_VOICE) > 0)) {
-                            callForwardingStatus = callForwardResults[0] == 0
+                            callWaitingStatus = callForwardResults[0] == 0
                                     ? TelephonyManager.CALL_WAITING_STATUS_DISABLED
                                     : TelephonyManager.CALL_WAITING_STATUS_ENABLED;
                         } else {
-                            callForwardingStatus = TelephonyManager.CALL_WAITING_STATUS_DISABLED;
+                            callWaitingStatus = TelephonyManager.CALL_WAITING_STATUS_DISABLED;
                         }
                     } else {
                         if (ar.result == null) {
@@ -1295,12 +1296,15 @@
                             CommandException.Error error =
                                     ((CommandException) (ar.exception)).getCommandError();
                             if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
-                                callForwardingStatus =
+                                callWaitingStatus =
                                         TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED;
+                            } else if (error == CommandException.Error.FDN_CHECK_FAILURE) {
+                                callWaitingStatus =
+                                        TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE;
                             }
                         }
                     }
-                    callback.accept(callForwardingStatus);
+                    callback.accept(callWaitingStatus);
                     break;
                 }
 
@@ -1325,6 +1329,9 @@
                                     ((CommandException) (ar.exception)).getCommandError();
                             if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                                 callback.accept(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+                            } else if (error == CommandException.Error.FDN_CHECK_FAILURE) {
+                                callback.accept(
+                                        TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE);
                             } else {
                                 callback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
                             }
@@ -8901,8 +8908,8 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             UiccSlot[] slots = UiccController.getInstance().getUiccSlots();
-            if (slots == null) {
-                Rlog.i(LOG_TAG, "slots is null.");
+            if (slots == null || slots.length == 0) {
+                Rlog.i(LOG_TAG, "slots is null or empty.");
                 return null;
             }
             UiccSlotInfo[] infos = new UiccSlotInfo[slots.length];
@@ -8956,13 +8963,8 @@
                 for (int portIdx : portIndexes) {
                     String iccId = IccUtils.stripTrailingFs(getIccId(slot, portIdx,
                             callingPackage, hasReadPermission));
-                    if (slot.isPortActive(portIdx)) {
-                        UiccPort port = slot.getUiccCard().getUiccPort(portIdx);
-                        portInfos.add(new UiccPortInfo(iccId, port.getPortIdx(),
-                                port.getPhoneId(), true));
-                    } else {
-                        portInfos.add(new UiccPortInfo(iccId, portIdx, -1, false));
-                    }
+                    portInfos.add(new UiccPortInfo(iccId, portIdx,
+                            slot.getPhoneIdFromPortIndex(portIdx), slot.isPortActive(portIdx)));
                 }
                 infos[i] = new UiccSlotInfo(
                         slot.isEuicc(),
@@ -9117,6 +9119,11 @@
                 loge("setCarrierTestOverride fails with invalid subId: " + subId);
                 return;
             }
+            CarrierPrivilegesTracker cpt = phone.getCarrierPrivilegesTracker();
+            if (cpt != null) {
+                cpt.setTestOverrideCarrierPrivilegeRules(carrierPrivilegeRules);
+            }
+            // TODO(b/211796398): remove the legacy logic below once CPT migration is done.
             phone.setCarrierTestOverride(mccmnc, imsi, iccid, gid1, gid2, plmn, spn,
                     carrierPrivilegeRules, apn);
             if (carrierPrivilegeRules == null) {
diff --git a/tests/src/com/android/phone/ImsProvisioningLoaderTest.java b/tests/src/com/android/phone/ImsProvisioningLoaderTest.java
new file mode 100644
index 0000000..61cab1d
--- /dev/null
+++ b/tests/src/com/android/phone/ImsProvisioningLoaderTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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 junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.PersistableBundle;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Unit Test for ImsProvisioningLoader.
+ */
+public class ImsProvisioningLoaderTest {
+    private static final String LOG_TAG = ImsProvisioningLoaderTest.class.getSimpleName();
+
+    private static final int IMS_FEATURE_MMTEL = ImsProvisioningLoader.IMS_FEATURE_MMTEL;
+    private static final int IMS_FEATURE_RCS = ImsProvisioningLoader.IMS_FEATURE_RCS;
+
+    private static final int TECH_LTE = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+    private static final int TECH_IWLAN = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+    private static final int TECH_NEW = Integer.MAX_VALUE;
+
+    private static final int CAPA_VOICE = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
+    private static final int CAPA_VIDEO = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
+    private static final int CAPA_UT = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
+    private static final int CAPA_PRESENCE =
+            RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
+    private static final int CAPA_NEW = Integer.MAX_VALUE;
+
+    private static final int STATUS_NOT_PROVISIONED = ImsProvisioningLoader.STATUS_NOT_PROVISIONED;
+    private static final int STATUS_PROVISIONED = ImsProvisioningLoader.STATUS_PROVISIONED;
+
+    private static final int SUB_ID_1 = 111111;
+    private static final int SUB_ID_2 = 222222;
+
+    @Mock
+    Context mContext;
+    @Mock
+    SharedPreferences mSharedPreferences;
+    private ImsProvisioningLoader mImsProvisioningLoader;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+        doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
+                mContext).getFilesDir();
+
+        mImsProvisioningLoader = new ImsProvisioningLoader(mContext);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mImsProvisioningLoader != null) {
+            mImsProvisioningLoader.clear();
+        }
+        deleteXml(SUB_ID_1, mContext);
+        deleteXml(SUB_ID_2, mContext);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProvisioningStatus_ExistFeature() {
+        // Set MMTEL IWLAN VOICE to STATUS_PROVISIONED
+        String[] info =
+                new String[]{IMS_FEATURE_MMTEL + "," + TECH_IWLAN + "," + CAPA_VOICE + "," + getInt(
+                        true)};
+        mImsProvisioningLoader.setProvisioningToXml(SUB_ID_1, new PersistableBundle(), info);
+
+        int curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1, IMS_FEATURE_MMTEL,
+                CAPA_VOICE, TECH_IWLAN);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(true), curValue);
+
+        // Change MMTEL IWLAN VOICE provisioning status
+        boolean saveResult = mImsProvisioningLoader.setProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_IWLAN, false);
+        curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_IWLAN);
+        assertEquals(getXmlContents(SUB_ID_1), true, saveResult);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(false), curValue);
+
+        // If set to the same provisioning status,  don't save it.
+        saveResult = mImsProvisioningLoader.setProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_IWLAN, false);
+        curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_IWLAN);
+        assertEquals(getXmlContents(SUB_ID_1), false, saveResult);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(false), curValue);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProvisioningStatus_NewFeature() {
+        // Set new capability
+        // Return true as a result to setProvisioningStatus()
+        boolean saveResult = mImsProvisioningLoader.setProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_NEW, TECH_LTE, true);
+        int curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_NEW, TECH_LTE);
+        assertEquals(getXmlContents(SUB_ID_1), true, saveResult);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(true), curValue);
+
+        // Set new tech
+        saveResult = mImsProvisioningLoader.setProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_NEW, false);
+        curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_NEW);
+        assertEquals(getXmlContents(SUB_ID_1), true, saveResult);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(false), curValue);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetProvisioningStatus_DifferentSim() {
+        // Check whether the provisioning status does not change even if SIM is changed
+        // Sub id 2, set provisioning status
+        boolean prevValue = getBooleanFromProvisioningStatus(SUB_ID_2,
+                IMS_FEATURE_RCS, CAPA_PRESENCE, TECH_IWLAN);
+        boolean saveResult = mImsProvisioningLoader.setProvisioningStatus(
+                SUB_ID_2, IMS_FEATURE_RCS, CAPA_PRESENCE, TECH_IWLAN, !prevValue);
+        int curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_2,
+                IMS_FEATURE_RCS, CAPA_PRESENCE, TECH_IWLAN);
+        assertEquals(getXmlContents(SUB_ID_2), true, saveResult);
+        assertEquals(getXmlContents(SUB_ID_2), getInt(!prevValue), curValue);
+
+        // Sub id 1, set other provisioned status
+        mImsProvisioningLoader.setProvisioningStatus(
+                SUB_ID_1, IMS_FEATURE_RCS, CAPA_PRESENCE, TECH_IWLAN, prevValue);
+
+        // Sub id 2, check the previous provisioning status isn't changed
+        curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_2,
+                IMS_FEATURE_RCS, CAPA_PRESENCE, TECH_IWLAN);
+        assertEquals(getXmlContents(SUB_ID_2), getInt(!prevValue), curValue);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetProvisioningStatus_UtProvisioningStatusIsExistInPref() {
+        // Ut provisioning status exists in preference
+        doReturn(1).when(mSharedPreferences).getInt(anyString(), anyInt());
+        int curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_UT, TECH_LTE);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(true), curValue);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetProvisioningStatus_ExistXml() {
+        // Set MMTEL LTE VOICE to STATUS_PROVISIONED, MMTEL LTE VIDEO to STATUS_NOT_PROVISIONED
+        String[] info =
+                new String[]{IMS_FEATURE_MMTEL + "," + TECH_LTE + "," + CAPA_VOICE + "," + getInt(
+                        true),
+                        IMS_FEATURE_MMTEL + "," + TECH_LTE + "," + CAPA_VIDEO + "," + getInt(
+                                false)};
+        mImsProvisioningLoader.setProvisioningToXml(SUB_ID_1, new PersistableBundle(), info);
+
+        int curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VOICE, TECH_LTE);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(true), curValue);
+
+        curValue = mImsProvisioningLoader.getProvisioningStatus(SUB_ID_1,
+                IMS_FEATURE_MMTEL, CAPA_VIDEO, TECH_LTE);
+        assertEquals(getXmlContents(SUB_ID_1), getInt(false), curValue);
+    }
+
+    private boolean getBooleanFromProvisioningStatus(int subId, int imsFeature, int capa,
+            int tech) {
+        // Return provisioning status to bool
+        return mImsProvisioningLoader.getProvisioningStatus(
+                subId, imsFeature, capa, tech) == STATUS_PROVISIONED ? true
+                : false;
+    }
+
+    private int getInt(boolean isProvisioned) {
+        return isProvisioned ? STATUS_PROVISIONED : STATUS_NOT_PROVISIONED;
+    }
+
+    private void deleteXml(int subId, Context context) {
+        String fileName = getFileName(subId);
+        File file = null;
+        try {
+            file = new File(context.getFilesDir(), fileName);
+        } catch (Exception e) {
+            logd(e.toString());
+        }
+        file.delete();
+    }
+
+    private String getXmlContents(int subId) {
+        String fileName = getFileName(subId);
+
+        File file = null;
+        FileInputStream inFile = null;
+        StringBuilder readString = new StringBuilder();
+        readString.append("file name " + fileName + "\n");
+        byte[] buffer = new byte[1024];
+        int n = 0;
+        try {
+            file = new File(mContext.getFilesDir(), fileName);
+            inFile = new FileInputStream(file);
+            while ((n = inFile.read(buffer)) != -1) {
+                readString.append(new String(buffer, 0, n));
+            }
+            inFile.close();
+        } catch (FileNotFoundException e) {
+            logd(e.toString());
+
+        } catch (IOException e) {
+            logd(e.toString());
+        }
+        return readString.toString();
+    }
+
+    private String getFileName(int subId) {
+        // Resulting name is imsprovisioningstatus_{subId}.xml
+        return "imsprovisioningstatus_" + subId + ".xml";
+    }
+
+    private static void logd(String contents) {
+        Log.d(LOG_TAG, contents);
+    }
+
+}
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
index e4cdc6e..cbd6ceb 100644
--- a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -726,7 +726,7 @@
         verify(mCallback2, times(2)).onUnavailable(anyInt());
         verify(mCallback3, times(2)).onUnavailable(anyInt());
 
-        mMmTelConnectorListenerSlot1.getValue().connectionReady(null, SLOT_0_SUB_ID);
+        mMmTelConnectorListenerSlot1.getValue().connectionReady(null, SLOT_1_SUB_ID);
         processAllMessages();
         verify(mCallback0, times(1)).onAvailable();
         verify(mCallback1, times(1)).onAvailable();
@@ -737,7 +737,7 @@
         verify(mCallback2, times(2)).onUnavailable(anyInt());
         verify(mCallback3, times(2)).onUnavailable(anyInt());
 
-        mRcsConnectorListenerSlot1.getValue().connectionReady(null, SLOT_0_SUB_ID);
+        mRcsConnectorListenerSlot1.getValue().connectionReady(null, SLOT_1_SUB_ID);
         processAllMessages();
         verify(mCallback0, times(1)).onAvailable();
         verify(mCallback1, times(1)).onAvailable();