[automerger skipped] Specify the component for the ACTION_SHOW_ECM_EXIT_DIALOG Intent am: 11c41d321d am: 46eb4bfb39 am: 4c46c1a119 am: 5384400e3b am: f89a32db01 am: 2804fd0079 -s ours am: b0206e5f84 -s ours am: 3eb48999b2 -s ours

am skip reason: Change-Id Id5df0b428bd7e24aa11fb9dd4c920d1019f1b389 with SHA-1 e5bd70c0c0 is in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/11401739

Change-Id: Icb375faea6dc3db7340445ed3ba09b5cef95b806
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 425440a..6040a64 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -95,6 +95,9 @@
     <!-- For Vendor Debugging in Telephony -->
     <protected-broadcast android:name="android.telephony.action.ANOMALY_REPORTED" />
 
+    <protected-broadcast android:name= "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED" />
+    <protected-broadcast android:name= "android.intent.action.ACTION_MANAGED_ROAMING_IND" />
+
     <!-- Allows granting runtime permissions to telephony related components. -->
     <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
 
@@ -238,6 +241,7 @@
             android:label="@string/emergencyDialerIconLabel"
             android:theme="@style/EmergencyDialerTheme"
             android:screenOrientation="portrait"
+            android:exported="true"
             android:resizeableActivity="false">
             <intent-filter>
                 <action android:name="com.android.phone.EmergencyDialer.DIAL" />
@@ -265,6 +269,7 @@
             android:label="@string/simContacts_title"
             android:theme="@style/SimImportTheme"
             android:screenOrientation="portrait"
+            android:exported="true"
             android:icon="@mipmap/ic_launcher_contacts">
 
             <intent-filter>
@@ -276,6 +281,7 @@
 
         <activity android:name="com.android.phone.settings.fdn.FdnList"
                 android:label="@string/fdnListLabel"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
@@ -290,6 +296,7 @@
 
         <activity android:name="GsmUmtsCallOptions"
                 android:label="@string/gsm_umts_options"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -298,6 +305,7 @@
 
         <activity android:name="CdmaCallOptions"
                 android:label="@string/cdma_options"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -307,6 +315,17 @@
         <activity android:name="GsmUmtsCallForwardOptions"
                 android:label="@string/labelCF"
                 android:configChanges="orientation|screenSize|keyboardHidden"
+                android:exported="true"
+                android:theme="@style/DialerSettingsLight">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="CdmaCallForwardOptions"
+                android:label="@string/labelCF"
+                android:configChanges="orientation|screenSize|keyboardHidden"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -316,6 +335,7 @@
         <activity android:name="GsmUmtsCallBarringOptions"
                 android:label="@string/labelCallBarring"
                 android:configChanges="orientation|screenSize|keyboardHidden"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -325,6 +345,7 @@
         <activity android:name="GsmUmtsAdditionalCallOptions"
                 android:label="@string/labelGSMMore"
                 android:configChanges="orientation|screenSize|keyboardHidden"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -334,6 +355,7 @@
         <!-- fdn setting -->
         <activity android:name="com.android.phone.settings.fdn.FdnSetting"
                 android:label="@string/fdn"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -343,6 +365,7 @@
         <!-- SIM PIN setting -->
         <activity android:name="EnableIccPinScreen"
                 android:label="@string/enable_pin"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -352,6 +375,7 @@
 
         <activity android:name="ChangeIccPinScreen"
                 android:label="@string/change_pin"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -379,6 +403,7 @@
         <activity android:name="CallFeaturesSetting"
                 android:label="@string/call_settings"
                 android:configChanges="orientation|screenSize|keyboardHidden"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
@@ -391,6 +416,7 @@
         <!-- Activation service that trigger OTASP sim provisioning -->
         <service android:name=".otasp.OtaspActivationService" android:launchMode="singleInstance"
             androidprv:systemUserOnly="true"
+            android:exported="true"
             android:permission="android.permission.MODIFY_PHONE_STATE">
             <intent-filter>
                 <action android:name="android.service.simActivation.SimActivationService" />
@@ -407,6 +433,7 @@
         <!-- "Accessibility" settings UI. Referenced by Dialer application. -->
         <activity android:name="com.android.phone.settings.AccessibilitySettingsActivity"
                 android:label="@string/accessibility_settings_activity_title"
+                android:exported="true"
                 android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -421,6 +448,7 @@
 
         <!-- service to dump telephony information -->
         <service android:name="com.android.phone.TelephonyDebugService"
+                android:exported="true"
                  android:permission="android.permission.DUMP">
             <intent-filter>
                 <action android:name="com.android.phone.TelephonyDebugService" />
@@ -430,6 +458,7 @@
         <!-- Handler for EuiccManager's public-facing intents. -->
         <activity android:name=".euicc.EuiccUiDispatcherActivity"
             android:theme="@android:style/Theme.NoDisplay"
+            android:exported="true"
             android:permission="android.permission.MODIFY_PHONE_STATE">
             <!-- Max out priority to ensure nobody else will handle these intents. -->
             <intent-filter android:priority="1000">
@@ -450,6 +479,7 @@
             EuiccController#RESOLUTION_ACTIVITY_CLASS_NAME
         -->
         <activity android:name=".euicc.EuiccResolutionUiDispatcherActivity"
+            android:exported="true"
             android:permission="android.permission.CALL_PRIVILEGED">
             <!-- Max out priority to ensure nobody else will handle these intents. -->
             <intent-filter android:priority="1000">
@@ -465,6 +495,7 @@
         -->
         <activity android:name=".euicc.EuiccPrivilegedActionUiDispatcherActivity"
                   android:theme="@android:style/Theme.NoDisplay"
+                  android:exported="true"
                   android:permission="android.permission.CALL_PRIVILEGED">
             <!-- Max out priority to ensure nobody else will handle these intents. -->
             <intent-filter android:priority="1000">
@@ -484,6 +515,7 @@
             whitelisted by the underlying eUICC service implementation (i.e. the LPA).
         -->
         <activity android:name=".euicc.EuiccPublicActionUiDispatcherActivity"
+            android:exported="true"
             android:theme="@android:style/Theme.NoDisplay">
             <!-- Max out priority to ensure nobody else will handle these intents. -->
             <intent-filter android:priority="1000">
@@ -497,6 +529,7 @@
             android:excludeFromRecents="true"
             android:label="@string/ecm_exit_dialog"
             android:launchMode="singleTop"
+            android:exported="true"
             android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG" />
@@ -509,13 +542,15 @@
         <service android:name="com.android.services.telephony.sip.SipConnectionService"
                  android:label="@string/sip_connection_service_label"
                  android:singleUser="true"
+                 android:exported="true"
                  android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
             </intent-filter>
         </service>
 
-        <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver">
+        <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.net.sip.action.SIP_INCOMING_CALL" />
             </intent-filter>
@@ -523,6 +558,7 @@
 
         <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" />
@@ -536,6 +572,7 @@
                   android:launchMode="singleTop"
                   android:configChanges="orientation|screenSize|keyboardHidden"
                   android:uiOptions="splitActionBarWhenNarrow"
+                  android:exported="true"
                   android:parentActivityName="com.android.phone.CallFeaturesSetting" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -548,7 +585,8 @@
                 android:uiOptions="splitActionBarWhenNarrow">
         </activity>
 
-        <service android:name="com.android.services.telephony.sip.components.TelephonySipService">
+        <service android:name="com.android.services.telephony.sip.components.TelephonySipService"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.net.sip.action.START_SIP" />
             </intent-filter>
@@ -565,6 +603,7 @@
 
         <activity android:name="com.android.phone.settings.PhoneAccountSettingsActivity"
             android:label="@string/phone_accounts"
+            android:exported="true"
             android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.telecom.action.CHANGE_PHONE_ACCOUNTS" />
@@ -576,6 +615,7 @@
             android:label="@string/voicemail"
             android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
             android:screenOrientation="portrait"
+            android:exported="true"
             android:theme="@style/DialerSettingsLight">
             <intent-filter >
                 <!-- DO NOT RENAME. There are existing apps which use this string. -->
@@ -593,6 +633,7 @@
                 android:singleUser="true"
                 android:name="com.android.services.telephony.TelephonyConnectionService"
                 android:label="@string/pstn_connection_service_label"
+                android:exported="true"
                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
@@ -641,6 +682,7 @@
             </intent-filter>
         </service>
         <service android:name="com.android.internal.telephony.dataconnection.CellularDataService"
+            android:exported="true"
             android:permission="android.permission.BIND_TELEPHONY_DATA_SERVICE" >
             <intent-filter>
                 <action android:name="android.telephony.data.DataService" />
@@ -650,6 +692,7 @@
         <activity
             android:name=".settings.RadioInfo"
             android:label="@string/phone_info_label"
+            android:exported="true"
             android:theme="@style/Theme.AppCompat.DayNight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -659,6 +702,7 @@
 
         <activity android:name=".settings.BandMode"
                   android:label="@string/band_mode_title"
+                  android:exported="true"
                   android:theme="@style/Theme.AppCompat.DayNight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e75dcb0..75b9d49 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,6 +7,9 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CarrierAppIntegrationTestCases"
     }
   ]
 }
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1e44c72..47652e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -273,6 +273,15 @@
     <string name="sum_cfnrc_disabled">Off</string>
     <!-- Error message displayed after failing to disable forwarding calls when the phone is unreachable -->
     <string name="disable_cfnrc_forbidden">Your carrier doesn\'t support disabling call forwarding when your phone is unreachable.</string>
+    <string name="registration_cf_forbidden">Your carrier doesn\'t support call forwarding.</string>
+
+    <!-- Cdma Call waiting settings screen, setting option name -->
+    <string name="cdma_call_waiting">Turn on call waiting?</string>
+    <string name="enable_cdma_call_waiting_setting">During a call, you\'ll be notified about incoming calls</string>
+    <string name="enable_cdma_cw">Turn on</string>
+    <string name="disable_cdma_cw">Cancel</string>
+    <string name="cdma_call_waiting_in_ims_on">CDMA Call Waiting under IMS On</string>
+    <string name="cdma_call_waiting_in_ims_off">CDMA Call Waiting under IMS Off</string>
 
     <!-- Title of the progress dialog displayed while updating Call settings -->
     <string name="updating_title">Call settings</string>
@@ -2153,5 +2162,4 @@
     <string name="carrier_provisioning">Carrier Provisioning Info</string>
     <!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
     <string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
-
 </resources>
diff --git a/res/xml/cdma_call_privacy.xml b/res/xml/cdma_call_privacy.xml
index 1aeeefe..a16a504 100644
--- a/res/xml/cdma_call_privacy.xml
+++ b/res/xml/cdma_call_privacy.xml
@@ -7,4 +7,14 @@
         android:title="@string/voice_privacy"
         android:persistent="false"
         android:summary="@string/voice_privacy_summary"/>
+
+    <PreferenceScreen
+        android:key="call_forwarding_key"
+        android:title="@string/labelCF"
+        android:persistent="false" />
+
+    <com.android.phone.CdmaCallWaitingPreference
+        android:key="call_waiting_key"
+        android:title="@string/labelCW"
+        android:persistent="false" />
 </PreferenceScreen>
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 54acaad..ef83ead 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -397,14 +397,10 @@
             } else {
                 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                     prefSet.removePreference(fdnButton);
-
-                    if (!carrierConfig.getBoolean(
-                            CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
-                        addPreferencesFromResource(R.xml.cdma_call_privacy);
-                        CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
-                                (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
-                        buttonVoicePrivacy.setPhone(mPhone);
-                    }
+                    addPreferencesFromResource(R.xml.cdma_call_privacy);
+                    CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+                            (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+                    buttonVoicePrivacy.setPhone(mPhone);
                 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
                     if (mPhone.getIccCard() == null || !mPhone.getIccCard().getIccFdnAvailable()) {
                         prefSet.removePreference(fdnButton);
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index e8cf0d1..8e0b685 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -122,7 +122,27 @@
         Log.d(LOG_TAG, "mButtonClicked=" + mButtonClicked + ", positiveResult=" + positiveResult);
         // Ignore this event if the user clicked the cancel button, or if the dialog is dismissed
         // without any button being pressed (back button press or click event outside the dialog).
-        if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+        if (isUnknownStatus() && this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+            int action = (mButtonClicked == DialogInterface.BUTTON_POSITIVE) ?
+                CommandsInterface.CF_ACTION_REGISTRATION :
+                CommandsInterface.CF_ACTION_DISABLE;
+            final String number = (action == CommandsInterface.CF_ACTION_DISABLE) ?
+                    "" : getPhoneNumber();
+
+            Log.d(LOG_TAG, "reason=" + reason + ", action=" + action + ", number=" + number);
+
+            // Display no forwarding number while we're waiting for confirmation.
+            setSummaryOff("");
+
+            mPhone.setCallForwardingOption(action,
+                    reason,
+                    number,
+                    mServiceClass,
+                    0,
+                    mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
+                        action,
+                        MyHandler.MESSAGE_SET_CF));
+        } else if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
             int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
                     CommandsInterface.CF_ACTION_REGISTRATION :
                     CommandsInterface.CF_ACTION_DISABLE;
@@ -194,6 +214,7 @@
             Log.i(LOG_TAG, "handleGetCFResponse: Overridding CF number");
         }
 
+        setUnknownStatus(callForwardInfo.status == CommandsInterface.SS_STATUS_UNKNOWN);
         setToggled(callForwardInfo.status == 1);
         boolean displayVoicemailNumber = false;
         if (TextUtils.isEmpty(callForwardInfo.number)) {
@@ -343,6 +364,7 @@
             AsyncResult ar = (AsyncResult) msg.obj;
 
             callForwardInfo = null;
+            boolean summaryOff = false;
             if (ar.exception != null) {
                 Log.d(LOG_TAG, "handleGetCFResponse: ar.exception=" + ar.exception);
                 if (ar.exception instanceof CommandException) {
@@ -372,6 +394,8 @@
                             CallForwardInfo info = cfInfoArray[i];
                             handleCallForwardResult(info);
 
+                            summaryOff = (info.status == CommandsInterface.SS_STATUS_UNKNOWN);
+
                             if (ar.userObj instanceof Throwable) {
                                 Log.d(LOG_TAG, "Skipped duplicated error dialog");
                                 continue;
@@ -379,8 +403,7 @@
 
                             // Show an alert if we got a success response but
                             // with unexpected values.
-                            // Currently only handle the fail-to-disable case
-                            // since we haven't observed fail-to-enable.
+                            // Handle the fail-to-disable case.
                             if (msg.arg2 == MESSAGE_SET_CF &&
                                     msg.arg1 == CommandsInterface.CF_ACTION_DISABLE &&
                                     info.status == 1) {
@@ -404,7 +427,21 @@
                                 }
                                 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                                 builder.setNeutralButton(R.string.close_dialog, null);
-                                builder.setTitle(getContext().getText(R.string.error_updating_title));
+                                builder.setTitle(getContext()
+                                        .getText(R.string.error_updating_title));
+                                builder.setMessage(s);
+                                builder.setCancelable(true);
+                                builder.create().show();
+                            } else if (msg.arg2 == MESSAGE_SET_CF &&
+                                    msg.arg1 == CommandsInterface.CF_ACTION_REGISTRATION &&
+                                    info.status == 0) {
+                                // Handle the fail-to-enable case.
+                                CharSequence s = getContext()
+                                    .getText(R.string.registration_cf_forbidden);
+                                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+                                builder.setNeutralButton(R.string.close_dialog, null);
+                                builder.setTitle(getContext()
+                                        .getText(R.string.error_updating_title));
                                 builder.setMessage(s);
                                 builder.setCancelable(true);
                                 builder.create().show();
@@ -417,7 +454,15 @@
             // Now whether or not we got a new number, reset our enabled
             // summary text since it may have been replaced by an empty
             // placeholder.
-            updateSummaryText();
+            // for CDMA, doesn't display summary.
+            if (summaryOff) {
+                setSummaryOff("");
+            } else {
+                // Now whether or not we got a new number, reset our enabled
+                // summary text since it may have been replaced by an empty
+                // placeholder.
+                updateSummaryText();
+            }
         }
 
         private void handleSetCFResponse(Message msg) {
@@ -426,6 +471,16 @@
                 Log.d(LOG_TAG, "handleSetCFResponse: ar.exception=" + ar.exception);
                 // setEnabled(false);
             }
+
+            if (ar.result != null) {
+                int arr = (int)ar.result;
+                if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+                    Log.d(LOG_TAG, "handleSetCFResponse: no need to re get in CDMA");
+                    mTcpListener.onFinished(CallForwardEditPreference.this, false);
+                    return;
+                }
+            }
+
             Log.d(LOG_TAG, "handleSetCFResponse: re get");
             if (!mCallForwardByUssd) {
                 mPhone.getCallForwardingOption(reason, mServiceClass,
diff --git a/src/com/android/phone/CdmaCallForwardOptions.java b/src/com/android/phone/CdmaCallForwardOptions.java
new file mode 100644
index 0000000..a8d2e93
--- /dev/null
+++ b/src/com/android/phone/CdmaCallForwardOptions.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 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.app.ActionBar;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+import android.view.MenuItem;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import java.util.ArrayList;
+
+public class CdmaCallForwardOptions extends TimeConsumingPreferenceActivity {
+    private static final String LOG_TAG = "CdmaCallForwardOptions";
+
+    private static final String NUM_PROJECTION[] = {
+        android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER
+    };
+
+    private static final String BUTTON_CFU_KEY   = "button_cfu_key";
+    private static final String BUTTON_CFB_KEY   = "button_cfb_key";
+    private static final String BUTTON_CFNRY_KEY = "button_cfnry_key";
+    private static final String BUTTON_CFNRC_KEY = "button_cfnrc_key";
+
+    private static final String KEY_TOGGLE = "toggle";
+    private static final String KEY_STATUS = "status";
+    private static final String KEY_NUMBER = "number";
+    private static final String KEY_ENABLE = "enable";
+
+    private CallForwardEditPreference mButtonCFU;
+    private CallForwardEditPreference mButtonCFB;
+    private CallForwardEditPreference mButtonCFNRy;
+    private CallForwardEditPreference mButtonCFNRc;
+
+    private final ArrayList<CallForwardEditPreference> mPreferences =
+            new ArrayList<CallForwardEditPreference> ();
+    private int mInitIndex= 0;
+
+    private boolean mFirstResume;
+    private Bundle mIcicle;
+    private Phone mPhone;
+    private SubscriptionInfoHelper mSubscriptionInfoHelper;
+    private boolean mReplaceInvalidCFNumbers;
+    private boolean mCallForwardByUssd;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.callforward_options);
+
+        mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent());
+        mSubscriptionInfoHelper.setActionBarTitle(
+                getActionBar(), getResources(), R.string.call_forwarding_settings_with_label);
+        mPhone = mSubscriptionInfoHelper.getPhone();
+
+        PersistableBundle b = null;
+        boolean supportCFNRc = true;
+        if (mSubscriptionInfoHelper.hasSubId()) {
+            b = PhoneGlobals.getInstance().getCarrierConfigForSubId(
+                    mSubscriptionInfoHelper.getSubId());
+        } else {
+            b = PhoneGlobals.getInstance().getCarrierConfig();
+        }
+        if (b != null) {
+            mReplaceInvalidCFNumbers = b.getBoolean(
+                    CarrierConfigManager.KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL);
+            mCallForwardByUssd = b.getBoolean(
+                    CarrierConfigManager.KEY_USE_CALL_FORWARDING_USSD_BOOL);
+            supportCFNRc = b.getBoolean(
+                    CarrierConfigManager.KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL);
+        }
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+        mButtonCFU = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFU_KEY);
+        mButtonCFB = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFB_KEY);
+        mButtonCFNRy = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRY_KEY);
+        mButtonCFNRc = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRC_KEY);
+
+        mButtonCFU.setParentActivity(this, mButtonCFU.reason);
+        mButtonCFB.setParentActivity(this, mButtonCFB.reason);
+        mButtonCFNRy.setParentActivity(this, mButtonCFNRy.reason);
+        mButtonCFNRc.setParentActivity(this, mButtonCFNRc.reason);
+
+        mPreferences.add(mButtonCFU);
+        mPreferences.add(mButtonCFB);
+        mPreferences.add(mButtonCFNRy);
+
+        if (supportCFNRc) {
+            mPreferences.add(mButtonCFNRc);
+        } else {
+            // When CFNRc is not supported, mButtonCFNRc is grayed out from the menu.
+            // Default state for the preferences in this PreferenceScreen is disabled.
+            // Only preferences listed in the ArrayList mPreferences will be enabled.
+            // By not adding mButtonCFNRc to mPreferences it will be kept disabled.
+            Log.d(LOG_TAG, "onCreate: CFNRc is not supported, grey out the item.");
+        }
+
+        if (mCallForwardByUssd) {
+            //the call forwarding ussd command's behavior is similar to the call forwarding when
+            //unanswered,so only display the call forwarding when unanswered item.
+            prefSet.removePreference(mButtonCFU);
+            prefSet.removePreference(mButtonCFB);
+            prefSet.removePreference(mButtonCFNRc);
+            mPreferences.remove(mButtonCFU);
+            mPreferences.remove(mButtonCFB);
+            mPreferences.remove(mButtonCFNRc);
+            mButtonCFNRy.setDependency(null);
+        }
+
+        // we wait to do the initialization until onResume so that the
+        // TimeConsumingPreferenceActivity dialog can display as it
+        // relies on onResume / onPause to maintain its foreground state.
+
+        mFirstResume = true;
+        mIcicle = icicle;
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (mFirstResume) {
+            if (mIcicle == null) {
+                Log.d(LOG_TAG, "start to init ");
+                CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+                pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+                pref.startCallForwardOptionsQuery();
+
+            } else {
+                mInitIndex = mPreferences.size();
+
+                for (CallForwardEditPreference pref : mPreferences) {
+                    Bundle bundle = mIcicle.getParcelable(pref.getKey());
+                    pref.setToggled(bundle.getBoolean(KEY_TOGGLE));
+                    pref.setEnabled(bundle.getBoolean(KEY_ENABLE));
+                    CallForwardInfo cf = new CallForwardInfo();
+                    cf.number = bundle.getString(KEY_NUMBER);
+                    cf.status = bundle.getInt(KEY_STATUS);
+                    pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+                    pref.restoreCallForwardInfo(cf);
+                }
+            }
+            mFirstResume = false;
+            mIcicle = null;
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        for (CallForwardEditPreference pref : mPreferences) {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(KEY_TOGGLE, pref.isToggled());
+            bundle.putBoolean(KEY_ENABLE, pref.isEnabled());
+            if (pref.callForwardInfo != null) {
+                bundle.putString(KEY_NUMBER, pref.callForwardInfo.number);
+                bundle.putInt(KEY_STATUS, pref.callForwardInfo.status);
+            }
+            outState.putParcelable(pref.getKey(), bundle);
+        }
+    }
+
+    @Override
+    public void onFinished(Preference preference, boolean reading) {
+        if (mInitIndex < mPreferences.size()-1 && !isFinishing()) {
+            mInitIndex++;
+            CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+            pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+            pref.startCallForwardOptionsQuery();
+        }
+
+        super.onFinished(preference, reading);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(LOG_TAG, "onActivityResult: done");
+        if (resultCode != RESULT_OK) {
+            Log.d(LOG_TAG, "onActivityResult: contact picker result not OK.");
+            return;
+        }
+        Cursor cursor = null;
+        try {
+            cursor = getContentResolver().query(data.getData(),
+                NUM_PROJECTION, null, null, null);
+            if ((cursor == null) || (!cursor.moveToFirst())) {
+                Log.d(LOG_TAG, "onActivityResult: bad contact data, no results found.");
+                return;
+            }
+
+            switch (requestCode) {
+                case CommandsInterface.CF_REASON_UNCONDITIONAL:
+                    mButtonCFU.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_BUSY:
+                    mButtonCFB.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_NO_REPLY:
+                    mButtonCFNRy.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_NOT_REACHABLE:
+                    mButtonCFNRc.onPickActivityResult(cursor.getString(0));
+                    break;
+                default:
+                    // TODO: may need exception here.
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            CallFeaturesSetting.goUpToTopLevelSetting(this, mSubscriptionInfoHelper);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 8513664..2e310aa 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -19,18 +19,19 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.preference.Preference;
-import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
 import android.telephony.CarrierConfigManager;
 import android.view.MenuItem;
 
 import com.android.internal.telephony.PhoneConstants;
 
-public class CdmaCallOptions extends PreferenceActivity {
+public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
     private static final String LOG_TAG = "CdmaCallOptions";
     private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
 
     private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
+    private static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+    private static final String CALL_WAITING_KEY = "call_waiting_key";
     private CdmaVoicePrivacySwitchPreference mButtonVoicePrivacy;
 
     @Override
@@ -55,8 +56,15 @@
         if (subInfoHelper.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
                 || carrierConfig.getBoolean(CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
             // disable the entire screen
-            getPreferenceScreen().setEnabled(false);
+            mButtonVoicePrivacy.setEnabled(false);
         }
+
+        Preference callForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
+        callForwardingPref.setIntent(subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+
+        CdmaCallWaitingPreference callWaitingPref = (CdmaCallWaitingPreference)getPreferenceScreen()
+                                                     .findPreference(CALL_WAITING_KEY);
+        callWaitingPref.init(this, subInfoHelper.getPhone());
     }
 
     @Override
@@ -76,5 +84,4 @@
         }
         return false;
     }
-
 }
diff --git a/src/com/android/phone/CdmaCallWaitingPreference.java b/src/com/android/phone/CdmaCallWaitingPreference.java
new file mode 100644
index 0000000..4cda7ba
--- /dev/null
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 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 com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.util.AttributeSet;
+import android.util.Log;
+
+public class CdmaCallWaitingPreference extends Preference {
+    private static final String LOG_TAG = "CdmaCallWaitingPreference";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private int mButtonClicked;
+    private Context mContext;
+    private Phone mPhone;
+    private SubscriptionInfoHelper mSubscriptionInfoHelper;
+    private TimeConsumingPreferenceListener mTcpListener;
+    private MyHandler mHandler = new MyHandler();
+
+    public CdmaCallWaitingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContext = context;
+    }
+
+    public CdmaCallWaitingPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.preferenceStyle);
+    }
+
+    public CdmaCallWaitingPreference(Context context) {
+        this(context, null);
+    }
+
+    public void init(TimeConsumingPreferenceListener listener, Phone phone) {
+        mPhone = phone;
+        mTcpListener = listener;
+        Log.d(LOG_TAG, "phone id= " + mPhone.getPhoneId());
+        mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
+                    MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+        if (mTcpListener != null) {
+            mTcpListener.onStarted(this, true);
+        }
+    }
+
+    @Override
+    public void onClick() {
+        super.onClick();
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+        builder.setTitle(mContext.getText(R.string.cdma_call_waiting));
+        builder.setMessage(mContext.getText(R.string.enable_cdma_call_waiting_setting));
+        builder.setPositiveButton(R.string.enable_cdma_cw, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                mPhone.setCallWaiting(true,
+                        mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+                if (mTcpListener != null) {
+                    mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+                }
+            }
+        });
+        builder.setNegativeButton(R.string.disable_cdma_cw, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                mPhone.setCallWaiting(false,
+                        mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+                if (mTcpListener != null) {
+                    mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+                }
+            }
+        });
+        builder.create().show();
+    }
+
+    private class MyHandler extends Handler {
+        static final int MESSAGE_GET_CALL_WAITING = 0;
+        static final int MESSAGE_SET_CALL_WAITING = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_CALL_WAITING:
+                    handleGetCallWaitingResponse(msg);
+                    break;
+                case MESSAGE_SET_CALL_WAITING:
+                    handleSetCallWaitingResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetCallWaitingResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (mTcpListener != null) {
+                if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+                    mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+                } else {
+                    mTcpListener.onFinished(CdmaCallWaitingPreference.this, true);
+                }
+            }
+
+            if (ar.exception instanceof CommandException) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: CommandException=" +
+                            ar.exception);
+                }
+                if (mTcpListener != null) {
+                    mTcpListener.onException(CdmaCallWaitingPreference.this,
+                                             (CommandException)ar.exception);
+                }
+            } else if (ar.userObj instanceof Throwable || ar.exception != null) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception" + ar.exception);
+                }
+                if (mTcpListener != null) {
+                    mTcpListener.onError(CdmaCallWaitingPreference.this,
+                                         TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+                }
+            } else {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
+                }
+                int[] cwArray = (int[])ar.result;
+                if (cwArray == null) {
+                    if (mTcpListener != null) {
+                        mTcpListener.onError(CdmaCallWaitingPreference.this,
+                                             TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+                    }
+                    return;
+                }
+
+                try {
+                    if (cwArray[0] == CommandsInterface.SS_STATUS_UNKNOWN) {
+                        setSummary("");
+                    } else if(cwArray[0] == 1) {
+                        setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_on));
+                    } else if(cwArray[0] == 0) {
+                        setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_off));
+                    }
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    setSummary("");
+                    Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
+                            + e.getMessage());
+                }
+            }
+        }
+
+        private void handleSetCallWaitingResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
+                }
+            }
+
+            if (ar.result != null) {
+                int arr = (int)ar.result;
+                if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+                    Log.d(LOG_TAG, "handleSetCallWaitingResponse: no need to re get in CDMA");
+                    mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+                    return;
+                }
+            }
+
+            if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
+            mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
+                        MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+        }
+    }
+}
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
index 74b8a45..505c284 100644
--- a/src/com/android/phone/EditPhoneNumberPreference.java
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.phone;
 
+import static android.view.View.LAYOUT_DIRECTION_LOCALE;
+import static android.view.View.TEXT_DIRECTION_LOCALE;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -37,6 +40,8 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import com.android.internal.telephony.CommandsInterface;
+
 public class EditPhoneNumberPreference extends EditTextPreference {
 
     //allowed modes for this preference.
@@ -90,6 +95,7 @@
     private String mPhoneNumber;
     private boolean mChecked;
 
+    private boolean mIsUnknownStatus;
 
     /**
      * Interface for the dialog closed listener, related to
@@ -209,7 +215,9 @@
                 }
             }
             editText.setText(BidiFormatter.getInstance().unicodeWrap(
-                    mPhoneNumber, TextDirectionHeuristics.LTR));
+                    mPhoneNumber, TextDirectionHeuristics.LOCALE));
+            editText.setTextDirection(TEXT_DIRECTION_LOCALE);
+            editText.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
             editText.setMovementMethod(ArrowKeyMovementMethod.getInstance());
             editText.setKeyListener(DialerKeyListener.getInstance());
             editText.setOnFocusChangeListener(mDialogFocusChangeListener);
@@ -254,7 +262,13 @@
         // displayed, since there is no need to hide the edittext
         // field anymore.
         if (mConfirmationMode == CM_ACTIVATION) {
-            if (mChecked) {
+            if (mIsUnknownStatus) {
+                builder.setPositiveButton(mEnableText, this);
+                builder.setNeutralButton(mDisableText, this);
+                if (mPrefId == CommandsInterface.CF_REASON_ALL) {
+                    builder.setPositiveButton(null, null);
+                }
+            } else if (mChecked) {
                 builder.setPositiveButton(mChangeNumberText, this);
                 builder.setNeutralButton(mDisableText, this);
             } else {
@@ -310,7 +324,8 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         // The neutral button (button3) is always the toggle.
-        if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)) {
+        if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)
+             && !mIsUnknownStatus) {
             //flip the toggle if we are in the correct mode.
             setToggled(!isToggled());
         }
@@ -499,4 +514,12 @@
     public void showPhoneNumberDialog() {
         showDialog(null);
     }
+
+    public void setUnknownStatus(boolean isUnknown) {
+        mIsUnknownStatus = isUnknown;
+    }
+
+    public boolean isUnknownStatus() {
+        return mIsUnknownStatus;
+    }
 }
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index dcae24b..f5f24d3 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -28,6 +28,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
@@ -196,6 +197,40 @@
         }
     }
 
+    @Override
+    public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+        enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+                    UserCapabilityExchangeImpl.class);
+            if (uce == null) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "This subscription does not support UCE.");
+            }
+            uce.registerPublishStateCallback(c);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+        enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+                    UserCapabilityExchangeImpl.class);
+            if (uce == null) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "This subscription does not support UCE.");
+            }
+            uce.unregisterUcePublishStateCallback(c);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Query for the capability of an IMS RCS service
      *
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7ed7aec..d3bb176 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1881,6 +1881,11 @@
                                                 ((CommandException)(ar.exception)).getCommandError()
                                                 == CommandException.Error.PASSWORD_INCORRECT) {
                                             mResult = PhoneConstants.PIN_PASSWORD_INCORRECT;
+                                        } //When UiccCardApp dispose,handle message and return exception
+                                          else if (ar.exception instanceof CommandException &&
+                                                ((CommandException) (ar.exception)).getCommandError()
+                                                        == CommandException.Error.ABORTED) {
+                                            mResult = PhoneConstants.PIN_OPERATION_ABORTED;
                                         } else {
                                             mResult = PhoneConstants.PIN_GENERAL_FAILURE;
                                         }
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 31b7a9e..d0951e4 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -38,6 +38,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -465,12 +466,12 @@
 
         mQueuedWork = new ThreadPoolExecutor(1, 1, RUNNABLE_TIMEOUT_MS, TimeUnit.MICROSECONDS,
                 new LinkedBlockingDeque<Runnable>());
-        mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
         mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
         mPhone = PhoneFactory.getDefaultPhone();
+        mTelephonyManager = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
+                .createForSubscriptionId(mPhone.getSubId());
 
-        mImsManager = ImsManager.getInstance(getApplicationContext(),
-                SubscriptionManager.getDefaultVoicePhoneId());
+        mImsManager = ImsManager.getInstance(getApplicationContext(), mPhone.getPhoneId());
 
         sPhoneIndexLabels = getPhoneIndexLabels(mTelephonyManager);
 
@@ -1465,9 +1466,9 @@
     };
 
     private boolean isImsVolteProvisioned() {
-        if (mPhone != null && mImsManager != null) {
-            return mImsManager.isVolteEnabledByPlatform(mPhone.getContext())
-                && mImsManager.isVolteProvisionedOnDevice(mPhone.getContext());
+        if (mImsManager != null) {
+            return mImsManager.isVolteEnabledByPlatform()
+                && mImsManager.isVolteProvisionedOnDevice();
         }
         return false;
     }
@@ -1480,9 +1481,9 @@
     };
 
     private boolean isImsVtProvisioned() {
-        if (mPhone != null && mImsManager != null) {
-            return mImsManager.isVtEnabledByPlatform(mPhone.getContext())
-                && mImsManager.isVtProvisionedOnDevice(mPhone.getContext());
+        if (mImsManager != null) {
+            return mImsManager.isVtEnabledByPlatform()
+                && mImsManager.isVtProvisionedOnDevice();
         }
         return false;
     }
@@ -1495,9 +1496,9 @@
     };
 
     private boolean isImsWfcProvisioned() {
-        if (mPhone != null && mImsManager != null) {
-            return mImsManager.isWfcEnabledByPlatform(mPhone.getContext())
-                && mImsManager.isWfcProvisionedOnDevice(mPhone.getContext());
+        if (mImsManager != null) {
+            return mImsManager.isWfcEnabledByPlatform()
+                && mImsManager.isWfcProvisionedOnDevice();
         }
         return false;
     }
@@ -1539,13 +1540,14 @@
         return provisioned;
     }
 
-    private static boolean isEabEnabledByPlatform(Context context) {
-        if (context != null) {
+    private boolean isEabEnabledByPlatform() {
+        if (mPhone != null) {
             CarrierConfigManager configManager = (CarrierConfigManager)
-                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configManager != null && configManager.getConfig().getBoolean(
-                        CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)) {
-                return true;
+                    mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+            if (b != null) {
+                return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL,
+                        false);
             }
         }
         return false;
@@ -1562,25 +1564,25 @@
         mImsVolteProvisionedSwitch.setChecked(isImsVolteProvisioned());
         mImsVolteProvisionedSwitch.setOnCheckedChangeListener(mImsVolteCheckedChangeListener);
         mImsVolteProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isVolteEnabledByPlatform(mPhone.getContext()));
+                && mImsManager.isVolteEnabledByPlatform());
 
         mImsVtProvisionedSwitch.setOnCheckedChangeListener(null);
         mImsVtProvisionedSwitch.setChecked(isImsVtProvisioned());
         mImsVtProvisionedSwitch.setOnCheckedChangeListener(mImsVtCheckedChangeListener);
         mImsVtProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isVtEnabledByPlatform(mPhone.getContext()));
+                && mImsManager.isVtEnabledByPlatform());
 
         mImsWfcProvisionedSwitch.setOnCheckedChangeListener(null);
         mImsWfcProvisionedSwitch.setChecked(isImsWfcProvisioned());
         mImsWfcProvisionedSwitch.setOnCheckedChangeListener(mImsWfcCheckedChangeListener);
         mImsWfcProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isWfcEnabledByPlatform(mPhone.getContext()));
+                && mImsManager.isWfcEnabledByPlatform());
 
         mEabProvisionedSwitch.setOnCheckedChangeListener(null);
         mEabProvisionedSwitch.setChecked(isEabProvisioned());
         mEabProvisionedSwitch.setOnCheckedChangeListener(mEabCheckedChangeListener);
         mEabProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && isEabEnabledByPlatform(mPhone.getContext()));
+                && isEabEnabledByPlatform());
     }
 
     OnClickListener mDnsCheckButtonHandler = new OnClickListener() {
diff --git a/src/com/android/phone/settings/SuppServicesUiUtil.java b/src/com/android/phone/settings/SuppServicesUiUtil.java
index 4e9841f..4f1a79f 100644
--- a/src/com/android/phone/settings/SuppServicesUiUtil.java
+++ b/src/com/android/phone/settings/SuppServicesUiUtil.java
@@ -84,7 +84,7 @@
                 .create();
     }
 
-    private static String makeMessage(Context context, String preferenceKey, Phone phone) {
+    public static String makeMessage(Context context, String preferenceKey, Phone phone) {
         String message = "";
         int simSlot = (phone.getPhoneId() == 0) ? 1 : 2;
         String suppServiceName = getSuppServiceName(context, preferenceKey);
diff --git a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
index 92baa97..8b17cfb 100644
--- a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
@@ -64,7 +64,8 @@
 
         resolveIntent();
 
-        authenticatePin2();
+        // Starts PIN2 authentication only for the first time.
+        if (icicle == null) authenticatePin2();
 
         getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
         setContentView(R.layout.delete_fdn_contact_screen);
diff --git a/src/com/android/services/telephony/MmiCodeUtil.java b/src/com/android/services/telephony/MmiCodeUtil.java
new file mode 100644
index 0000000..d208ec3
--- /dev/null
+++ b/src/com/android/services/telephony/MmiCodeUtil.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 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;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class MmiCodeUtil {
+    //***** Constants
+
+    // Supp Service codes from TS 22.030 Annex B
+
+    //Called line presentation
+    static final String SC_CLIP    = "30";
+    static final String SC_CLIR    = "31";
+
+    // Call Forwarding
+    static final String SC_CFU     = "21";
+    static final String SC_CFB     = "67";
+    static final String SC_CFNRy   = "61";
+    static final String SC_CFNR    = "62";
+
+    static final String SC_CF_All = "002";
+    static final String SC_CF_All_Conditional = "004";
+
+    // Call Waiting
+    static final String SC_WAIT     = "43";
+
+    // Call Barring
+    static final String SC_BAOC         = "33";
+    static final String SC_BAOIC        = "331";
+    static final String SC_BAOICxH      = "332";
+    static final String SC_BAIC         = "35";
+    static final String SC_BAICr        = "351";
+
+    static final String SC_BA_ALL       = "330";
+    static final String SC_BA_MO        = "333";
+    static final String SC_BA_MT        = "353";
+
+    // Supp Service Password registration
+    static final String SC_PWD          = "03";
+
+    // PIN/PIN2/PUK/PUK2
+    static final String SC_PIN          = "04";
+    static final String SC_PIN2         = "042";
+    static final String SC_PUK          = "05";
+    static final String SC_PUK2         = "052";
+
+    // See TS 22.030 6.5.2 "Structure of the MMI"
+
+    static Pattern sPatternSuppService = Pattern.compile(
+        "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+/*       1  2                    3          4  5       6   7         8    9     10  11             12
+
+         1 = Full string up to and including #
+         2 = action (activation/interrogation/registration/erasure)
+         3 = service code
+         5 = SIA
+         7 = SIB
+         9 = SIC
+         10 = dialing number
+*/
+
+    static final int MATCH_GROUP_SERVICE_CODE = 3;
+
+    public static final String BUTTON_CLIR_KEY  = "button_clir_key";
+    public static final String BUTTON_CW_KEY    = "button_cw_key";
+    public static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+    public static final String CALL_BARRING_KEY = "call_barring_key";
+
+    //***** Public Class methods
+    public static String getMmiServiceCode(String dialString) {
+        Matcher m;
+        String ret = null;
+
+        m = sPatternSuppService.matcher(dialString);
+
+        if (m.matches()) {
+            ret = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+        }
+
+        return ret;
+    }
+
+    private static String makeEmptyNull(String s) {
+        if (s != null && s.length() == 0) return null;
+
+        return s;
+    }
+
+    static boolean isServiceCodeCallForwarding(String sc) {
+        return sc != null &&
+                (sc.equals(SC_CFU)
+                || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+                || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+                || sc.equals(SC_CF_All_Conditional));
+    }
+
+    static boolean isServiceCodeCallBarring(String sc) {
+        return sc != null &&
+            (sc.equals(SC_BAOC)
+             || sc.equals(SC_BAOIC) || sc.equals(SC_BAOICxH)
+             || sc.equals(SC_BAIC) || sc.equals(SC_BAICr)
+             || sc.equals(SC_BA_ALL) || sc.equals(SC_BA_MO)
+             || sc.equals(SC_BA_MT));
+    }
+
+    static boolean isPinPukCommand(String sc) {
+        return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
+                              || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
+     }
+
+    public static String getSuppServiceKey(String dialString) {
+        String sc = getMmiServiceCode(dialString);
+        if (sc != null && sc.equals(SC_CLIP)) {
+            return "";
+        } else if (sc != null && sc.equals(SC_CLIR)) {
+            return BUTTON_CLIR_KEY;
+        } else if (isServiceCodeCallForwarding(sc)) {
+            return CALL_FORWARDING_KEY;
+        } else if (isServiceCodeCallBarring(sc)) {
+            return CALL_BARRING_KEY;
+        } else if (sc != null && sc.equals(SC_PWD)) {
+            return "";
+        } else if (sc != null && sc.equals(SC_WAIT)) {
+            return BUTTON_CW_KEY;
+        } else if (isPinPukCommand(sc)) {
+            return "";
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index b4dd050..983351f 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -117,6 +117,9 @@
     private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
     private static final int MSG_REJECT = 21;
 
+    private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
+    private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
+
     private List<Uri> mParticipants;
     private boolean mIsAdhocConferenceCall;
 
@@ -1295,6 +1298,9 @@
             if (isShowingOriginalDialString()
                     && mOriginalConnection.getOrigDialString() != null) {
                 address = getAddressFromNumber(mOriginalConnection.getOrigDialString());
+            } else if (isNeededToFormatIncomingNumberForJp()) {
+                address = getAddressFromNumber(
+                        formatIncomingNumberForJp(mOriginalConnection.getAddress()));
             } else {
                 address = getAddressFromNumber(mOriginalConnection.getAddress());
             }
@@ -3201,4 +3207,29 @@
             listener.onStatusHintsChanged(this, statusHints);
         }
     }
+
+    /**
+     * Whether the incoming call number should be formatted to national number for Japan.
+     * @return {@code true} should be convert to the national format, {@code false} otherwise.
+     */
+    private boolean isNeededToFormatIncomingNumberForJp() {
+        if (mOriginalConnection.isIncoming()
+                && !TextUtils.isEmpty(mOriginalConnection.getAddress())
+                && mOriginalConnection.getAddress().startsWith(JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN)) {
+            PersistableBundle b = getCarrierConfig();
+            return b != null && b.getBoolean(
+                    CarrierConfigManager.KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL);
+        }
+        return false;
+    }
+
+    /**
+     * Format the incoming call number to national number for Japan.
+     * @param number
+     * @return the formatted phone number (e.g, "+819012345678" -> "09012345678")
+     */
+    private String formatIncomingNumberForJp(String number) {
+        return PhoneNumberUtils.stripSeparators(
+                PhoneNumberUtils.formatNumber(number, JAPAN_ISO_COUNTRY_CODE));
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 356407a..ba3f0cc 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -17,10 +17,13 @@
 package com.android.services.telephony;
 
 import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
@@ -46,6 +49,7 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Call;
@@ -65,6 +69,7 @@
 import com.android.phone.MMIDialogActivity;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.phone.settings.SuppServicesUiUtil;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -1655,6 +1660,12 @@
                 ? connection.getAddress().getSchemeSpecificPart()
                 : "";
 
+        if (showDataDialog(phone, number)) {
+            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+                        android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
+            return;
+        }
+
         com.android.internal.telephony.Connection originalConnection = null;
         try {
             if (phone != null) {
@@ -1713,7 +1724,8 @@
         if (originalConnection == null) {
             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
             // On GSM phones, null connection means that we dialed an MMI code
-            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
+                phone.isUtEnabled()) {
                 Log.d(this, "dialed MMI code");
                 int subId = phone.getSubId();
                 Log.d(this, "subId: "+subId);
@@ -2358,6 +2370,78 @@
         }
     }
 
+    private boolean showDataDialog(Phone phone, String number) {
+        boolean ret = false;
+        final Context context = getApplicationContext();
+        String suppKey = MmiCodeUtil.getSuppServiceKey(number);
+        if (suppKey != null) {
+            boolean clirOverUtPrecautions = false;
+            boolean cfOverUtPrecautions = false;
+            boolean cbOverUtPrecautions = false;
+            boolean cwOverUtPrecautions = false;
+
+            CarrierConfigManager cfgManager = (CarrierConfigManager)
+                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            if (cfgManager != null) {
+                clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+                    .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL);
+                cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+                    .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL);
+                cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+                    .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL);
+                cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+                    .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL);
+            }
+
+            boolean isSsOverUtPrecautions = SuppServicesUiUtil
+                .isSsOverUtPrecautions(context, phone);
+            if (isSsOverUtPrecautions) {
+                boolean showDialog = false;
+                if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) {
+                    showDialog = true;
+                } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) {
+                    showDialog = true;
+                } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) {
+                    showDialog = true;
+                } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) {
+                    showDialog = true;
+                }
+
+                if (showDialog) {
+                    Log.d(this, "Creating UT Data enable dialog");
+                    String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
+                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
+                    DialogInterface.OnClickListener networkSettingsClickListener =
+                            new Dialog.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialog, int which) {
+                                    Intent intent = new Intent(Intent.ACTION_MAIN);
+                                    ComponentName mobileNetworkSettingsComponent
+                                        = new ComponentName(
+                                                context.getString(
+                                                    R.string.mobile_network_settings_package),
+                                                context.getString(
+                                                    R.string.mobile_network_settings_class));
+                                    intent.setComponent(mobileNetworkSettingsComponent);
+                                    context.startActivity(intent);
+                                }
+                            };
+                    Dialog dialog = builder.setMessage(message)
+                        .setNeutralButton(context.getResources().getString(
+                                R.string.settings_label),
+                                networkSettingsClickListener)
+                        .setPositiveButton(context.getResources().getString(
+                                R.string.supp_service_over_ut_precautions_dialog_dismiss), null)
+                        .create();
+                    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                    dialog.show();
+                    ret = true;
+                }
+            }
+        }
+        return ret;
+    }
+
     /**
      * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
      * changes to the conference.  Should be used instead of {@link #addConference(Conference)}.
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 5094c57..fcfe312 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -20,9 +20,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -131,13 +129,14 @@
                     try {
                         // May throw ImsException if for some reason the connection to the
                         // ImsService is gone.
+                        updateConnectionStatus(manager);
                         setupConnectionToService(manager);
                     } catch (ImsException e) {
+                        updateConnectionStatus(null /*manager*/);
                         // Use deprecated Exception for compatibility.
                         throw new com.android.ims.ImsException(e.getMessage(),
                                 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
                     }
-                    updateConnectionStatus(manager);
                 }
 
                 @Override
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index c85e9a9..69d8f82 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -274,6 +274,7 @@
         synchronized (mLock) {
             for (int i = 0; i < mNumSlots; i++) {
                 RcsFeatureController f = mFeatureControllers.get(i);
+                if (f == null) continue;
                 pw.increaseIndent();
                 f.dump(fd, printWriter, args);
                 pw.decreaseIndent();
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index ac8f9bf..ee0c5be 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -16,16 +16,45 @@
 
 package com.android.services.telephony.rcs;
 
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.telecom.TelecomManager;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
 import android.util.Log;
 
 import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
 import com.android.ims.ResultCode;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.R;
 import com.android.service.ims.presence.ContactCapabilityResponse;
 import com.android.service.ims.presence.PresenceBase;
@@ -34,7 +63,12 @@
 import com.android.service.ims.presence.PresenceSubscriber;
 import com.android.service.ims.presence.SubscribePublisher;
 
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
@@ -46,12 +80,32 @@
 
     private static final String LOG_TAG = "RcsUceImpl";
 
-    private int mSlotId;
-    private int mSubId;
+    private final int mSlotId;
+    private volatile int mSubId;
+    private volatile boolean mImsContentChangedCallbackRegistered = false;
+    // The result of requesting publish
+    private volatile int mPublishState = PresenceBase.PUBLISH_STATE_NOT_PUBLISHED;
+    // The network type which IMS registers on
+    private volatile int mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+    // The MMTel capabilities of this subscription Id
+    private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
+    private final Object mCapabilitiesLock = new Object();
 
+    private final Context mContext;
+    private final UceImplHandler mUceImplHandler;
+    private RcsFeatureManager mRcsFeatureManager;
     private final PresencePublication mPresencePublication;
     private final PresenceSubscriber mPresenceSubscriber;
 
+    // The task Ids of updating capabilities
+    private final Set<Integer> mRequestingPublishTaskIds = new HashSet<>();
+
+    // The callbacks to notify publish state changed.
+    private final RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
+    // The task Ids of pending availability request.
+    private final Set<Integer> mPendingAvailabilityRequests = new HashSet<>();
+
     private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
             new ConcurrentHashMap<>();
 
@@ -60,6 +114,13 @@
         mSubId = subId;
         logi("created");
 
+        mContext = context;
+        mPublishStateCallbacks = new RemoteCallbackList<>();
+
+        HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+        handlerThread.start();
+        mUceImplHandler = new UceImplHandler(this, handlerThread.getLooper());
+
         String[] volteError = context.getResources().getStringArray(
                 R.array.config_volte_provision_error_on_publish_response);
         String[] rcsError = context.getResources().getStringArray(
@@ -73,13 +134,31 @@
                 volteError, rcsError);
 
         onAssociatedSubscriptionUpdated(mSubId);
+        registerReceivers();
     }
 
+    @VisibleForTesting
+    UserCapabilityExchangeImpl(Context context, int slotId, int subId, Looper looper,
+            PresencePublication presencePublication, PresenceSubscriber presenceSubscriber,
+            RemoteCallbackList<IRcsUcePublishStateCallback> publishStateCallbacks) {
+        mSlotId = slotId;
+        mSubId = subId;
+        mContext = context;
+        mPublishStateCallbacks = publishStateCallbacks;
+        mUceImplHandler = new UceImplHandler(this, looper);
+        mPresencePublication = presencePublication;
+        mPresenceSubscriber = presenceSubscriber;
+        onAssociatedSubscriptionUpdated(mSubId);
+        registerReceivers();
+    }
 
     // Runs on main thread.
     @Override
     public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
         logi("onRcsConnected");
+        mRcsFeatureManager = rcsFeatureManager;
+        mRcsFeatureManager.addFeatureListenerCallback(mRcsFeatureCallback);
+
         mPresencePublication.updatePresencePublisher(this);
         mPresenceSubscriber.updatePresenceSubscriber(this);
     }
@@ -90,11 +169,22 @@
         logi("onRcsDisconnected");
         mPresencePublication.removePresencePublisher();
         mPresenceSubscriber.removePresenceSubscriber();
+
+        if (mRcsFeatureManager != null) {
+            mRcsFeatureManager.releaseConnection();
+            mRcsFeatureManager = null;
+        }
     }
 
     // Runs on main thread.
     @Override
     public void onAssociatedSubscriptionUpdated(int subId) {
+        logi("onAssociatedSubscriptionUpdated: new subId=" + subId);
+
+        // Listen to the IMS content changed with new subId.
+        mUceImplHandler.registerImsContentChangedReceiver(subId);
+
+        mSubId = subId;
         mPresencePublication.handleAssociatedSubscriptionChanged(subId);
         mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
     }
@@ -105,6 +195,10 @@
      */
     // Called on main thread.
     public void onDestroy() {
+        logi("onDestroy");
+        mUceImplHandler.getLooper().quit();
+        unregisterReceivers();
+        unregisterImsProvisionCallback(mSubId);
         onRcsDisconnected();
     }
 
@@ -117,6 +211,54 @@
         return toUcePublishState(publishState);
     }
 
+    @VisibleForTesting
+    public UceImplHandler getHandler() {
+        return mUceImplHandler;
+    }
+
+    /**
+     * Register receiver to receive UCE publish state changed.
+     */
+    public void registerPublishStateCallback(IRcsUcePublishStateCallback c) {
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.register(c);
+        }
+    }
+
+    /**
+     * Unregister UCE publish state callback.
+     */
+    public void unregisterUcePublishStateCallback(IRcsUcePublishStateCallback c) {
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.unregister(c);
+        }
+    }
+
+    private void clearPublishStateCallbacks() {
+        synchronized (mPublishStateCallbacks) {
+            logi("clearPublishStateCallbacks");
+            final int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
+            for (int index = lastIndex; index >= 0; index--) {
+                IRcsUcePublishStateCallback callback =
+                        mPublishStateCallbacks.getRegisteredCallbackItem(index);
+                mPublishStateCallbacks.unregister(callback);
+            }
+        }
+    }
+
+    private void notifyPublishStateChanged(@PresenceBase.PresencePublishState int state) {
+        int result = toUcePublishState(state);
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.broadcast(c -> {
+                try {
+                    c.onPublishStateChanged(result);
+                } catch (RemoteException e) {
+                    logw("notifyPublishStateChanged error: " + e);
+                }
+            });
+        }
+    }
+
     /**
      * Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
      */
@@ -183,43 +325,393 @@
         if (taskId < 0) {
             try {
                 c.onError(toUceError(taskId));
-                return;
             } catch (RemoteException e) {
                 logi("Calling back to dead service");
             }
+            return;
         }
         mPendingCapabilityRequests.put(taskId, c);
     }
 
     @Override
-    public int getPublisherState() {
-        return 0;
+    public int requestCapability(String[] formattedContacts, int taskId) {
+        if (formattedContacts == null || formattedContacts.length == 0) {
+            logw("requestCapability error: contacts is null.");
+            return ResultCode.SUBSCRIBE_INVALID_PARAM;
+        }
+        if (mRcsFeatureManager == null) {
+            logw("requestCapability error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestCapability: taskId=" + taskId);
+
+        try {
+            List<Uri> contactList = Arrays.stream(formattedContacts)
+                    .map(Uri::parse).collect(Collectors.toList());
+            mRcsFeatureManager.requestCapabilities(contactList, taskId);
+        } catch (Exception e) {
+            logw("requestCapability error: " + e.getMessage());
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    @Override
+    public int requestAvailability(String formattedContact, int taskId) {
+        if (formattedContact == null || formattedContact.isEmpty()) {
+            logw("requestAvailability error: contact is null.");
+            return ResultCode.SUBSCRIBE_INVALID_PARAM;
+        }
+        if (mRcsFeatureManager == null) {
+            logw("requestAvailability error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestAvailability: taskId=" + taskId);
+        addRequestingAvailabilityTaskId(taskId);
+
+        try {
+            Uri contactUri = Uri.parse(formattedContact);
+            List<Uri> contactUris = new ArrayList<>(Arrays.asList(contactUri));
+            mRcsFeatureManager.requestCapabilities(contactUris, taskId);
+        } catch (Exception e) {
+            logw("requestAvailability error: " + e.getMessage());
+            removeRequestingAvailabilityTaskId(taskId);
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    @Override
+    public int getStackStatusForCapabilityRequest() {
+        if (mRcsFeatureManager == null) {
+            logw("Check Stack status: Error! RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        if (!isCapabilityDiscoveryEnabled(mSubId)) {
+            logw("Check Stack status: Error! capability discovery not enabled");
+            return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+        }
+
+        if (!isEabProvisioned(mContext, mSubId)) {
+            logw("Check Stack status: Error! EAB provisioning disabled.");
+            return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+        }
+
+        if (getPublisherState() != PresenceBase.PUBLISH_STATE_200_OK) {
+            logw("Check Stack status: Error! publish state " + getPublisherState());
+            return ResultCode.ERROR_SERVICE_NOT_PUBLISHED;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    /**
+     * The feature callback is to receive the request and update from RcsPresExchangeImplBase
+     */
+    @VisibleForTesting
+    public RcsFeatureCallbacks mRcsFeatureCallback = new RcsFeatureCallbacks() {
+        public void onCommandUpdate(int commandCode, int operationToken) {
+            logi("onCommandUpdate: code=" + commandCode + ", token=" + operationToken);
+            if (isPublishRequestExisted(operationToken)) {
+                onCommandUpdateForPublishRequest(commandCode, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                onCommandUpdateForCapabilityRequest(commandCode, operationToken);
+            } else if (isAvailabilityRequestExisted(operationToken)) {
+                onCommandUpdateForAvailabilityRequest(commandCode, operationToken);
+            } else {
+                logw("onCommandUpdate: invalid token " + operationToken);
+            }
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onNetworkResponse(int, String, int)} */
+        public void onNetworkResponse(int responseCode, String reason, int operationToken) {
+            logi("onNetworkResponse: code=" + responseCode + ", reason=" + reason
+                    + ", operationToken=" + operationToken);
+            if (isPublishRequestExisted(operationToken)) {
+                onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                onNetworkResponseForCapabilityRequest(responseCode, reason, operationToken);
+            } else if (isAvailabilityRequestExisted(operationToken)) {
+                onNetworkResponseForAvailabilityRequest(responseCode, reason, operationToken);
+            } else {
+                logw("onNetworkResponse: invalid token " + operationToken);
+            }
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onCapabilityRequestResponse(List, int)} */
+        public void onCapabilityRequestResponsePresence(List<RcsContactUceCapability> infos,
+                int operationToken) {
+            if (isAvailabilityRequestExisted(operationToken)) {
+                handleAvailabilityReqResponse(infos, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                handleCapabilityReqResponse(infos, operationToken);
+            } else {
+                logw("capability request response: invalid token " + operationToken);
+            }
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onNotifyUpdateCapabilites(int)} */
+        public void onNotifyUpdateCapabilities(int publishTriggerType) {
+            logi("onNotifyUpdateCapabilities: type=" + publishTriggerType);
+            mUceImplHandler.notifyUpdateCapabilities(publishTriggerType);
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onUnpublish()} */
+        public void onUnpublish() {
+            logi("onUnpublish");
+            mUceImplHandler.unpublish();
+        }
+    };
+
+    private static class UceImplHandler extends Handler {
+        private static final int EVENT_REGISTER_IMS_CHANGED_RECEIVER = 1;
+        private static final int EVENT_NOTIFY_UPDATE_CAPABILITIES = 2;
+        private static final int EVENT_UNPUBLISH = 3;
+
+        private static final int REGISTER_IMS_CHANGED_DELAY = 10000;  //10 seconds
+
+        private final WeakReference<UserCapabilityExchangeImpl> mUceImplRef;
+
+        UceImplHandler(UserCapabilityExchangeImpl uceImpl, Looper looper) {
+            super(looper);
+            mUceImplRef = new WeakReference(uceImpl);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            UserCapabilityExchangeImpl uceImpl = mUceImplRef.get();
+            if (uceImpl == null) {
+                return;
+            }
+            switch (msg.what) {
+                case EVENT_REGISTER_IMS_CHANGED_RECEIVER:
+                    int subId = msg.arg1;
+                    uceImpl.registerImsContentChangedReceiverInternal(subId);
+                    break;
+                case EVENT_NOTIFY_UPDATE_CAPABILITIES:
+                    int publishTriggerType = msg.arg1;
+                    uceImpl.onNotifyUpdateCapabilities(publishTriggerType);
+                    break;
+                case EVENT_UNPUBLISH:
+                    uceImpl.onUnPublish();
+                    break;
+                default:
+                    Log.w(LOG_TAG, "handleMessage: error=" + msg.what);
+                    break;
+            }
+        }
+
+        private void retryRegisteringImsContentChangedReceiver(int subId) {
+            sendRegisteringImsContentChangedMessage(subId, REGISTER_IMS_CHANGED_DELAY);
+        }
+
+        private void registerImsContentChangedReceiver(int subId) {
+            sendRegisteringImsContentChangedMessage(subId, 0);
+        }
+
+        private void sendRegisteringImsContentChangedMessage(int subId, int delay) {
+            if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                return;
+            }
+            removeRegisteringImsContentChangedReceiver();
+            Message message = obtainMessage(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+            message.arg1 = subId;
+            sendMessageDelayed(message, delay);
+        }
+
+        private void removeRegisteringImsContentChangedReceiver() {
+            removeMessages(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+        }
+
+        private void notifyUpdateCapabilities(int publishTriggerType) {
+            Message message = obtainMessage(EVENT_NOTIFY_UPDATE_CAPABILITIES);
+            message.arg1 = publishTriggerType;
+            sendMessage(message);
+        }
+
+        private void unpublish() {
+            sendEmptyMessage(EVENT_UNPUBLISH);
+        }
+    }
+
+    private void onNotifyUpdateCapabilities(int publishTriggerType) {
+        mPresencePublication.onStackPublishRequested(publishTriggerType);
+    }
+
+    private void onUnPublish() {
+        mPresencePublication.setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+    }
+
+    @Override
+    public @PresenceBase.PresencePublishState int getPublisherState() {
+        return mPublishState;
     }
 
     @Override
     public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
             int taskId) {
-        return 0;
+        if (mRcsFeatureManager == null) {
+            logw("requestPublication error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestPublication: taskId=" + taskId);
+        addPublishRequestTaskId(taskId);
+
+        try {
+            mRcsFeatureManager.requestPublication(capabilities, taskId);
+        } catch (Exception ex) {
+            logw("requestPublication error: " + ex.getMessage());
+            removePublishRequestTaskId(taskId);
+            return ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    /*
+     * Handle the callback method RcsFeatureCallbacks#onCommandUpdate(int, int)
+     */
+    private void onCommandUpdateForPublishRequest(int commandCode, int operationToken) {
+        if (!isPublishRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("onCommandUpdateForPublishRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
+            removePublishRequestTaskId(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresencePublication.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
+    private void onCommandUpdateForCapabilityRequest(int commandCode, int operationToken) {
+        if (!isCapabilityRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("onCommandUpdateForCapabilityRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
+            mPendingCapabilityRequests.remove(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
+    private void onCommandUpdateForAvailabilityRequest(int commandCode, int operationToken) {
+        if (!isAvailabilityRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("onCommandUpdateForAvailabilityRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
+            removeRequestingAvailabilityTaskId(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
+    /*
+     * Handle the callback method RcsFeatureCallbacks#onNetworkResponse(int, String, int)
+     */
+    private void onNetworkResponseForPublishRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isPublishRequestExisted(operationToken)) {
+            return;
+        }
+        removePublishRequestTaskId(operationToken);
+        mPresencePublication.onSipResponse(operationToken, responseCode, reason);
+    }
+
+    private void onNetworkResponseForCapabilityRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isCapabilityRequestExisted(operationToken)) {
+            return;
+        }
+        mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
+    }
+
+    private void onNetworkResponseForAvailabilityRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isAvailabilityRequestExisted(operationToken)) {
+            return;
+        }
+        removeRequestingAvailabilityTaskId(operationToken);
+        mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
+    }
+
+    private void handleAvailabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+        try {
+            if (infos == null || infos.isEmpty()) {
+                logw("handle availability request response: infos is null " + token);
+                return;
+            }
+            logi("handleAvailabilityReqResponse: token=" + token);
+            mPresenceSubscriber.updatePresence(infos.get(0));
+        } finally {
+            removeRequestingAvailabilityTaskId(token);
+        }
+    }
+
+    private void handleCapabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+        if (infos == null) {
+            logw("handleCapabilityReqResponse: infos is null " + token);
+            mPendingCapabilityRequests.remove(token);
+            return;
+        }
+        logi("handleCapabilityReqResponse: token=" + token);
+        mPresenceSubscriber.updatePresences(token, infos, true, null);
     }
 
     @Override
-    public int requestCapability(String[] formatedContacts, int taskId) {
-        return 0;
+    public void updatePublisherState(@PresenceBase.PresencePublishState int publishState) {
+        logi("updatePublisherState: from " + mPublishState + " to " + publishState);
+        mPublishState = publishState;
+        notifyPublishStateChanged(publishState);
     }
 
-    @Override
-    public int requestAvailability(String formattedContact, int taskId) {
-        return 0;
+    private void addPublishRequestTaskId(int taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            mRequestingPublishTaskIds.add(taskId);
+        }
     }
 
-    @Override
-    public int getStackStatusForCapabilityRequest() {
-        return 0;
+    private void removePublishRequestTaskId(int taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            mRequestingPublishTaskIds.remove(taskId);
+        }
     }
 
-    @Override
-    public void updatePublisherState(int publishState) {
+    private boolean isPublishRequestExisted(Integer taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            return mRequestingPublishTaskIds.contains(taskId);
+        }
+    }
 
+    private void addRequestingAvailabilityTaskId(int taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            mPendingAvailabilityRequests.contains(taskId);
+        }
+    }
+
+    private void removeRequestingAvailabilityTaskId(int taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            mPendingAvailabilityRequests.remove(taskId);
+        }
+    }
+
+    private boolean isAvailabilityRequestExisted(Integer taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            return mPendingAvailabilityRequests.contains(taskId);
+        }
+    }
+
+    private boolean isCapabilityRequestExisted(Integer taskId) {
+        return mPendingCapabilityRequests.containsKey(taskId);
     }
 
     private static String getNumberFromUri(Uri uri) {
@@ -274,6 +766,286 @@
         }
     }
 
+    /*
+     * Register receivers for updating capabilities
+     */
+    private void registerReceivers() {
+        IntentFilter filter = new IntentFilter(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mContext.registerReceiver(mReceiver, filter);
+
+        ContentResolver resolver = mContext.getContentResolver();
+        if (resolver != null) {
+            // Register mobile data content changed.
+            resolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.MOBILE_DATA), false,
+                    mMobileDataObserver);
+
+            // Register SIM info content changed.
+            resolver.registerContentObserver(Telephony.SimInfo.CONTENT_URI, false,
+                    mSimInfoContentObserver);
+        }
+    }
+
+    private void unregisterReceivers() {
+        mContext.unregisterReceiver(mReceiver);
+        ContentResolver resolver = mContext.getContentResolver();
+        if (resolver != null) {
+            resolver.unregisterContentObserver(mMobileDataObserver);
+            resolver.unregisterContentObserver(mSimInfoContentObserver);
+        }
+    }
+
+    /**
+     * Register IMS and provision content changed.
+     *
+     * Call the UceImplHandler#registerImsContentChangedReceiver instead of
+     * calling this method directly.
+     */
+    private void registerImsContentChangedReceiverInternal(int subId) {
+        mUceImplHandler.removeRegisteringImsContentChangedReceiver();
+        try {
+            final int originalSubId = mSubId;
+            if ((originalSubId == subId) && (mImsContentChangedCallbackRegistered)) {
+                logi("registerImsContentChangedReceiverInternal: already registered. skip");
+                return;
+            }
+            // Unregister original IMS and Provision callback
+            unregisterImsProvisionCallback(originalSubId);
+            // Register new IMS and Provision callback
+            registerImsProvisionCallback(subId);
+        } catch (ImsException e) {
+            logw("registerImsContentChangedReceiverInternal error: " + e);
+            mUceImplHandler.retryRegisteringImsContentChangedReceiver(subId);
+        }
+    }
+
+    private void unregisterImsProvisionCallback(int subId) {
+        if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+        // Unregister IMS callback
+        ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+        if (imsMmtelManager != null) {
+            try {
+                imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+                imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
+            } catch (RuntimeException e) {
+                logw("unregister IMS callback error: " + e.getMessage());
+            }
+        }
+
+        // Unregister provision changed callback
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(subId);
+        try {
+            provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
+        } catch (RuntimeException e) {
+            logw("unregister provisioning callback error: " + e.getMessage());
+        }
+
+        // Remove all publish state callbacks
+        clearPublishStateCallbacks();
+
+        mImsContentChangedCallbackRegistered = false;
+    }
+
+    private void registerImsProvisionCallback(int subId) throws ImsException {
+        if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+        // Register IMS callback
+        ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+        if (imsMmtelManager != null) {
+            imsMmtelManager.registerImsRegistrationCallback(mContext.getMainExecutor(),
+                    mImsRegistrationCallback);
+            imsMmtelManager.registerMmTelCapabilityCallback(mContext.getMainExecutor(),
+                    mCapabilityCallback);
+        }
+        // Register provision changed callback
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(subId);
+        provisioningManager.registerProvisioningChangedCallback(mContext.getMainExecutor(),
+                mProvisioningChangedCallback);
+
+        mImsContentChangedCallbackRegistered = true;
+        logi("registerImsProvisionCallback");
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null) return;
+            switch (intent.getAction()) {
+                case TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED:
+                    int preferredMode = intent.getIntExtra(
+                            TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+                    logi("TTY preferred mode changed: " + preferredMode);
+                    mPresencePublication.onTtyPreferredModeChanged(preferredMode);
+                    break;
+
+                case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+                    boolean airplaneMode = intent.getBooleanExtra("state", false);
+                    logi("Airplane mode changed: " + airplaneMode);
+                    mPresencePublication.onAirplaneModeChanged(airplaneMode);
+                    break;
+            }
+        }
+    };
+
+    private ContentObserver mMobileDataObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.MOBILE_DATA, 1) == 1;
+            logi("Mobile data changed: enabled=" + isEnabled);
+            mPresencePublication.onMobileDataChanged(isEnabled);
+        }
+    };
+
+    private ContentObserver mSimInfoContentObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            if (mSubId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                return;
+            }
+
+            ImsMmTelManager ims = getImsMmTelManager(mSubId);
+            if (ims == null) return;
+
+            try {
+                boolean isEnabled = ims.isVtSettingEnabled();
+                logi("SimInfo changed: VT setting=" + isEnabled);
+                mPresencePublication.onVtEnabled(isEnabled);
+            } catch (RuntimeException e) {
+                logw("SimInfo changed error: " + e);
+            }
+        }
+    };
+
+    private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
+        @Override
+        public void onRegistered(int imsTransportType) {
+            logi("onRegistered: type=" + imsTransportType);
+            mNetworkRegistrationType = imsTransportType;
+            mPresencePublication.onImsConnected();
+
+            // Also trigger PresencePublication#onFeatureCapabilityChanged method
+            MmTelFeature.MmTelCapabilities capabilities = null;
+            synchronized (mCapabilitiesLock) {
+                capabilities = mMmTelCapabilities;
+            }
+
+            if (capabilities != null) {
+                mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
+                        capabilities);
+            }
+        }
+
+        @Override
+        public void onUnregistered(ImsReasonInfo info) {
+            logi("onUnregistered");
+            mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+
+            // Also trigger PresencePublication#onFeatureCapabilityChanged method
+            MmTelFeature.MmTelCapabilities capabilities = null;
+            synchronized (mCapabilitiesLock) {
+                capabilities = mMmTelCapabilities;
+            }
+
+            if (capabilities != null) {
+                mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
+                        capabilities);
+            }
+            mPresencePublication.onImsDisconnected();
+        }
+    };
+
+    private ImsMmTelManager.CapabilityCallback mCapabilityCallback =
+            new ImsMmTelManager.CapabilityCallback() {
+        @Override
+        public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+            if (capabilities == null) {
+                logw("onCapabilitiesStatusChanged: parameter is null");
+                return;
+            }
+            synchronized (mCapabilitiesLock) {
+                mMmTelCapabilities = capabilities;
+            }
+            mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
+        }
+    };
+
+    private ProvisioningManager.Callback mProvisioningChangedCallback =
+            new ProvisioningManager.Callback() {
+        @Override
+        public void onProvisioningIntChanged(int item, int value) {
+            logi("onProvisioningIntChanged: item=" + item);
+            switch (item) {
+                case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
+                case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
+                case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
+                    mPresencePublication.handleProvisioningChanged();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private boolean isCapabilityDiscoveryEnabled(int subId) {
+        try {
+            ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+            int discoveryEnabled = manager.getProvisioningIntValue(
+                    ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED);
+            return (discoveryEnabled == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        } catch (Exception e) {
+            logw("isCapabilityDiscoveryEnabled error: " + e.getMessage());
+        }
+        return false;
+    }
+
+    private boolean isEabProvisioned(Context context, int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            logw("isEabProvisioned error: invalid subscriptionId " + subId);
+            return false;
+        }
+
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle config = configManager.getConfigForSubId(subId);
+            if (config != null && !config.getBoolean(
+                    CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) {
+                return true;
+            }
+        }
+
+        try {
+            ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+            int provisioningStatus = manager.getProvisioningIntValue(
+                    ProvisioningManager.KEY_EAB_PROVISIONING_STATUS);
+            return (provisioningStatus == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        } catch (Exception e) {
+            logw("isEabProvisioned error: " + e.getMessage());
+        }
+        return false;
+    }
+
+    private ImsMmTelManager getImsMmTelManager(int subId) {
+        try {
+            ImsManager imsManager = (ImsManager) mContext.getSystemService(
+                    Context.TELEPHONY_IMS_SERVICE);
+            return (imsManager == null) ? null : imsManager.getImsMmTelManager(subId);
+        } catch (IllegalArgumentException e) {
+            logw("getImsMmTelManager error: " + e.getMessage());
+            return null;
+        }
+    }
+
     private void logi(String log) {
         Log.i(LOG_TAG, getLogPrefix().append(log).toString());
     }
diff --git a/testapps/EmbmsServiceTestApp/AndroidManifest.xml b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
index 91d8508..943fc78 100644
--- a/testapps/EmbmsServiceTestApp/AndroidManifest.xml
+++ b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
@@ -15,30 +15,31 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="com.android.phone.testapps.embmsmw"
-        coreApp="true">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="com.android.phone.testapps.embmsmw"
+     coreApp="true">
   <uses-permission android:name="android.permission.SEND_EMBMS_INTENTS"/>
 
   <application android:label="EmbmsTestMiddleware">
     <service android:name="com.android.phone.testapps.embmsmw.EmbmsTestStreamingService"
-            android:launchMode="singleInstance"
-            androidprv:systemUserOnly="true">
+         android:launchMode="singleInstance"
+         androidprv:systemUserOnly="true"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsStreaming" />
+        <action android:name="android.telephony.action.EmbmsStreaming"/>
       </intent-filter>
     </service>
     <service android:name="com.android.phone.testapps.embmsmw.EmbmsSampleDownloadService"
-             android:launchMode="singleInstance"
-             androidprv:systemUserOnly="true">
+         android:launchMode="singleInstance"
+         androidprv:systemUserOnly="true"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsDownload" />
+        <action android:name="android.telephony.action.EmbmsDownload"/>
       </intent-filter>
     </service>
 
     <receiver android:name="com.android.phone.testapps.embmsmw.SideChannelReceiver"
-              android:enabled="true"
-              android:exported="true"/>
+         android:enabled="true"
+         android:exported="true"/>
   </application>
 </manifest>
-
diff --git a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
index e93cd19..640fcd1 100644
--- a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
@@ -15,57 +15,54 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.phone.testapps.embmsdownload">
+     package="com.android.phone.testapps.embmsdownload">
     <application android:label="EmbmsTestDownloadApp">
-        <activity
-            android:name=".EmbmsTestDownloadApp"
-            android:label="EmbmsDownloadFrontend">
+        <activity android:name=".EmbmsTestDownloadApp"
+             android:label="EmbmsDownloadFrontend"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <!-- This is the receiver defined by the MBMS api. -->
-        <receiver
-            android:name="android.telephony.mbms.MbmsDownloadReceiver"
-            android:permission="android.permission.SEND_EMBMS_INTENTS"
-            android:enabled="true"
-            android:exported="true">
+        <receiver android:name="android.telephony.mbms.MbmsDownloadReceiver"
+             android:permission="android.permission.SEND_EMBMS_INTENTS"
+             android:enabled="true"
+             android:exported="true">
         </receiver>
 
         <!-- This is the receiver defined by app to receive the download-done intent that was
          passed into DownloadRequest. -->
-        <receiver
-            android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
-            android:enabled="true">
+        <receiver android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
+             android:enabled="true">
         </receiver>
 
         <!-- This is the provider that apps must declare in their manifest. It allows the
         middleware to obtain file descriptors to temp files in the app's file space -->
         <!-- grantUriPermissions must be set to true -->
-        <provider
-            android:name="android.telephony.mbms.MbmsTempFileProvider"
-            android:authorities="com.android.phone.testapps.embmsdownload"
-            android:exported="false"
-            android:grantUriPermissions="true">
+        <provider android:name="android.telephony.mbms.MbmsTempFileProvider"
+             android:authorities="com.android.phone.testapps.embmsdownload"
+             android:exported="false"
+             android:grantUriPermissions="true">
             <!-- This is a mandatory piece of metadata that contains the directory where temp
             files should be put. It should be a relative path from Context.getFilesDir() or from
             Context.getExternalStorageDir(null), depending on the value of the
             use-external-storage metadata. -->
-            <meta-data android:name="temp-file-path" android:value="/mbms-temp/"/>
+            <meta-data android:name="temp-file-path"
+                 android:value="/mbms-temp/"/>
 
             <!-- This tells the provider whether to use the sdcard partition for the temp files or
             not. -->
-            <meta-data android:name="use-external-storage" android:value="false"/>
+            <meta-data android:name="use-external-storage"
+                 android:value="false"/>
         </provider>
 
         <!-- This is a mandatory piece of metadata that contains the authority string for the
         provider declared above -->
-        <meta-data
-            android:name="mbms-file-provider-authority"
-            android:value="com.android.phone.testapps.embmsdownload"/>
+        <meta-data android:name="mbms-file-provider-authority"
+             android:value="com.android.phone.testapps.embmsdownload"/>
     </application>
 </manifest>
-
diff --git a/testapps/EmbmsTestStreamingApp/AndroidManifest.xml b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
index d13425d..9cb83f2 100644
--- a/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
@@ -15,17 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.phone.testapps.embmsfrontend">
+     package="com.android.phone.testapps.embmsfrontend">
     <application android:label="EmbmsTestStreamingApp">
-        <activity
-            android:name=".EmbmsTestStreamingApp"
-            android:label="EmbmsStreamingFrontend">
+        <activity android:name=".EmbmsTestStreamingApp"
+             android:label="EmbmsStreamingFrontend"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/testapps/ImsTestService/AndroidManifest.xml b/testapps/ImsTestService/AndroidManifest.xml
index eea54b8..46d0721 100644
--- a/testapps/ImsTestService/AndroidManifest.xml
+++ b/testapps/ImsTestService/AndroidManifest.xml
@@ -16,40 +16,42 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          coreApp="true"
-          package="com.android.phone.testapps.imstestapp">
+     coreApp="true"
+     package="com.android.phone.testapps.imstestapp">
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <!--Beware, declaring the below permission will cause the device to not boot unless you add
         this app and permission to frameworks/base/data/etc/privapp-permissions-platform.xml-->
-    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
-    <application
-        android:label="ImsTestService"
-        android:directBootAware="true">
-        <activity
-            android:name=".ImsTestServiceApp"
-            android:label="ImsTestService">
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+    <application android:label="ImsTestService"
+         android:directBootAware="true">
+        <activity android:name=".ImsTestServiceApp"
+             android:label="ImsTestService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".ImsRegistrationActivity" android:label="IMS Registration" />
-        <activity android:name=".ImsCallingActivity" android:label="IMS Calling" />
-        <activity android:name=".ImsConfigActivity" android:label="IMS Config" />
+        <activity android:name=".ImsRegistrationActivity"
+             android:label="IMS Registration"/>
+        <activity android:name=".ImsCallingActivity"
+             android:label="IMS Calling"/>
+        <activity android:name=".ImsConfigActivity"
+             android:label="IMS Config"/>
 
         <service android:name=".TestImsService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:persistent="true"
-                 android:permission="android.permission.BIND_IMS_SERVICE">
-            <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE" android:value="true"/-->
+             android:exported="true"
+             android:enabled="true"
+             android:persistent="true"
+             android:permission="android.permission.BIND_IMS_SERVICE">
+            <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE"
+                 android:value="true"/-->
             <!-- No features means we will get queried for dynamic config. -->
             <intent-filter>
-                <action android:name="android.telephony.ims.ImsService" />
+                <action android:name="android.telephony.ims.ImsService"/>
             </intent-filter>
         </service>
     </application>
 </manifest>
-
diff --git a/testapps/SmsManagerTestApp/AndroidManifest.xml b/testapps/SmsManagerTestApp/AndroidManifest.xml
index c5f4621..57b7344 100644
--- a/testapps/SmsManagerTestApp/AndroidManifest.xml
+++ b/testapps/SmsManagerTestApp/AndroidManifest.xml
@@ -16,29 +16,30 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.phone.testapps.smsmanagertestapp">
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="29" />
+     package="com.android.phone.testapps.smsmanagertestapp">
+    <uses-sdk android:minSdkVersion="24"
+         android:targetSdkVersion="29"/>
     <uses-permission android:name="android.permission.SEND_SMS"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <application android:label="SmsManagerTestApp">
-        <activity
-            android:name=".SmsManagerTestApp"
-            android:label="SmsManagerTestApp">
+        <activity android:name=".SmsManagerTestApp"
+             android:label="SmsManagerTestApp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <service android:name=".SmsManagerTestService" android:exported="false" />
+        <service android:name=".SmsManagerTestService"
+             android:exported="false"/>
         <receiver android:name=".SendStatusReceiver"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action" />
-                <data android:scheme="content" />
+                <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action"/>
+                <data android:scheme="content"/>
             </intent-filter>
         </receiver>
 
     </application>
 </manifest>
-
diff --git a/testapps/TelephonyManagerTestApp/AndroidManifest.xml b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
index 044d0b2..40fc549 100644
--- a/testapps/TelephonyManagerTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.phone.testapps.telephonymanagertestapp">
+     package="com.android.phone.testapps.telephonymanagertestapp">
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
@@ -26,31 +26,29 @@
     <uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
 
-    android.Manifest.permission.ACCESS_FINE_LOCATION
+            android.Manifest.permission.ACCESS_FINE_LOCATION
     <application android:label="TelephonyManagerTestApp">
-        <activity
-            android:name=".TelephonyManagerTestApp"
-            android:label="TelephonyManagerTestApp">
+        <activity android:name=".TelephonyManagerTestApp"
+             android:label="TelephonyManagerTestApp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.intent.action.SEARCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.intent.action.SEARCH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data
-                android:name="android.app.searchable"
-                android:resource="@xml/searchable">
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable">
             </meta-data>
         </activity>
 
-        <activity
-            android:name=".CallingMethodActivity"
-            android:label="CallingMethodActivity">
+        <activity android:name=".CallingMethodActivity"
+             android:label="CallingMethodActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 550c9f0..7432156 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -15,24 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.phone.testapps.telephonyregistry">
+     package="com.android.phone.testapps.telephonyregistry">
     <uses-sdk android:minSdkVersion="25"
-          android:targetSdkVersion="25"/>
+         android:targetSdkVersion="25"/>
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
     <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE"/>
     <application android:label="TelephonyRegistryTestApp">
-        <activity
-            android:name=".TelephonyRegistryTestApp"
-            android:label="TelephonyRegistryTestApp">
+        <activity android:name=".TelephonyRegistryTestApp"
+             android:label="TelephonyRegistryTestApp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index d434650..174d22e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -31,6 +31,7 @@
                 adb shell am start -n com.android.phone.tests/.CallDialTest
         -->
         <activity android:name="CallDialTest"
+                  android:exported="true"
                   android:label="@string/callDialTestLabel">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -39,6 +40,7 @@
         </activity>
 
         <service android:name="SendInstantTextTestService"
+                 android:exported="true"
                  android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
             <intent-filter>
                 <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index c5b9b1e..13bfe3b 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -29,6 +30,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
 import android.test.mock.MockContext;
 
 import org.mockito.Mock;
@@ -42,6 +44,7 @@
     @Mock TelecomManager mMockTelecomManager;
     @Mock TelephonyManager mMockTelephonyManager;
     @Mock SubscriptionManager mMockSubscriptionManager;
+    @Mock ImsManager mMockImsManager;
 
     private PersistableBundle mCarrierConfig = new PersistableBundle();
 
@@ -94,6 +97,11 @@
     }
 
     @Override
+    public ContentResolver getContentResolver() {
+        return null;
+    }
+
+    @Override
     public Object getSystemService(String name) {
         switch (name) {
             case (Context.CARRIER_CONFIG_SERVICE) : {
@@ -108,6 +116,9 @@
             case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
                 return mMockSubscriptionManager;
             }
+            case(Context.TELEPHONY_IMS_SERVICE) : {
+                return mMockImsManager;
+            }
         }
         return null;
     }
diff --git a/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
new file mode 100644
index 0000000..82ecd79
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 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.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
+import com.android.ims.ResultCode;
+import com.android.service.ims.presence.PresenceBase;
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresencePublisher;
+import com.android.service.ims.presence.PresenceSubscriber;
+import com.android.service.ims.presence.SubscribePublisher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class UserCapabilityExchangeImplTest extends TelephonyTestBase {
+
+    private int  mSlotId = 0;
+    private int mSubId = 1;
+    private int mUpdatedSubId = 2;
+
+    @Captor ArgumentCaptor<IRcsUcePublishStateCallback> mPublishStateCallbacksCaptor;
+
+    @Mock PresencePublication mPresencePublication;
+    @Mock PresenceSubscriber mPresenceSubscriber;
+    @Mock RcsFeatureManager mRcsFeatureManager;
+    @Mock ImsMmTelManager mImsMmTelManager;
+    @Mock RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
+    private Looper mLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        ImsManager imsManager =
+                (ImsManager) mContext.getSystemService(Context.TELEPHONY_IMS_SERVICE);
+        when(imsManager.getImsMmTelManager(mSubId)).thenReturn(mImsMmTelManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+    @Test
+    public void testServiceConnected() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        verify(mRcsFeatureManager).addFeatureListenerCallback(any(RcsFeatureCallbacks.class));
+        verify(mPresencePublication).updatePresencePublisher(any(PresencePublisher.class));
+        verify(mPresenceSubscriber).updatePresenceSubscriber(any(SubscribePublisher.class));
+    }
+
+    @Test
+    public void testServiceDisconnected() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsDisconnected();
+
+        verify(mPresencePublication).removePresencePublisher();
+        verify(mPresenceSubscriber).removePresenceSubscriber();
+    }
+
+    @Test
+    public void testSubscriptionUpdated() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onAssociatedSubscriptionUpdated(mUpdatedSubId);
+
+        verify(mImsMmTelManager).registerImsRegistrationCallback(any(Executor.class),
+                any(RegistrationManager.RegistrationCallback.class));
+        verify(mImsMmTelManager).registerMmTelCapabilityCallback(any(Executor.class),
+                any(ImsMmTelManager.CapabilityCallback.class));
+        verify(mPresencePublication).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+        verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+    }
+
+    @Test
+    public void testUcePublishStateRetrieval() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.getUcePublishState();
+
+        verify(mPresencePublication).getPublishState();
+    }
+
+    @Test
+    public void testRegisterPublishStateCallbacks() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.registerPublishStateCallback(any(IRcsUcePublishStateCallback.class));
+        verify(mPublishStateCallbacks).register(mPublishStateCallbacksCaptor.capture());
+    }
+
+    @Test
+    public void testOnNotifyUpdateCapabilities() throws Exception {
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        int triggerType = RcsPresenceExchangeImplBase.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN;
+        uceImpl.mRcsFeatureCallback.onNotifyUpdateCapabilities(triggerType);
+        waitForMs(1000);
+
+        verify(mPresencePublication).onStackPublishRequested(triggerType);
+    }
+
+    @Test
+    public void testRequestPublicationWithSuccessfulResponse() throws Exception {
+        int taskId = 1;
+        int sipResponse = 200;
+        Uri contact = Uri.fromParts("sip", "test", null);
+        RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+        RcsContactUceCapability capability = builder.build();
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        doAnswer(invocation -> {
+            uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+                    taskId);
+            uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+            return null;
+        }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+        // Request publication
+        int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+        assertEquals(ResultCode.SUCCESS, result);
+        verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+        verify(mPresencePublication).onSipResponse(taskId, sipResponse, null);
+    }
+
+    @Test
+    public void testRequestPublicationWithFailedResponse() throws Exception {
+        int taskId = 1;
+        Uri contact = Uri.fromParts("sip", "test", null);
+        RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+        RcsContactUceCapability capability = builder.build();
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        doAnswer(invocation -> {
+            uceImpl.mRcsFeatureCallback.onCommandUpdate(
+                    RcsCapabilityExchange.COMMAND_CODE_GENERIC_FAILURE, taskId);
+            return null;
+        }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+        // Request publication
+        int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+        assertEquals(ResultCode.SUCCESS, result);
+        verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId,
+                ResultCode.PUBLISH_GENERIC_FAILURE);
+    }
+
+    @Test
+    public void testRequestCapability() throws Exception {
+        int taskId = 1;
+        int sipResponse = 200;
+        List<RcsContactUceCapability> infos = new ArrayList<>();
+        List<Uri> contacts = Arrays.asList(Uri.fromParts("sip", "00000", null));
+        IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        when(mPresenceSubscriber.requestCapability(anyList(), any())).thenReturn(taskId);
+
+        doAnswer(invocation -> {
+            uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+                    taskId);
+            uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+            uceImpl.mRcsFeatureCallback.onCapabilityRequestResponsePresence(infos, taskId);
+            return null;
+        }).when(mRcsFeatureManager).requestCapabilities(anyList(), anyInt());
+
+        uceImpl.requestCapabilities(contacts, callback);
+        uceImpl.requestCapability(new String[] {"00000"}, taskId);
+
+        verify(mPresenceSubscriber).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+        verify(mPresenceSubscriber).onSipResponse(taskId, sipResponse, null);
+        verify(mPresenceSubscriber).updatePresences(taskId, infos, true, null);
+    }
+
+    @Test
+    public void testUpdatePublisherState() throws Exception {
+        IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+        doAnswer(invocation -> {
+            callback.onPublishStateChanged(anyInt());
+            return null;
+        }).when(mPublishStateCallbacks).broadcast(any());
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+        uceImpl.registerPublishStateCallback(callback);
+        uceImpl.updatePublisherState(PresenceBase.PUBLISH_STATE_200_OK);
+
+        assertEquals(PresenceBase.PUBLISH_STATE_200_OK, uceImpl.getPublisherState());
+        verify(callback).onPublishStateChanged(anyInt());
+    }
+
+    @Test
+    public void testUnpublish() throws Exception {
+        IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+        doAnswer(invocation -> {
+            callback.onPublishStateChanged(anyInt());
+            return null;
+        }).when(mPublishStateCallbacks).broadcast(any());
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+        uceImpl.mRcsFeatureCallback.onUnpublish();
+        waitForMs(1000);
+
+        verify(mPresencePublication).setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+    }
+
+    private UserCapabilityExchangeImpl createUserCapabilityExchangeImpl() throws Exception {
+        HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+        handlerThread.start();
+        mLooper = handlerThread.getLooper();
+        UserCapabilityExchangeImpl uceImpl = new UserCapabilityExchangeImpl(mContext, mSlotId,
+                mSubId, mLooper, mPresencePublication, mPresenceSubscriber,
+                mPublishStateCallbacks);
+        verify(mPresencePublication).handleAssociatedSubscriptionChanged(1);
+        verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(1);
+        waitForHandlerAction(uceImpl.getHandler(), 1000);
+        verify(mImsMmTelManager, atLeast(1)).registerImsRegistrationCallback(
+                any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+        verify(mContext).registerReceiver(any(), any());
+        return uceImpl;
+    }
+}