Merge "[RCS]Implement File Upload"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a6c1ec5..6bdf55c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -94,6 +94,11 @@
     <protected-broadcast android:name= "android.telephony.action.NETWORK_COUNTRY_CHANGED" />
     <protected-broadcast android:name= "android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED" />
     <protected-broadcast android:name= "android.telephony.action.MULTI_SIM_CONFIG_CHANGED" />
+    <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_RESET" />
+    <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE" />
+    <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
+    <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_REDIRECTED" />
+    <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED" />
 
     <!-- For Vendor Debugging in Telephony -->
     <protected-broadcast android:name="android.telephony.action.ANOMALY_REPORTED" />
@@ -575,16 +580,6 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name="com.android.services.telephony.sip.SipPhoneAccountSettingsActivity"
-                android:theme="@android:style/Theme.NoDisplay"
-                android:exported="true"
-                android:excludeFromRecents="true">
-            <intent-filter>
-                <action android:name="android.telecom.action.CONFIGURE_PHONE_ACCOUNT" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <activity android:label="Sip Settings"
                   android:name="com.android.services.telephony.sip.SipSettings"
                   android:theme="@style/DialerSettingsLight"
diff --git a/res/values/config.xml b/res/values/config.xml
index 08a84f8..9f8cc81 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -313,4 +313,8 @@
 
     <!-- Whether or not to show notifications for when bluetooth connection is bad during a call -->
     <bool name="enable_bluetooth_call_quality_notification">false</bool>
+
+    <!-- The package names which can request thermal mitigation. -->
+    <string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
+    </string-array>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4df6a52..46eaaaf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -644,6 +644,13 @@
     <string name="limited_sim_function_with_phone_num_notification_message"><xliff:g id="carrier_name">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="phone_number">%2$s</xliff:g>.</string>
     <!-- Notification message for limited sim function during dual sim [CHAR LIMIT=80]-->
     <string name="limited_sim_function_notification_message"><xliff:g id="carrier_name">%1$s</xliff:g> calls and data services may be blocked while using another SIM.</string>
+    <!-- Notification title for SIP accounts removed -->
+    <string name="sip_accounts_removed_notification_title">Deprecated SIP accounts found and removed</string>
+    <!-- Notification message for SIP accoutns removed -->
+    <string name="sip_accounts_removed_notification_message">
+        SIP calling is no longer supported by Android platform.\nYour existing SIP accounts <xliff:g id="removed_sip_accounts">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting.
+    </string>
+    <string name="sip_accounts_removed_notification_action">Go to settings</string>
     <!-- Mobile network settings screen, data usage setting check box name -->
     <string name="data_usage_title">App data usage</string>
     <!-- Summary about how much data has been used in a date range [CHAR LIMIT=100] -->
@@ -2180,4 +2187,7 @@
     <!-- name of the notification that pops up during
     a phone call when there is bad call quality -->
     <string name="call_quality_notification_name">Call Quality Notification</string>
+    <!-- Telephony notification channel name for a channel containing SIP accounts removed
+     notificatios -->
+    <string name="notification_channel_sip_account">Deprecated SIP accounts</string>
 </resources>
diff --git a/res/xml/phone_account_settings.xml b/res/xml/phone_account_settings.xml
index a243a65..8722761 100644
--- a/res/xml/phone_account_settings.xml
+++ b/res/xml/phone_account_settings.xml
@@ -55,34 +55,4 @@
 
     </PreferenceCategory>
 
-    <PreferenceCategory
-        android:key="phone_accounts_sip_settings_category_key"
-        android:title="@string/sip_settings"
-        android:persistent="false">
-
-        <PreferenceScreen
-            android:title="@string/sip_accounts"
-            android:persistent="false">
-
-            <intent android:action="android.intent.action.MAIN"
-                android:targetPackage="com.android.phone"
-                android:targetClass="com.android.services.telephony.sip.SipSettings" />
-
-        </PreferenceScreen>
-
-        <ListPreference
-            android:key="use_sip_calling_options_key"
-            android:title="@string/sip_call_options_title"
-            android:persistent="true"
-            android:entries="@array/sip_call_options_entries"
-            android:entryValues="@array/sip_call_options_values"/>
-
-        <SwitchPreference
-            android:key="sip_receive_calls_key"
-            android:title="@string/sip_receive_calls"
-            android:summary="@string/sip_receive_calls_summary"
-            android:persistent="true"/>
-
-    </PreferenceCategory>
-
 </PreferenceScreen>
diff --git a/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java b/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
index 1cf7f4b..2845dac 100644
--- a/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
+++ b/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
@@ -16,8 +16,12 @@
 
 package com.android.services.telephony.sip;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
-import android.net.sip.SipException;
+import android.content.Intent;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
 import android.telecom.PhoneAccount;
@@ -25,9 +29,13 @@
 import android.telecom.TelecomManager;
 import android.util.Log;
 
+import com.android.phone.R;
+
+import java.io.IOException;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
 
 /**
  * Manages the {@link PhoneAccount} entries for SIP calling.
@@ -45,41 +53,6 @@
         }
 
         /**
-         * Starts the SIP service associated with the SIP profile.
-         *
-         * @param sipManager The SIP manager.
-         * @param context The context.
-         * @param isReceivingCalls {@code True} if the sip service is being started to make and
-         *          receive calls.  {@code False} if the sip service is being started only for
-         *          outgoing calls.
-         * @return {@code True} if the service started successfully.
-         */
-        boolean startSipService(SipManager sipManager, Context context, boolean isReceivingCalls) {
-            if (VERBOSE) log("startSipService, profile: " + mProfile);
-            try {
-                // Stop the Sip service for the profile if it is already running.  This is important
-                // if we are changing the state of the "receive calls" option.
-                sipManager.close(mProfile.getUriString());
-
-                // Start the sip service for the profile.
-                if (isReceivingCalls) {
-                    sipManager.open(
-                            mProfile,
-                            SipUtil.createIncomingCallPendingIntent(context,
-                                    mProfile.getProfileName()),
-                            null);
-                } else {
-                    sipManager.open(mProfile);
-                }
-                return true;
-            } catch (SipException e) {
-                log("startSipService, profile: " + mProfile.getProfileName() +
-                        ", exception: " + e);
-            }
-            return false;
-        }
-
-        /**
          * Stops the SIP service associated with the SIP profile.  The {@code SipAccountRegistry} is
          * informed when the service has been stopped via an intent which triggers
          * {@link SipAccountRegistry#removeSipProfile(String)}.
@@ -102,9 +75,16 @@
     private static final String PREFIX = "[SipAccountRegistry] ";
     private static final boolean VERBOSE = false; /* STOP SHIP if true */
     private static final SipAccountRegistry INSTANCE = new SipAccountRegistry();
+    private static final String NOTIFICATION_TAG = SipAccountRegistry.class.getSimpleName();
+    private static final int SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID = 1;
+
+    private static final String CHANNEL_ID_SIP_ACCOUNTS_REMOVED = "sipAccountsRemoved";
 
     private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>();
 
+    private NotificationChannel mNotificationChannel;
+    private NotificationManager mNm;
+
     private SipAccountRegistry() {}
 
     public static SipAccountRegistry getInstance() {
@@ -115,8 +95,20 @@
      * Sets up the Account registry and performs any upgrade operations before it is used.
      */
     public void setup(Context context) {
+        setupNotificationChannel(context);
         verifyAndPurgeInvalidPhoneAccounts(context);
-        startSipProfilesAsync(context, (String) null, false);
+        startSipProfilesAsync(context);
+    }
+
+    private void setupNotificationChannel(Context context) {
+        mNotificationChannel = new NotificationChannel(
+                CHANNEL_ID_SIP_ACCOUNTS_REMOVED,
+                context.getText(R.string.notification_channel_sip_account),
+                NotificationManager.IMPORTANCE_HIGH);
+        mNm = context.getSystemService(NotificationManager.class);
+        if (mNm != null) {
+            mNm.createNotificationChannel(mNotificationChannel);
+        }
     }
 
     /**
@@ -149,8 +141,8 @@
      * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all.
      * @param enableProfile Sip account should be enabled
      */
-    void startSipService(Context context, String sipProfileName, boolean enableProfile) {
-        startSipProfilesAsync(context, sipProfileName, enableProfile);
+    void startSipService(Context context, String sipProfileName, boolean enabledProfile) {
+        startSipProfilesAsync(context);
     }
 
     /**
@@ -193,33 +185,20 @@
     }
 
     /**
-     * Causes the SIP service to be restarted for all {@link SipProfile}s.  For example, if the user
-     * toggles the "receive calls" option for SIP, this method handles restarting the SIP services
-     * in the new mode.
-     *
-     * @param context The context.
-     */
-    public void restartSipService(Context context) {
-        startSipProfiles(context, null, false);
-    }
-
-    /**
      * Performs an asynchronous call to
      * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the
      * specified SIP profile and registering its {@link android.telecom.PhoneAccount}.
      *
      * @param context The context.
-     * @param sipProfileName Name of the SIP profile.
-     * @param enableProfile Sip account should be enabled.
      */
     private void startSipProfilesAsync(
-            final Context context, final String sipProfileName, final boolean enableProfile) {
+            final Context context) {
         if (VERBOSE) log("startSipProfiles, start auto registration");
 
         new Thread(new Runnable() {
             @Override
             public void run() {
-                startSipProfiles(context, sipProfileName, enableProfile);
+                startSipProfiles(context);
             }}
         ).start();
     }
@@ -230,48 +209,54 @@
      * register the associated SIP account.
      *
      * @param context The context.
-     * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all.
-     * @param enableProfile Sip account should be enabled.
      */
-    private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) {
-        final SipPreferences sipPreferences = new SipPreferences(context);
-        boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled();
-        TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
-        SipManager sipManager = SipManager.newInstance(context);
+    private void startSipProfiles(Context context) {
         SipProfileDb profileDb = new SipProfileDb(context);
         List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
 
-        for (SipProfile profile : sipProfileList) {
-            // Register a PhoneAccount for the profile and optionally enable the primary
-            // profile.
-            if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) {
-                PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile);
-                telecomManager.registerPhoneAccount(phoneAccount);
-                if (enableProfile) {
-                    telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true);
+        // If there're SIP profiles existing in DB, display a notification and delete all these
+        // profiles.
+        if (!sipProfileList.isEmpty()) {
+            for (SipProfile profile : sipProfileList) {
+                stopSipService(context, profile.getProfileName());
+                removeSipProfile(profile.getProfileName());
+                try {
+                    profileDb.deleteProfile(profile);
+                } catch (IOException e) {
+                    // Ignore
                 }
-                startSipServiceForProfile(profile, sipManager, context, isReceivingCalls);
             }
+            sendSipAccountsRemovedNotification(context, sipProfileList);
         }
     }
 
-    /**
-     * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the
-     * registry.
-     *
-     * @param profile The {@link SipProfile} to start.
-     * @param sipManager The SIP manager.
-     * @param context The context.
-     * @param isReceivingCalls {@code True} if the profile should be started such that it can
-     *      receive incoming calls.
-     */
-    private void startSipServiceForProfile(SipProfile profile, SipManager sipManager,
-            Context context, boolean isReceivingCalls) {
-        removeSipProfile(profile.getUriString());
+    private void sendSipAccountsRemovedNotification(Context context, List<SipProfile> profiles) {
+        String sipAccounts = profiles.stream().map(p -> p.getProfileName())
+                .collect(Collectors.joining(","));
 
-        AccountEntry entry = new AccountEntry(profile);
-        if (entry.startSipService(sipManager, context, isReceivingCalls)) {
-            mAccounts.add(entry);
+        Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
+        intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+
+        Notification.Action action = new Notification.Action.Builder(R.drawable.ic_sim_card,
+                context.getString(R.string.sip_accounts_removed_notification_action),
+                pendingIntent).build();
+        Notification.Builder builder = new Notification.Builder(context)
+                .setSmallIcon(R.drawable.ic_sim_card)
+                .setChannelId(CHANNEL_ID_SIP_ACCOUNTS_REMOVED)
+                .setContentTitle(context.getText(R.string.sip_accounts_removed_notification_title))
+                .setStyle(new Notification.BigTextStyle()
+                .bigText(context.getString(
+                        R.string.sip_accounts_removed_notification_message,
+                        sipAccounts)))
+                .setAutoCancel(true)
+                .addAction(action);
+        Notification notification = builder.build();
+        if (mNm != null) {
+            mNm.notify(NOTIFICATION_TAG, SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID,
+                    notification);
+        } else {
+            log("NotificationManager is null when send the notification of removed SIP accounts");
         }
     }
 
diff --git a/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java b/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java
deleted file mode 100644
index a6f6381..0000000
--- a/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony.sip;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.sip.SipProfile;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.util.Log;
-
-/**
- * This activity receives the standard telecom intent to open settings for a PhoneAccount. It
- * translates the incoming phone account to a SIP profile and opens the corresponding
- * PreferenceActivity for said profile.
- */
-public final class SipPhoneAccountSettingsActivity extends Activity {
-    private static final String TAG = "SipSettingsActivity";
-
-    /** ${inheritDoc} */
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = getIntent();
-        Log.i(TAG, "" + intent);
-        if (intent != null) {
-            PhoneAccountHandle accountHandle = (PhoneAccountHandle)
-                    intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-            Log.i(TAG, "" + accountHandle);
-
-            if (accountHandle != null) {
-                SipProfileDb profileDb = new SipProfileDb(this);
-                String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle);
-                SipProfile profile = profileDb.retrieveSipProfileFromName(profileName);
-                if (profile != null) {
-                    Intent settingsIntent = new Intent(this, SipEditor.class);
-                    settingsIntent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
-                    startActivity(settingsIntent);
-                }
-            }
-        }
-
-        finish();
-    }
-}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 69c2b8a..9a1e275 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -333,6 +333,7 @@
 
     /** The singleton instance. */
     private static PhoneInterfaceManager sInstance;
+    private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
 
     private PhoneGlobals mApp;
     private CallManager mCM;
@@ -9404,11 +9405,43 @@
         return TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
     }
 
+    private static List<String> getThermalMitigationAllowlist(Context context) {
+        if (sThermalMitigationAllowlistedPackages.isEmpty()) {
+            for (String pckg : context.getResources()
+                    .getStringArray(R.array.thermal_mitigation_allowlisted_packages)) {
+                sThermalMitigationAllowlistedPackages.add(pckg);
+            }
+        }
+
+        return sThermalMitigationAllowlistedPackages;
+    }
+
+    /**
+     * Used by shell commands to add an authorized package name for thermal mitigation.
+     * @param packageName name of package to be allowlisted
+     * @param context
+     */
+    static void addPackageToThermalMitigationAllowlist(String packageName, Context context) {
+        sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+        sThermalMitigationAllowlistedPackages.add(packageName);
+    }
+
+    /**
+     * Used by shell commands to remove an authorized package name for thermal mitigation.
+     * @param packageName name of package to remove from allowlist
+     * @param context
+     */
+    static void removePackageFromThermalMitigationAllowlist(String packageName, Context context) {
+        sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+        sThermalMitigationAllowlistedPackages.remove(packageName);
+    }
+
     /**
      * Thermal mitigation request to control functionalities at modem.
      *
      * @param subId the id of the subscription.
      * @param thermalMitigationRequest holds all necessary information to be passed down to modem.
+     * @param callingPackage the package name of the calling package.
      *
      * @return thermalMitigationResult enum as defined in android.telephony.Annotation.
      */
@@ -9416,9 +9449,17 @@
     @ThermalMitigationResult
     public int sendThermalMitigationRequest(
             int subId,
-            ThermalMitigationRequest thermalMitigationRequest) throws IllegalArgumentException {
+            ThermalMitigationRequest thermalMitigationRequest,
+            String callingPackage) throws IllegalArgumentException {
         enforceModifyPermission();
 
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        if (!getThermalMitigationAllowlist(getDefaultPhone().getContext())
+                .contains(callingPackage)) {
+            throw new SecurityException("Calling package must be configured in the device config. "
+                    + "calling package: " + callingPackage);
+        }
+
         WorkSource workSource = getWorkSource(Binder.getCallingUid());
         final long identity = Binder.clearCallingIdentity();
 
@@ -9843,6 +9884,30 @@
     }
 
     /**
+     * Overrides the ims feature validation result
+     */
+    @Override
+    public boolean setImsFeatureValidationOverride(int subId, String enabledStr) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "setImsFeatureValidationOverride");
+
+        Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+                : Boolean.parseBoolean(enabledStr);
+        return RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(
+                subId, enabled);
+    }
+
+    /**
+     * Gets the ims feature validation override value
+     */
+    @Override
+    public boolean getImsFeatureValidationOverride(int subId) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "getImsFeatureValidationOverride");
+        return RcsProvisioningMonitor.getInstance().getImsFeatureValidationOverride(subId);
+    }
+
+    /**
      * Get the mobile provisioning url that is used to launch a browser to allow users to manage
      * their mobile plan.
      */
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index bcf1491..6fdde78 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -73,6 +73,7 @@
     private static final int EVENT_DEVICE_CONFIG_OVERRIDE = 6;
     private static final int EVENT_CARRIER_CONFIG_OVERRIDE = 7;
     private static final int EVENT_RESET = 8;
+    private static final int EVENT_FEATURE_ENABLED_OVERRIDE = 9;
 
     private final PhoneGlobals mPhone;
     private final Handler mHandler;
@@ -82,6 +83,8 @@
     private Boolean mDeviceSingleRegistrationEnabledOverride;
     private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
             new HashMap<>();
+    private final ConcurrentHashMap<Integer, Boolean> mImsFeatureValidationOverride =
+            new ConcurrentHashMap<>();
     private String mDmaPackageName;
     private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
     private volatile boolean mTestModeEnabled;
@@ -626,6 +629,18 @@
     }
 
     /**
+     * override the rcs feature validation result for a subscription
+     */
+    public boolean overrideImsFeatureValidation(int subId, Boolean enabled) {
+        if (enabled == null) {
+            mImsFeatureValidationOverride.remove(subId);
+        } else {
+            mImsFeatureValidationOverride.put(subId, enabled);
+        }
+        return true;
+    }
+
+    /**
      * Returns the device config whether single registration is enabled
      */
     public boolean getDeviceSingleRegistrationEnabled() {
@@ -647,6 +662,13 @@
         return false;
     }
 
+    /**
+     * Returns the rcs feature validation override value, null if it is not set.
+     */
+    public Boolean getImsFeatureValidationOverride(int subId) {
+        return mImsFeatureValidationOverride.get(subId);
+    }
+
     private void onDefaultMessagingApplicationChanged() {
         final String packageName = getDmaPackageName();
         if (!TextUtils.equals(mDmaPackageName, packageName)) {
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
index a7d27d5..32562fa 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -18,6 +18,9 @@
 
 import static android.provider.Telephony.ServiceStateTable;
 import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
 import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
 import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
@@ -61,18 +64,6 @@
     public static final String SERVICE_STATE = "service_state";
 
     /**
-     * An integer value indicating the current data service state.
-     * <p>
-     * Valid values: {@link ServiceState#STATE_IN_SERVICE},
-     * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
-     * {@link ServiceState#STATE_POWER_OFF}.
-     * <p>
-     * This is the same as {@link ServiceState#getDataRegState()}.
-     * @hide
-     */
-    public static final String DATA_REG_STATE = "data_reg_state";
-
-    /**
      * An integer value indicating the current voice roaming type.
      * <p>
      * This is the same as {@link ServiceState#getVoiceRoamingType()}.
@@ -257,6 +248,8 @@
         IS_USING_CARRIER_AGGREGATION,
         OPERATOR_ALPHA_LONG_RAW,
         OPERATOR_ALPHA_SHORT_RAW,
+        DATA_NETWORK_TYPE,
+        DUPLEX_MODE,
     };
 
     @Override
@@ -392,6 +385,8 @@
             final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
             final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
             final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
+            final int data_network_type = ss.getDataNetworkType();
+            final int duplex_mode = ss.getDuplexMode();
 
             return buildSingleRowResult(projection, sColumns, new Object[] {
                     voice_reg_state,
@@ -418,6 +413,8 @@
                     is_using_carrier_aggregation,
                     operator_alpha_long_raw,
                     operator_alpha_short_raw,
+                    data_network_type,
+                    duplex_mode,
             });
         }
     }
@@ -480,6 +477,10 @@
             context.getContentResolver().notifyChange(
                     getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
         }
+        if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
+        }
     }
 
     private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
@@ -498,6 +499,10 @@
         return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
     }
 
+    private static boolean dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getDataNetworkType() != newSS.getDataNetworkType();
+    }
+
     /**
      * Notify interested apps that the ServiceState has changed.
      *
@@ -517,7 +522,8 @@
         // If oldSS is null and newSS is not (e.g. first update of service state) this will also
         // notify
         if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
-                || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) {
+                || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
+                || dataNetworkTypeChanged(oldSS, newSS)) {
             context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
         }
     }
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 87dc868..36d539a 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -113,6 +113,8 @@
     private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled";
     private static final String SRC_SET_TEST_ENABLED = "set-test-enabled";
     private static final String SRC_GET_TEST_ENABLED = "get-test-enabled";
+    private static final String SRC_SET_FEATURE_ENABLED = "set-feature-validation";
+    private static final String SRC_GET_FEATURE_ENABLED = "get-feature-validation";
 
     private static final String D2D_SUBCOMMAND = "d2d";
     private static final String D2D_SEND = "send";
@@ -128,6 +130,10 @@
     // Check if a package has carrier privileges on any SIM, regardless of subId/phoneId.
     private static final String HAS_CARRIER_PRIVILEGES_COMMAND = "has-carrier-privileges";
 
+    private static final String THERMAL_MITIGATION_COMMAND = "thermal-mitigation";
+    private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
+    private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -264,6 +270,8 @@
                 return handleUnattendedReboot();
             case HAS_CARRIER_PRIVILEGES_COMMAND:
                 return handleHasCarrierPrivilegesCommand();
+            case THERMAL_MITIGATION_COMMAND:
+                return handleThermalMitigationCommand();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -410,6 +418,16 @@
         pw.println("    1 if the call would have been intercepted, 0 otherwise.");
     }
 
+    private void onHelpThermalMitigation() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Thermal mitigation commands");
+        pw.println("  thermal-mitigation allow-package PACKAGE_NAME");
+        pw.println("    Set the package as one of authorized packages for thermal mitigation.");
+        pw.println("  thermal-mitigation disallow-package PACKAGE_NAME");
+        pw.println("    Remove the package from one of the authorized packages for thermal "
+                + "mitigation.");
+    }
+
     private void onHelpDataTestMode() {
         PrintWriter pw = getOutPrintWriter();
         pw.println("Mobile Data Test Mode Commands:");
@@ -516,6 +534,17 @@
         pw.println("    Options are:");
         pw.println("      -s: The SIM slot ID to read the config value for. If no option");
         pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  src set-feature-validation [-s SLOT_ID] true|false|null");
+        pw.println("    Sets ims feature validation result.");
+        pw.println("    The value could be true, false, or null(undefined).");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to set the config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("  src get-feature-validation [-s SLOT_ID]");
+        pw.println("    Gets ims feature validation override value.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read the config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
     }
 
     private int handleImsCommand() {
@@ -696,6 +725,36 @@
         return -1;
     }
 
+    private int handleThermalMitigationCommand() {
+        String arg = getNextArg();
+        String packageName = getNextArg();
+        if (arg == null || packageName == null) {
+            onHelpThermalMitigation();
+            return 0;
+        }
+
+        if (!checkShellUid()) {
+            return -1;
+        }
+
+        switch (arg) {
+            case ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+                PhoneInterfaceManager.addPackageToThermalMitigationAllowlist(packageName, mContext);
+                return 0;
+            }
+            case DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+                PhoneInterfaceManager.removePackageFromThermalMitigationAllowlist(packageName,
+                        mContext);
+                return 0;
+            }
+            default:
+                onHelpThermalMitigation();
+        }
+
+        return -1;
+
+    }
+
     private int handleD2dCommand() {
         String arg = getNextArg();
         if (arg == null) {
@@ -1719,6 +1778,12 @@
             case SRC_GET_CARRIER_ENABLED: {
                 return handleSrcGetCarrierEnabledCommand();
             }
+            case SRC_SET_FEATURE_ENABLED: {
+                return handleSrcSetFeatureValidationCommand();
+            }
+            case SRC_GET_FEATURE_ENABLED: {
+                return handleSrcGetFeatureValidationCommand();
+            }
         }
 
         return -1;
@@ -2042,6 +2107,55 @@
         return 0;
     }
 
+    private int handleSrcSetFeatureValidationCommand() {
+        //the release time value could be -1
+        int subId = getRemainingArgsCount() > 1 ? getSubId("src set-feature-validation")
+                : SubscriptionManager.getDefaultSubscriptionId();
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String enabledStr = getNextArg();
+        if (enabledStr == null) {
+            return -1;
+        }
+
+        try {
+            boolean result =
+                    mInterface.setImsFeatureValidationOverride(subId, enabledStr);
+            if (VDBG) {
+                Log.v(LOG_TAG, "src set-feature-validation -s " + subId + " "
+                        + enabledStr + ", result=" + result);
+            }
+            getOutPrintWriter().println(result);
+        } catch (NumberFormatException | RemoteException e) {
+            Log.w(LOG_TAG, "src set-feature-validation -s " + subId + " "
+                    + enabledStr + ", error" + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
+    private int handleSrcGetFeatureValidationCommand() {
+        int subId = getSubId("src get-feature-validation");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        Boolean result = false;
+        try {
+            result = mInterface.getImsFeatureValidationOverride(subId);
+        } catch (RemoteException e) {
+            return -1;
+        }
+        if (VDBG) {
+            Log.v(LOG_TAG, "src get-feature-validation -s " + subId + ", returned: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
+
     private int handleHasCarrierPrivilegesCommand() {
         String packageName = getNextArgRequired();
 
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 3811a77..224a1f9 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -6,14 +6,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
-import android.net.sip.SipManager;
 import android.os.Bundle;
 import android.os.UserManager;
-import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceFragment;
-import android.preference.SwitchPreference;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -28,9 +25,6 @@
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.phone.SubscriptionInfoHelper;
-import com.android.services.telephony.sip.SipAccountRegistry;
-import com.android.services.telephony.sip.SipPreferences;
-import com.android.services.telephony.sip.SipUtil;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -48,11 +42,6 @@
 
     private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_accounts_all_calling_accounts";
 
-    private static final String SIP_SETTINGS_CATEGORY_PREF_KEY =
-            "phone_accounts_sip_settings_category_key";
-    private static final String USE_SIP_PREF_KEY = "use_sip_calling_options_key";
-    private static final String SIP_RECEIVE_CALLS_PREF_KEY = "sip_receive_calls_key";
-
     private static final String MAKE_AND_RECEIVE_CALLS_CATEGORY_KEY =
             "make_and_receive_calls_settings_category_key";
     private static final String DEFAULT_OUTGOING_ACCOUNT_KEY = "default_outgoing_account";
@@ -84,10 +73,6 @@
     private PreferenceCategory mMakeAndReceiveCallsCategory;
     private boolean mMakeAndReceiveCallsCategoryPresent;
 
-    private ListPreference mUseSipCalling;
-    private SwitchPreference mSipReceiveCallsPreference;
-    private SipPreferences mSipPreferences;
-
     private final SubscriptionManager.OnSubscriptionsChangedListener
             mOnSubscriptionsChangeListener =
             new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -154,39 +139,6 @@
         updateAccounts();
         updateMakeCallsOptions();
 
-        if (isPrimaryUser() && SipUtil.isVoipSupported(getActivity())) {
-            mSipPreferences = new SipPreferences(getActivity());
-
-            mUseSipCalling = (ListPreference)
-                    getPreferenceScreen().findPreference(USE_SIP_PREF_KEY);
-            mUseSipCalling.setEntries(!SipManager.isSipWifiOnly(getActivity())
-                    ? R.array.sip_call_options_wifi_only_entries
-                    : R.array.sip_call_options_entries);
-            mUseSipCalling.setOnPreferenceChangeListener(this);
-
-            int optionsValueIndex =
-                    mUseSipCalling.findIndexOfValue(mSipPreferences.getSipCallOption());
-            if (optionsValueIndex == -1) {
-                // If the option is invalid (eg. deprecated value), default to SIP_ADDRESS_ONLY.
-                mSipPreferences.setSipCallOption(
-                        getResources().getString(R.string.sip_address_only));
-                optionsValueIndex =
-                        mUseSipCalling.findIndexOfValue(mSipPreferences.getSipCallOption());
-            }
-            mUseSipCalling.setValueIndex(optionsValueIndex);
-            mUseSipCalling.setSummary(mUseSipCalling.getEntry());
-
-            mSipReceiveCallsPreference = (SwitchPreference)
-                    getPreferenceScreen().findPreference(SIP_RECEIVE_CALLS_PREF_KEY);
-            mSipReceiveCallsPreference.setEnabled(SipUtil.isPhoneIdle(getActivity()));
-            mSipReceiveCallsPreference.setChecked(
-                    mSipPreferences.isReceivingCallsEnabled());
-            mSipReceiveCallsPreference.setOnPreferenceChangeListener(this);
-        } else {
-            getPreferenceScreen().removePreference(
-                    getPreferenceScreen().findPreference(SIP_SETTINGS_CATEGORY_PREF_KEY));
-        }
-
         SubscriptionManager.from(getActivity()).addOnSubscriptionsChangedListener(
                 mOnSubscriptionsChangeListener);
     }
@@ -207,21 +159,6 @@
      */
     @Override
     public boolean onPreferenceChange(Preference pref, Object objValue) {
-        if (pref == mUseSipCalling) {
-            String option = objValue.toString();
-            mSipPreferences.setSipCallOption(option);
-            mUseSipCalling.setValueIndex(mUseSipCalling.findIndexOfValue(option));
-            mUseSipCalling.setSummary(mUseSipCalling.getEntry());
-            return true;
-        } else if (pref == mSipReceiveCallsPreference) {
-            final boolean isEnabled = !mSipReceiveCallsPreference.isChecked();
-            new Thread(new Runnable() {
-                public void run() {
-                    handleSipReceiveCallsOption(isEnabled);
-                }
-            }).start();
-            return true;
-        }
         return false;
     }
 
@@ -256,22 +193,6 @@
     @Override
     public void onAccountChanged(AccountSelectionPreference pref) {}
 
-    private synchronized void handleSipReceiveCallsOption(boolean isEnabled) {
-        Context context = getActivity();
-        if (context == null) {
-            // Return if the fragment is detached from parent activity before executed by thread.
-            return;
-        }
-
-        mSipPreferences.setReceivingCallsEnabled(isEnabled);
-
-        SipUtil.useSipToReceiveIncomingCalls(context, isEnabled);
-
-        // Restart all Sip services to ensure we reflect whether we are receiving calls.
-        SipAccountRegistry sipAccountRegistry = SipAccountRegistry.getInstance();
-        sipAccountRegistry.restartSipService(context);
-    }
-
     /**
      * Queries the telcomm manager to update the default outgoing account selection preference
      * with the list of outgoing accounts and the current default outgoing account.
@@ -409,32 +330,24 @@
             mAccountList.removeAll();
             List<PhoneAccountHandle> allNonSimAccounts =
                     getCallingAccounts(false /* includeSims */, true /* includeDisabled */);
-            // Check to see if we should show the entire section at all.
-            if (shouldShowConnectionServiceList(allNonSimAccounts)) {
-                List<PhoneAccountHandle> enabledAccounts =
-                        getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
-                // Initialize the account list with the set of enabled & SIM accounts.
-                initAccountList(enabledAccounts);
 
-                // Only show the 'Make Calls With..." option if there are multiple accounts.
-                if (enabledAccounts.size() > 1) {
-                    mMakeAndReceiveCallsCategory.addPreference(mDefaultOutgoingAccount);
-                    mMakeAndReceiveCallsCategoryPresent = true;
-                    mDefaultOutgoingAccount.setListener(this);
-                    updateDefaultOutgoingAccountsModel();
-                } else {
-                    mMakeAndReceiveCallsCategory.removePreference(mDefaultOutgoingAccount);
-                }
+            List<PhoneAccountHandle> enabledAccounts =
+                    getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
+            // Initialize the account list with the set of enabled & SIM accounts.
+            initAccountList(enabledAccounts);
 
-                // If there are no third party (nonSim) accounts,
-                // then don't show enable/disable dialog.
-                if (!allNonSimAccounts.isEmpty()) {
-                    mAccountList.addPreference(mAllCallingAccounts);
-                } else {
-                    mAccountList.removePreference(mAllCallingAccounts);
-                }
+            // Always show the 'Make Calls With..." option
+            mMakeAndReceiveCallsCategory.addPreference(mDefaultOutgoingAccount);
+            mMakeAndReceiveCallsCategoryPresent = true;
+            mDefaultOutgoingAccount.setListener(this);
+            updateDefaultOutgoingAccountsModel();
+
+            // If there are no third party (nonSim) accounts,
+            // then don't show enable/disable dialog.
+            if (!allNonSimAccounts.isEmpty()) {
+                mAccountList.addPreference(mAllCallingAccounts);
             } else {
-                getPreferenceScreen().removePreference(mAccountList);
+                mAccountList.removePreference(mAllCallingAccounts);
             }
         }
     }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 3706d3b..d745aab 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -1596,6 +1596,7 @@
                 Connection.AUDIO_CODEC_NONE);
         if (newCodecType != oldCodecType) {
             newExtras.putInt(Connection.EXTRA_AUDIO_CODEC, newCodecType);
+            Log.i(this, "put audio codec:" + newCodecType);
             changed = true;
         }
         if (isImsConnection()) {
@@ -1603,6 +1604,7 @@
             float oldBitrate = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f);
             if (Math.abs(newBitrate - oldBitrate) > THRESHOLD) {
                 newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, newBitrate);
+                Log.i(this, "put audio bitrate:" + newBitrate);
                 changed = true;
             }
 
@@ -1611,6 +1613,7 @@
                     0.0f);
             if (Math.abs(newBandwidth - oldBandwidth) > THRESHOLD) {
                 newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, newBandwidth);
+                Log.i(this, "put audio bandwidth:" + newBandwidth);
                 changed = true;
             }
         } else {
@@ -1621,6 +1624,12 @@
         }
 
         if (changed) {
+            Log.i(this, "Audio attribute, Codec:"
+                    + newExtras.getInt(Connection.EXTRA_AUDIO_CODEC, Connection.AUDIO_CODEC_NONE)
+                    + ", Bitrate:"
+                    + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f)
+                    + ", Bandwidth:"
+                    + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, 0.0f));
             putTelephonyExtras(newExtras);
         }
     }
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
index a948cdb..cecc8a2 100644
--- a/src/com/android/services/telephony/rcs/SipTransportController.java
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -19,8 +19,10 @@
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.content.Context;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ims.DelegateRequest;
 import android.telephony.ims.FeatureTagState;
 import android.telephony.ims.ImsException;
@@ -44,12 +46,14 @@
 import com.android.ims.RcsFeatureManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.RcsProvisioningMonitor;
 
 import com.google.common.base.Objects;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -247,6 +251,10 @@
     private RcsFeatureManager mRcsManager;
     // Cached package name of the app that is considered the default SMS app.
     private String mCachedSmsRolePackageName = "";
+    // Callback to monitor rcs provisioning change
+    private CarrierConfigManager mCarrierConfigManager;
+    // Cached allowed feature tags from carrier config
+    ArraySet<String> mFeatureTagsAllowed = new ArraySet<>();
 
     /**
      * Create an instance of SipTransportController.
@@ -261,6 +269,7 @@
         mRoleManagerAdapter = new RoleManagerAdapterImpl(context);
         mTimerAdapter = new TimerAdapterImpl();
         mExecutorService = Executors.newSingleThreadScheduledExecutor();
+        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
     }
 
     /**
@@ -277,6 +286,7 @@
         mTimerAdapter = timerAdapter;
         mDelegateControllerFactory = delegateFactory;
         mExecutorService = executor;
+        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
         logi("created");
     }
 
@@ -813,21 +823,14 @@
         }
 
         ArraySet<String> previouslyGrantedTags = new ArraySet<>(alreadyRequestedTags);
-        // deny tags already used by other delegates
-        Set<FeatureTagState> deniedTags = new ArraySet<>();
-        for (String s : requestedFeatureTags) {
-            if (previouslyGrantedTags.contains(s)) {
-                deniedTags.add(new FeatureTagState(s,
-                        SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE));
-            }
-        }
-        Set<String> nonDeniedTags = requestedFeatureTags.stream()
-                .filter(r -> !previouslyGrantedTags.contains(r))
-                .collect(Collectors.toSet());
+        ArraySet<String> candidateFeatureTags = new ArraySet<>(requestedFeatureTags);
+        Set<FeatureTagState> deniedTags =
+                updateSupportedTags(candidateFeatureTags, previouslyGrantedTags);
+
         // Add newly granted tags to the already requested tags list.
-        previouslyGrantedTags.addAll(nonDeniedTags);
+        previouslyGrantedTags.addAll(candidateFeatureTags);
         CompletableFuture<Boolean> pendingChange = controller.changeSupportedFeatureTags(
-                nonDeniedTags, deniedTags);
+                candidateFeatureTags, deniedTags);
         logi("changeSupportedFeatureTags pendingChange=" + pendingChange);
         // do not worry about executor used here, this stage used to interpret result + add log.
         return pendingChange.thenApply((completedSuccessfully) ->  {
@@ -838,6 +841,51 @@
     }
 
     /**
+     * Update candidate feature tags according to feature tags allowed by carrier config,
+     * and previously granted by other SipDelegates.
+     *
+     * @param candidateFeatureTags The candidate feature tags to be updated. It will be
+     * updated as needed per the carrier config and previously granted feature tags.
+     * @param previouslyGrantedTags The feature tags already granted by other SipDelegates.
+     * @return The set of denied feature tags.
+     */
+    private Set<FeatureTagState> updateSupportedTags(Set<String> candidateFeatureTags,
+            Set<String> previouslyGrantedTags) {
+        Boolean overrideRes = RcsProvisioningMonitor.getInstance()
+                .getImsFeatureValidationOverride(mSubId);
+        // deny tags already used by other delegates
+        Set<FeatureTagState> deniedTags = new ArraySet<>();
+
+        // match config if feature validation is not overridden
+        if (overrideRes == null) {
+            Iterator<String> it = candidateFeatureTags.iterator();
+            while (it.hasNext()) {
+                String tag = it.next();
+                if (previouslyGrantedTags.contains(tag)) {
+                    logi(tag + " has already been granted previously.");
+                    it.remove();
+                    deniedTags.add(new FeatureTagState(tag,
+                            SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE));
+                } else if (!mFeatureTagsAllowed.contains(tag.trim().toLowerCase())) {
+                    logi(tag + " is not allowed per config.");
+                    it.remove();
+                    deniedTags.add(new FeatureTagState(tag,
+                              SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+                }
+            }
+        } else if (Boolean.FALSE.equals(overrideRes)) {
+            logi("all features are denied for test purpose.");
+            for (String s : candidateFeatureTags) {
+                deniedTags.add(new FeatureTagState(s,
+                        SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+            }
+            candidateFeatureTags.clear();
+        }
+
+        return deniedTags;
+    }
+
+    /**
      * Run a Callable on the ExecutorService Thread and wait for the result.
      * If an ImsException is thrown, catch it and rethrow it to caller.
      */
@@ -918,6 +966,8 @@
             scheduleDestroyDelegates(
                     SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
         }
+
+        onCarrierConfigChangedInternal();
     }
 
     /**
@@ -925,6 +975,15 @@
      */
     private void onCarrierConfigChangedInternal() {
         logi("Carrier Config changed for subId: " + mSubId);
+        mFeatureTagsAllowed.clear();
+        PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
+        String[] tagConfigs = carrierConfig.getStringArray(
+                CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY);
+        if (tagConfigs != null && tagConfigs.length > 0) {
+            for (String tag : tagConfigs) {
+                mFeatureTagsAllowed.add(tag.trim().toLowerCase());
+            }
+        }
     }
 
     /**
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 28c4390..54333bb 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -537,6 +537,50 @@
 
     @Test
     @SmallTest
+    public void testOverrideDeviceSingleRegistrationEnabled() throws Exception {
+        createMonitor(1);
+
+        when(mPackageManager.hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(false);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(null);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        when(mPackageManager.hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+        //use carrier config change to refresh the value as system feature is static
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(true);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(null);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+    }
+
+    @Test
+    @SmallTest
     public void testTestModeEnabledAndDisabled() throws Exception {
         when(mCursor.getBlob(anyInt())).thenReturn(null);
         createMonitor(1);
@@ -574,6 +618,65 @@
                 (byte[]) mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG)));
     }
 
+    @Test
+    @SmallTest
+    public void testOverrideCarrierSingleRegistrationEnabled() throws Exception {
+        createMonitor(1);
+
+        when(mPackageManager.hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor
+                .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, false);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor
+                .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, null);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor
+                .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, true);
+        processAllMessages();
+        assertTrue(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+        assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor
+                .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, null);
+        processAllMessages();
+        assertFalse(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+        assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+    }
+
+    @Test
+    @SmallTest
+    public void testOverrideImsFeatureValidation() throws Exception {
+        createMonitor(1);
+
+        mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, false);
+        assertFalse(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, true);
+        assertTrue(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+
+        mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, null);
+        assertNull(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+    }
+
     private void createMonitor(int subCount) throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
index 32e5f26..d85976a 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Telephony.ServiceStateTable;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -31,8 +32,11 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -60,7 +64,7 @@
     private final String[] mTestProjection =
     {
         ServiceStateTable.VOICE_REG_STATE,
-        ServiceStateProvider.DATA_REG_STATE,
+        ServiceStateTable.DATA_REG_STATE,
         ServiceStateProvider.VOICE_OPERATOR_ALPHA_LONG,
         ServiceStateProvider.VOICE_OPERATOR_ALPHA_SHORT,
         ServiceStateTable.VOICE_OPERATOR_NUMERIC,
@@ -81,15 +85,24 @@
         ServiceStateProvider.IS_USING_CARRIER_AGGREGATION,
         ServiceStateProvider.OPERATOR_ALPHA_LONG_RAW,
         ServiceStateProvider.OPERATOR_ALPHA_SHORT_RAW,
+        ServiceStateTable.DATA_NETWORK_TYPE,
+        ServiceStateTable.DUPLEX_MODE,
     };
 
+    // Exception used internally to verify if the Resolver#notifyChange has been called.
+    private class TestNotifierException extends RuntimeException {
+        TestNotifierException() {
+            super();
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         mContext = mock(Context.class);
         mContentResolver = new MockContentResolver() {
             @Override
             public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
-                throw new RuntimeException("notifyChange!");
+                throw new TestNotifierException();
             }
         };
         doReturn(mContentResolver).when(mContext).getContentResolver();
@@ -99,6 +112,15 @@
         mTestServiceStateForSubId1 = new ServiceState();
         mTestServiceStateForSubId1.setStateOff();
 
+        // Add NRI to trigger SS with non-default values (e.g. duplex mode)
+        NetworkRegistrationInfo nriWwan = new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .build();
+        mTestServiceStateForSubId1.addNetworkRegistrationInfo(nriWwan);
+        mTestServiceStateForSubId1.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
+
         // Mock out the actual phone state
         ServiceStateProvider provider = new ServiceStateProvider() {
             @Override
@@ -183,6 +205,8 @@
         final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
         final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
         final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
+        final int dataNetworkType = ss.getDataNetworkType();
+        final int duplexMode = ss.getDuplexMode();
 
         assertEquals(voiceRegState, cursor.getInt(0));
         assertEquals(dataRegState, cursor.getInt(1));
@@ -206,6 +230,8 @@
         assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
         assertEquals(operatorAlphaLongRaw, cursor.getString(20));
         assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+        assertEquals(dataNetworkType, cursor.getInt(22));
+        assertEquals(duplexMode, cursor.getInt(23));
     }
 
     /**
@@ -226,21 +252,12 @@
         newSS.setCdmaSystemAndNetworkId(0, 0);
 
         // Test that notifyChange is not called for these fields
-        boolean notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
+        assertFalse(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
     }
 
     @Test
     @SmallTest
-    public void testNotifyChanged() {
+    public void testNotifyChanged_noStateUpdated() {
         int subId = 0;
 
         ServiceState oldSS = new ServiceState();
@@ -251,57 +268,106 @@
         copyOfOldSS.setStateOutOfService();
         copyOfOldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
 
+        // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
+        assertFalse(notifyChangeCalledForSubId(oldSS, copyOfOldSS, subId));
+
+        // Test that notifyChange is not called with no change in notifyChangeForSubId
+        assertFalse(notifyChangeCalledForSubIdAndField(oldSS, copyOfOldSS, subId));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged_voiceRegStateUpdated() {
+        int subId = 0;
+
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
         ServiceState newSS = new ServiceState();
         newSS.setStateOutOfService();
         newSS.setVoiceRegState(ServiceState.STATE_POWER_OFF);
 
-        // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
-        boolean notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, copyOfOldSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
-
-        // Test that notifyChange is not called with no change in notifyChangeForSubId
-        notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, copyOfOldSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertFalse(notifyChangeWasCalled);
-
         // Test that notifyChange is called by notifyChangeForSubIdAndField when the voice_reg_state
         // changes
-        notifyChangeWasCalled = false;
-        try {
-            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
-        }
-        assertTrue(notifyChangeWasCalled);
+        assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
 
         // Test that notifyChange is called by notifyChangeForSubId when the voice_reg_state changes
-        notifyChangeWasCalled = false;
+        assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged_dataNetworkTypeUpdated() {
+        int subId = 0;
+
+        // While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
+        // change that will trigger the change of dataNetworkType, according to the logic in
+        // ServiceState#getDataNetworkType
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+
+        NetworkRegistrationInfo nriWwan = new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        newSS.addNetworkRegistrationInfo(nriWwan);
+
+        // Test that notifyChange is called by notifyChangeForSubId when the
+        // data_network_type changes
+        assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
+
+        // Test that notifyChange is called by notifyChangeForSubIdAndField when the
+        // data_network_type changes
+        assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged_dataRegStateUpdated() {
+        int subId = 0;
+
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+        newSS.setDataRegState(ServiceState.STATE_POWER_OFF);
+
+        // Test that notifyChange is called by notifyChangeForSubId
+        // when the data_reg_state changes
+        assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
+
+        // Test that notifyChange is called by notifyChangeForSubIdAndField
+        // when the data_reg_state changes
+        assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+    }
+
+    // Check if notifyChange was called by notifyChangeForSubId
+    private boolean notifyChangeCalledForSubId(ServiceState oldSS,
+            ServiceState newSS, int subId) {
         try {
             ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, newSS, subId);
-        } catch (RuntimeException e) {
-            final String message = e.getMessage();
-            if (message != null &&  message.equals("notifyChange!")) {
-                notifyChangeWasCalled = true;
-            }
+        } catch (TestNotifierException e) {
+            return true;
         }
-        assertTrue(notifyChangeWasCalled);
+        return false;
+    }
+
+    // Check if notifyChange was called by notifyChangeForSubIdAndField
+    private boolean notifyChangeCalledForSubIdAndField(ServiceState oldSS,
+            ServiceState newSS, int subId) {
+        try {
+            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+        } catch (TestNotifierException e) {
+            return true;
+        }
+        return false;
     }
 }
diff --git a/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
index fa27775..d364fe4 100644
--- a/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
@@ -35,7 +35,9 @@
 
 import android.app.role.RoleManager;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ims.DelegateRequest;
 import android.telephony.ims.FeatureTagState;
 import android.telephony.ims.ImsException;
@@ -54,6 +56,7 @@
 import com.android.TelephonyTestBase;
 import com.android.TestExecutorService;
 import com.android.ims.RcsFeatureManager;
+import com.android.phone.RcsProvisioningMonitor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -129,6 +132,9 @@
             return c;
         }).when(mMockDelegateControllerFactory).create(anyInt(), any(), anyString(), any(), any(),
                 any(), any(), any());
+        setFeatureAllowedConfig(TEST_SUB_ID, new String[]{ImsSignallingUtils.MMTEL_TAG,
+                ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG, ImsSignallingUtils.GROUP_CHAT_TAG,
+                ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
     }
 
     @After
@@ -138,6 +144,7 @@
         if (!isShutdown) {
             mExecutorService.shutdownNow();
         }
+        RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, null);
     }
 
     @SmallTest
@@ -607,6 +614,9 @@
     @Test
     public void testTimingSubIdChangedAndCreateNewSubId() throws Exception {
         SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+        setFeatureAllowedConfig(TEST_SUB_ID + 1, new String[]{ImsSignallingUtils.MMTEL_TAG,
+                ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG, ImsSignallingUtils.GROUP_CHAT_TAG,
+                ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
 
         ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
         DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
@@ -643,6 +653,67 @@
         verifyDelegateChanged(c2, pendingC2Change, secondDelegate, Collections.emptySet(), 0);
     }
 
+    @SmallTest
+    @Test
+    public void testFeatureTagsDeniedByConfig() throws Exception {
+        setFeatureAllowedConfig(TEST_SUB_ID, new String[]{ImsSignallingUtils.GROUP_CHAT_TAG,
+                ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
+        SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+        ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+        ArraySet<String> allowedTags = new ArraySet<>(requestTags);
+        ArraySet<String> deniedTags = new ArraySet<>();
+        DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+        SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+                delegateRequest);
+        CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+                delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+        allowedTags.remove(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+        deniedTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+
+        assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+        verifyDelegateChanged(sc, pendingScChange, allowedTags, getDeniedTagsForReason(
+                deniedTags, SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+    }
+
+    @SmallTest
+    @Test
+    public void testFeatureTagsDeniedByOverride() throws Exception {
+        RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, false);
+        SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+        ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+        ArraySet<String> deniedTags = new ArraySet<>(requestTags);
+        DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+        SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+                delegateRequest);
+        CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+                delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+
+        assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+        verifyDelegateChanged(sc, pendingScChange, Collections.emptySet(), getDeniedTagsForReason(
+                deniedTags, SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+    }
+
+    @SmallTest
+    @Test
+    public void testFeatureTagsDeniedByConfigAllowedByOverride() throws Exception {
+        setFeatureAllowedConfig(TEST_SUB_ID, new String[]{});
+        RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, true);
+        SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+        ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+        ArraySet<String> allowedTags = new ArraySet<>(requestTags);
+        DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+        SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+                delegateRequest);
+        CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+                delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+
+        assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+        verifyDelegateChanged(sc, pendingScChange, allowedTags, Collections.emptySet(), 0);
+    }
+
     @SafeVarargs
     private final Pair<Set<String>, Set<FeatureTagState>> getAllowedAndDeniedTagsForConfig(
             DelegateRequest r, int denyReason, Set<String>... previousRequestedTagSets) {
@@ -910,4 +981,10 @@
         }
         return true;
     }
+
+    private void setFeatureAllowedConfig(int subId, String[] tags) {
+        PersistableBundle bundle = mContext.getCarrierConfig(subId);
+        bundle.putStringArray(
+                CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, tags);
+    }
 }