Merge "Add RCS metrics of sipDelegate/sipDelegateFeatureTags"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index da61c80..3831b6b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,14 +12,6 @@
       "name": "CarrierAppIntegrationTestCases"
     },
     {
-      "name": "CtsTelephonySdk28TestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
       "name": "CtsSimRestrictedApisTestCases",
       "options": [
         {
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 43f4d95..8c257f3 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -534,7 +534,7 @@
     <string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"ভইচমেইলৰ নম্বৰ অজ্ঞাত"</string>
     <string name="notification_network_selection_title" msgid="255595526707809121">"কোনো সেৱা নাই"</string>
     <string name="notification_network_selection_text" msgid="553288408722427659">"বাছনি কৰা (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) নেটৱৰ্কটো উপলব্ধ নহয়"</string>
-    <string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"কল কৰিবৰ কাৰণে ম’বাইল নেটৱৰ্ক অন কৰক, এয়াৰপ্লেইন ম\'ড অফ কৰক বা বেটাৰি সঞ্চয়কাৰী ম\'ড অফ কৰক।"</string>
+    <string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"কল কৰিবৰ কাৰণে ম’বাইল নেটৱৰ্ক অন কৰক, এয়াৰপ্লেইন ম’ড অফ কৰক বা বেটাৰী সঞ্চয়কাৰী ম’ড অফ কৰক।"</string>
     <string name="incall_error_power_off" product="default" msgid="8131672264311208673">"কল কৰিবৰ কাৰণে এয়াৰপ্লেইন ম\'ড অফ কৰক।"</string>
     <string name="incall_error_power_off_wfc" msgid="9125661184694727052">"কল কৰিবৰ কাৰণে এয়াৰপ্লেইন ম\'ড অফ কৰক বা কোনো বেতাঁৰ নেটৱৰ্কৰ সৈতে সংযোগ কৰক।"</string>
     <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ফ’নটো বেছি গৰম হৈছে"</b>\n\n"এই কলটো সম্পূৰ্ণ কৰিব নোৱাৰি। আপোনাৰ ফ’নটো ঠাণ্ডা হোৱাৰ পাছত পুনৰ চেষ্টা কৰক।\n\nআপুনি তথাপি জৰুৰীকালীন কল কৰিব পাৰিব।"</string>
@@ -689,8 +689,8 @@
     <string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"কেইবাটাও কল ইতিমধ্যে সক্ৰিয় হৈ আছে। নতুন কল এটা কৰাৰ আগেয়ে অনুগ্ৰহ কৰি সেইবোৰ একেলগ কৰক বা সমাপ্ত কৰক।"</string>
     <string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"সংযোগ কৰিব পৰা নাই, অনুগ্ৰহ কৰি এখন মান্য ছিম কাৰ্ড ভৰাওক।"</string>
     <string name="callFailed_wifi_lost" msgid="1788036730589163141">"ৱাই-ফাইৰ সৈতে সংযোগ বিচ্ছিন্ন হৈছে। কলৰ অন্ত পৰিছে।"</string>
-    <string name="dialFailed_low_battery" msgid="6857904237423407056">"বেটাৰি কম থকাৰ বাবে ভিডিঅ\' কল কৰিব নোৱাৰি।"</string>
-    <string name="callFailed_low_battery" msgid="4056828320214416182">"বেটাৰি কম থকাৰ বাবে ভিডিঅ\' কলৰ অন্ত পৰিছে।"</string>
+    <string name="dialFailed_low_battery" msgid="6857904237423407056">"বেটাৰী কম থকাৰ বাবে ভিডিঅ’ কল কৰিব নোৱাৰি।"</string>
+    <string name="callFailed_low_battery" msgid="4056828320214416182">"বেটাৰী কম থকাৰ বাবে ভিডিঅ’ কল সমাপ্ত হৈছে।"</string>
     <string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"এই অৱস্থানটোত ৱাই-ফাই কলিঙৰ জৰিয়তে জৰুৰীকালীন কল কৰাৰ সুবিধা উপলব্ধ নহয়।"</string>
     <string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"এই অৱস্থানটোত ৱাই-ফাই কলিং উপলব্ধ নহয়।"</string>
     <string name="change_pin_title" msgid="3564254326626797321">"ভইচমেইলৰ পিন সলনি কৰক"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index a5c5e86..7738bcd 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -310,7 +310,7 @@
     <string name="sim_change_data_title" msgid="9142726786345906606">"Promijeniti SIM za prijenos podataka?"</string>
     <string name="sim_change_data_message" msgid="3567358694255933280">"Koristiti SIM karticu <xliff:g id="NEW_SIM">%1$s</xliff:g> umjesto SIM kartice <xliff:g id="OLD_SIM">%2$s</xliff:g> za prijenos podataka na mobilnoj mreži?"</string>
     <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Pozivanje putem WiFi-ja"</string>
-    <string name="video_calling_settings_title" msgid="342829454913266078">"Operater video pozivanja"</string>
+    <string name="video_calling_settings_title" msgid="342829454913266078">"Video pozivi putem operatera"</string>
     <string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS opcije"</string>
     <string name="cdma_options" msgid="3669592472226145665">"CDMA opcije"</string>
     <string name="throttle_data_usage" msgid="1944145350660420711">"Korištenje podataka"</string>
@@ -839,7 +839,7 @@
     <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Omogućite vezu za prijenos podataka"</string>
     <string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Onemogući vezu za prijenos podataka"</string>
     <string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE omogućen"</string>
-    <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video poziv obezbijeđen"</string>
+    <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video pozivi su omogućeni"</string>
     <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"WiFi poziv obezbijeđen"</string>
     <string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/Omogućeno prisustvo"</string>
     <string name="cbrs_data_switch_string" msgid="6060356430838077653">"Cbrs podaci"</string>
@@ -858,7 +858,7 @@
     <string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Nije registrirano"</string>
     <string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Dostupno"</string>
     <string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Nedostupno"</string>
-    <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivanje: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+    <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivi: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
     <string name="radioInfo_service_in" msgid="45753418231446400">"Aktivno"</string>
     <string name="radioInfo_service_out" msgid="287972405416142312">"Ne radi"</string>
     <string name="radioInfo_service_emergency" msgid="4763879891415016848">"Samo hitni pozivi"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index b2a4e2e..5e5a3c5 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -63,18 +63,18 @@
     <string name="labelCdmaMore_with_label" msgid="7759692829160238152">"CDMA қоңырау параметрлері (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
     <string name="apn_settings" msgid="1978652203074756623">"APN"</string>
     <string name="settings_label" msgid="9101778088412567956">"Желі параметрлері"</string>
-    <string name="phone_accounts" msgid="1216879437523774604">"Қоңырау шалу есептік жазбалары"</string>
+    <string name="phone_accounts" msgid="1216879437523774604">"Қоңырау шалу аккаунттары"</string>
     <string name="phone_accounts_make_calls_with" msgid="16747814788918145">"Келесінің көмегімен қоңыраулар шалу"</string>
     <string name="phone_accounts_make_sip_calls_with" msgid="4691221006731847255">"Келесінің көмегімен SIP қоңырауларын шалу"</string>
     <string name="phone_accounts_ask_every_time" msgid="6192347582666047168">"Алдымен сұрау"</string>
     <string name="phone_accounts_default_account_label" msgid="5107598881335931101">"Қолжетімді желі жоқ"</string>
     <string name="phone_accounts_settings_header" msgid="6296501692964706536">"Параметрлер"</string>
-    <string name="phone_accounts_choose_accounts" msgid="4748805293314824974">"Есептік жазбаларды таңдау"</string>
-    <string name="phone_accounts_selection_header" msgid="2945830843104108440">"Телефон есептік жазбалары"</string>
-    <string name="phone_accounts_add_sip_account" msgid="1437634802033309305">"SIP есептік жазбасын қосу"</string>
-    <string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Есептік жазба параметрлерін конфигурациялау"</string>
-    <string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Барлық қоңырау шалу есептік жазбалары"</string>
-    <string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Қай есептік жазбалардың қоңырау шала алатынын таңдау"</string>
+    <string name="phone_accounts_choose_accounts" msgid="4748805293314824974">"Аккаунттарды таңдау"</string>
+    <string name="phone_accounts_selection_header" msgid="2945830843104108440">"Телефон аккаунттары"</string>
+    <string name="phone_accounts_add_sip_account" msgid="1437634802033309305">"SIP аккаунтын қосу"</string>
+    <string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Аккаунт параметрлерін конфигурациялау"</string>
+    <string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Барлық қоңырау шалу аккаунттары"</string>
+    <string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Қай аккаунттардың қоңырау шала алатынын таңдау"</string>
     <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi қоңыраулары"</string>
     <string name="connection_service_default_label" msgid="7332739049855715584">"Кірістірілген байланыс қызметі"</string>
     <string name="voicemail" msgid="7697769412804195032">"Дауыстық пошта"</string>
@@ -294,8 +294,8 @@
     <string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM картасының жұмысы шектеулі"</string>
     <string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> нөмірін пайдаланған кезде <xliff:g id="CARRIER_NAME">%1$s</xliff:g> қоңыраулары мен дерек тасымалдау қызметтері бөгелуі мүмкін."</string>
     <string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Екінші SIM картасын пайдаланылғанда, <xliff:g id="CARRIER_NAME">%1$s</xliff:g> қоңырауы мен дерек қызметі бөгелуі мүмкін."</string>
-    <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Қолданыстан шыққан SIP есептік жазбалары табылды және өшірілді"</string>
-    <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android платформасында бұдан былай SIP арқылы қоңырау шалу мүмкін емес.\nҚазіргі SIP есептік жазбаларыңыз (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) өшірілді.\nҚоңырау шалуға қолданылатын әдепкі есептік жазба параметрін растаңыз."</string>
+    <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Қолданыстан шыққан SIP аккаунттары табылды және өшірілді"</string>
+    <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android платформасында бұдан былай SIP арқылы қоңырау шалу мүмкін емес.\nҚазіргі SIP аккаунтларыңыз (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) өшірілді.\nҚоңырау шалуға қолданылатын әдепкі аккаунт параметрін растаңыз."</string>
     <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Параметрлерге өту"</string>
     <string name="data_usage_title" msgid="8438592133893837464">"Қолданба деректерінің трафигі"</string>
     <string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> аралығында <xliff:g id="ID_1">%1$s</xliff:g> мобильдік дерек қолданылды"</string>
@@ -926,5 +926,5 @@
     <string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Операторды инциализациялауды іске қосу"</string>
     <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth сигналы нашар. Спикерфонға ауысып көріңіз."</string>
     <string name="call_quality_notification_name" msgid="3476828289553948830">"Қоңырау сапасы туралы хабарландыру"</string>
-    <string name="notification_channel_sip_account" msgid="1261816025156179637">"Қолданыстан шыққан SIP есептік жазбалары"</string>
+    <string name="notification_channel_sip_account" msgid="1261816025156179637">"Қолданыстан шыққан SIP аккаунттары"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 8ec61fa..3e76079 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -340,22 +340,22 @@
     <string name="maintenance_disable" msgid="2121032601497725602">"അറ്റകുറ്റപ്പണി സേവനങ്ങൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="general_news_settings" msgid="2670499575962080411">"പൊതുവായ വാർത്തകൾ"</string>
     <string name="bf_news_settings" msgid="8571709425370794221">"ബിസിനസ്സ്, സാമ്പത്തിക വാർത്തകൾ"</string>
-    <string name="sports_news_settings" msgid="2684364556989168438">"കായിക വാർത്ത"</string>
-    <string name="entertainment_news_settings" msgid="4228527702346305543">"വിനോദവാർത്ത"</string>
+    <string name="sports_news_settings" msgid="2684364556989168438">"കായിക വാർത്തകൾ"</string>
+    <string name="entertainment_news_settings" msgid="4228527702346305543">"വിനോദ വാർത്തകൾ"</string>
     <string name="enable_disable_local" msgid="7654175079979415572">"പ്രാദേശികം"</string>
-    <string name="local_enable" msgid="790606890868710629">"പ്രാദേശിക വാർത്ത പ്രവർത്തനക്ഷമമാക്കി"</string>
-    <string name="local_disable" msgid="7649945293198602877">"പ്രാദേശിക വാർത്ത പ്രവർത്തനരഹിതമാക്കി"</string>
+    <string name="local_enable" msgid="790606890868710629">"പ്രാദേശിക വാർത്തകൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
+    <string name="local_disable" msgid="7649945293198602877">"പ്രാദേശിക വാർത്തകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="enable_disable_regional" msgid="5783403191376564638">"പ്രാദേശികം"</string>
     <string name="regional_enable" msgid="7730109417536296079">"പ്രാദേശിക വാർത്തകൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="regional_disable" msgid="3781951818157772545">"പ്രാദേശിക വാർത്തകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="enable_disable_national" msgid="6198481711934897632">"ദേശീയം"</string>
-    <string name="national_enable" msgid="5159683504138239304">"ദേശീയ വാർത്ത പ്രവർത്തനക്ഷമമാക്കി"</string>
-    <string name="national_disable" msgid="8484356368757118987">"ദേശീയ വാർത്ത പ്രവർത്തനരഹിതമാക്കി"</string>
+    <string name="national_enable" msgid="5159683504138239304">"ദേശീയ വാർത്തകൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
+    <string name="national_disable" msgid="8484356368757118987">"ദേശീയ വാർത്തകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="enable_disable_international" msgid="4204334217211198792">"അന്തര്‍‌ദ്ദേശീയം"</string>
-    <string name="international_enable" msgid="8943466745792690340">"അന്തർദ്ദേശീയ വാർത്ത പ്രവർത്തനക്ഷമമാക്കി"</string>
+    <string name="international_enable" msgid="8943466745792690340">"അന്തർദ്ദേശീയ വാർത്തകൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="international_disable" msgid="4803498658100318265">"അന്തർദ്ദേശീയ വാർത്തകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="list_language_title" msgid="1850167908665485738">"ഭാഷ"</string>
-    <string name="list_language_summary" msgid="7921756070782277559">"വാർത്താഭാഷ തിരഞ്ഞെടുക്"</string>
+    <string name="list_language_summary" msgid="7921756070782277559">"വാർത്താഭാഷ തിരഞ്ഞെടുക്കുക"</string>
   <string-array name="list_language_entries">
     <item msgid="2347238508726934281">"ഇംഗ്ലീഷ്"</item>
     <item msgid="5172468397620875174">"ഫ്രഞ്ച്"</item>
@@ -405,9 +405,9 @@
     <string name="enable_disable_mhh" msgid="715930476289202466">"മെഡിക്കൽ, ആരോഗ്യം, ആശുപത്രി എന്നിവ"</string>
     <string name="mhh_enable" msgid="7224396815285147008">"മെഡിക്കൽ, ആരോഗ്യം, ആശുപത്രി എന്നിവ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="mhh_disable" msgid="5503643028885686265">"മെഡിക്കൽ, ആരോഗ്യം, ആശുപത്രി എന്നിവ പ്രവർത്തനരഹിതമാക്കി"</string>
-    <string name="enable_disable_technology_news" msgid="2794845609698078400">"സാങ്കേതിക വാർത്ത"</string>
-    <string name="technology_news_enable" msgid="1908991199492598311">"സാങ്കേതിക വാർത്ത പ്രവർത്തനക്ഷമമാക്കി"</string>
-    <string name="technology_news_disable" msgid="8388582607149800889">"സാങ്കേതിക വാർത്ത പ്രവർത്തനരഹിതമാക്കി"</string>
+    <string name="enable_disable_technology_news" msgid="2794845609698078400">"സാങ്കേതിക വാർത്തകൾ"</string>
+    <string name="technology_news_enable" msgid="1908991199492598311">"സാങ്കേതിക വാർത്തകൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
+    <string name="technology_news_disable" msgid="8388582607149800889">"സാങ്കേതിക വാർത്തകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="enable_disable_multi_category" msgid="5958248155437940625">"ഒന്നിലധികം വിഭാഗം"</string>
     <string name="multi_category_enable" msgid="4531915767817483960">"ഒന്നിലധികം വിഭാഗം പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="multi_category_disable" msgid="6325934413701238104">"ഒന്നിലധികം വിഭാഗം പ്രവർത്തനരഹിതമാക്കി"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 142d1e1..e16c4fa 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -596,7 +596,7 @@
     <string name="hac_mode_summary" msgid="7774989500136009881">"Slå på kompatibilitet med høreapparater"</string>
     <string name="rtt_mode_title" msgid="3075948111362818043">"Sanntidstekst-anrop (STT)"</string>
     <string name="rtt_mode_summary" msgid="8631541375609989562">"Tillat meldingsutveksling i talesamtaler"</string>
-    <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, tunghørte, har talefunksjonshemning eller trenger mer enn bare tale.&lt;br&gt; &lt;a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>&gt;Finn ut mer&lt;/a&gt;\n       &lt;br&gt;&lt;br&gt; – STT-anrop lagres som meldingstranskripsjoner\n       &lt;br&gt; – STT er ikke tilgjengelig for videoanrop"</string>
+    <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, hørselshemmede, har taleproblemer eller trenger mer enn bare tale.&lt;br&gt; &lt;a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>&gt;Finn ut mer&lt;/a&gt;\n       &lt;br&gt;&lt;br&gt; – STT-anrop lagres som meldingstranskripsjoner\n       &lt;br&gt; – STT er ikke tilgjengelig for videoanrop"</string>
     <string name="no_rtt_when_roaming" msgid="5268008247378355389">"Merk: RTT er ikke tilgjengelig ved roaming"</string>
   <string-array name="tty_mode_entries">
     <item msgid="3238070884803849303">"TTY av"</item>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6ff98c6..850bfbb 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -125,9 +125,9 @@
     <string name="sum_cfnrc_disabled" msgid="739289696796917683">"Uit"</string>
     <string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Je provider biedt geen ondersteuning voor het uitschakelen van oproepdoorschakelingen wanneer je telefoon niet bereikbaar is."</string>
     <string name="registration_cf_forbidden" msgid="4386482610771190420">"Je provider ondersteunt het doorschakelen van gesprekken niet."</string>
-    <string name="cdma_call_waiting" msgid="4565070960879673216">"Wisselgesprek inschakelen?"</string>
+    <string name="cdma_call_waiting" msgid="4565070960879673216">"Wisselgesprek aanzetten?"</string>
     <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tijdens een gesprek krijg je een melding over inkomende gesprekken"</string>
-    <string name="enable_cdma_cw" msgid="811047045863422232">"Inschakelen"</string>
+    <string name="enable_cdma_cw" msgid="811047045863422232">"Aanzetten"</string>
     <string name="disable_cdma_cw" msgid="7119290446496301734">"Annuleren"</string>
     <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA-wisselgesprek onder IMS aan"</string>
     <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA-wisselgesprek onder IMS uit"</string>
@@ -147,7 +147,7 @@
     <string name="fdn_check_failure" msgid="1833769746374185247">"De instelling \'Vaste nummers\' in je app Telefoon is ingeschakeld. Hierdoor werken sommige oproepgerelateerde functies niet."</string>
     <string name="radio_off_error" msgid="8321564164914232181">"Schakel de radio in voordat je deze instellingen bekijkt."</string>
     <string name="close_dialog" msgid="1074977476136119408">"OK"</string>
-    <string name="enable" msgid="2636552299455477603">"Inschakelen"</string>
+    <string name="enable" msgid="2636552299455477603">"Aanzetten"</string>
     <string name="disable" msgid="1122698860799462116">"Uit"</string>
     <string name="change_num" msgid="6982164494063109334">"Updaten"</string>
   <string-array name="clir_display_values">
@@ -443,20 +443,20 @@
     <string name="fdn_activation" msgid="2178637004710435895">"FDN-activering"</string>
     <string name="fdn_enabled" msgid="7017355494808056447">"Vaste nummers zijn ingeschakeld"</string>
     <string name="fdn_disabled" msgid="6696468878037736600">"Vaste nummers zijn uitgeschakeld"</string>
-    <string name="enable_fdn" msgid="4830555730418033723">"FDN inschakelen"</string>
+    <string name="enable_fdn" msgid="4830555730418033723">"FDN aanzetten"</string>
     <string name="disable_fdn" msgid="3918794950264647541">"FDN uitschakelen"</string>
     <string name="change_pin2" msgid="3110844547237754871">"PIN2-code wijzigen"</string>
     <string name="enable_fdn_ok" msgid="5080925177369329827">"FDN uitschakelen"</string>
-    <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN inschakelen"</string>
+    <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN aanzetten"</string>
     <string name="sum_fdn" msgid="6152246141642323582">"Vaste nummers beheren"</string>
     <string name="sum_fdn_change_pin" msgid="3510994280557335727">"Pincode voor FDN-toegang wijzigen"</string>
     <string name="sum_fdn_manage_list" msgid="3311397063233992907">"Lijst met telefoonnummers beheren"</string>
     <string name="voice_privacy" msgid="7346935172372181951">"Spraakprivacy"</string>
-    <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus inschakelen"</string>
+    <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus aanzetten"</string>
     <string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-modus"</string>
     <string name="tty_mode_option_summary" msgid="4770510287236494371">"TTY-modus instellen"</string>
     <string name="auto_retry_mode_title" msgid="2985801935424422340">"Automatisch opnieuw proberen"</string>
-    <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen inschakelen"</string>
+    <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen aanzetten"</string>
     <string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"De TTY-modus mag niet worden gewijzigd tijdens een videogesprek"</string>
     <string name="menu_add" msgid="5616487894975773141">"Contact toevoegen"</string>
     <string name="menu_edit" msgid="3593856941552460706">"Contact bewerken"</string>
@@ -593,7 +593,7 @@
     <string name="singleContactImportedMsg" msgid="3619804066300998934">"Contact geïmporteerd"</string>
     <string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Kan contact niet importeren"</string>
     <string name="hac_mode_title" msgid="4127986689621125468">"Hoortoestellen"</string>
-    <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel inschakelen"</string>
+    <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel aanzetten"</string>
     <string name="rtt_mode_title" msgid="3075948111362818043">"Realtime tekstoproep (RTT)"</string>
     <string name="rtt_mode_summary" msgid="8631541375609989562">"Berichten in een audiogesprek toestaan"</string>
     <string name="rtt_mode_more_information" msgid="587500128658756318">"RTT helpt bellers die doof of slechthorend zijn, een spraakbeperking hebben of meer dan alleen een stem nodig hebben.&lt;br&gt; &lt;a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>&gt;Meer informatie&lt;/a&gt;\n       &lt;br&gt;&lt;br&gt; - RTT-gesprekken worden opgeslagen als berichttranscript.\n       &lt;br&gt; - RTT is niet beschikbaar voor videogesprekken"</string>
@@ -673,8 +673,8 @@
     <string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Pincode wijzigen"</string>
     <string name="preference_category_ringtone" msgid="8787281191375434976">"Ringtone en trillen"</string>
     <string name="pstn_connection_service_label" msgid="9200102709997537069">"Ingebouwde simkaarten"</string>
-    <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken inschakelen"</string>
-    <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt inschakelen, moet je de geoptimaliseerde 4G LTE-modus inschakelen in de netwerkinstellingen."</string>
+    <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken aanzetten"</string>
+    <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt aanzetten, moet je de geoptimaliseerde 4G LTE-modus aanzetten in de netwerkinstellingen."</string>
     <string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Netwerkinstellingen"</string>
     <string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Sluiten"</string>
     <string name="sim_label_emergency_calls" msgid="9078241989421522310">"Noodoproepen"</string>
@@ -833,14 +833,14 @@
     <string name="supp_service_over_ut_precautions_dual_sim" msgid="5166866975550910474">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
     <string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
     <string name="supp_service_over_ut_precautions_dialog_dismiss" msgid="5934541487903081652">"Sluiten"</string>
-    <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding inschakelen"</string>
+    <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding aanzetten"</string>
     <string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Dataverbinding uitschakelen"</string>
     <string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE-registratie"</string>
     <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Videogesprekken geregistreerd"</string>
     <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Wifi-gesprekken geregistreerd"</string>
     <string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/aanwezigheid geregistreerd"</string>
     <string name="cbrs_data_switch_string" msgid="6060356430838077653">"CBRS-gegevens"</string>
-    <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS inschakelen"</string>
+    <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS aanzetten"</string>
     <string name="dsds_dialog_title" msgid="8494569893941847575">"Apparaat opnieuw opstarten?"</string>
     <string name="dsds_dialog_message" msgid="4047480385678538850">"Start het apparaat opnieuw op om de instelling te wijzigen."</string>
     <string name="dsds_dialog_confirm" msgid="9032004888134129885">"Opnieuw opstarten"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 65376c9..f0da34a 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -451,8 +451,8 @@
     <string name="sum_fdn" msgid="6152246141642323582">"Hantera fasta nummer"</string>
     <string name="sum_fdn_change_pin" msgid="3510994280557335727">"Ändra PIN-kod för FDN-åtkomst"</string>
     <string name="sum_fdn_manage_list" msgid="3311397063233992907">"Hantera nummerlistan"</string>
-    <string name="voice_privacy" msgid="7346935172372181951">"Sekretess för Voice"</string>
-    <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat sekretessläge"</string>
+    <string name="voice_privacy" msgid="7346935172372181951">"Integritet för Voice"</string>
+    <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat integritetsläge"</string>
     <string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-läge"</string>
     <string name="tty_mode_option_summary" msgid="4770510287236494371">"Ange TTY-läge"</string>
     <string name="auto_retry_mode_title" msgid="2985801935424422340">"Försök igen automatiskt"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 7125e6a..6353cb4 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -664,7 +664,7 @@
     <string name="contactPhoto" msgid="7885089213135154834">"కాంటాక్ట్ ఫోటో"</string>
     <string name="goPrivate" msgid="4645108311382209551">"ప్రైవేట్‌గా వెళ్లు"</string>
     <string name="selectContact" msgid="1527612842599767382">"కాంటాక్ట్‌ను ఎంచుకోండి"</string>
-    <string name="not_voice_capable" msgid="2819996734252084253">"వాయిస్ కాలింగ్‌కు మద్దతు లేదు"</string>
+    <string name="not_voice_capable" msgid="2819996734252084253">"వాయిస్ కాలింగ్‌కు సపోర్ట్ లేదు"</string>
     <string name="description_dial_button" msgid="8614631902795087259">"డయల్ చేయి"</string>
     <string name="description_dialpad_button" msgid="7395114120463883623">"డయల్‌ప్యాడ్‌ను చూపు"</string>
     <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"అత్యవసర డయల్‌ప్యాడ్"</string>
@@ -890,9 +890,9 @@
     <string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"బూట్ చేసినప్పటి నుండి PPP రీసెట్ సంఖ్య:"</string>
     <string name="radio_info_current_network_label" msgid="3052098695239642450">"ప్రస్తుత నెట్‌వర్క్:"</string>
     <string name="radio_info_ppp_received_label" msgid="5753592451640644889">"స్వీకరించిన డేటా:"</string>
-    <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"వాయిస్ సేవ:"</string>
+    <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"వాయిస్ సర్వీస్:"</string>
     <string name="radio_info_signal_strength_label" msgid="5545444702102543260">"సిగ్నల్ సామర్థ్యం:"</string>
-    <string name="radio_info_call_status_label" msgid="7693575431923095487">"వాయిస్ కాల్ స్థితి:"</string>
+    <string name="radio_info_call_status_label" msgid="7693575431923095487">"వాయిస్ కాల్ స్టేటస్:"</string>
     <string name="radio_info_ppp_sent_label" msgid="6542208429356199695">"పంపిన డేటా:"</string>
     <string name="radio_info_message_waiting_label" msgid="1886549432566952078">"సందేశ నిరీక్షణ:"</string>
     <string name="radio_info_phone_number_label" msgid="2533852539562512203">"ఫోన్ నంబర్:"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 04c984e..f1770e1 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -579,7 +579,7 @@
     <string name="onscreenEndCallText" msgid="6138725377654842757">"สิ้นสุด"</string>
     <string name="onscreenShowDialpadText" msgid="658465753816164079">"แป้นหมายเลข"</string>
     <string name="onscreenMuteText" msgid="5470306116733843621">"ปิดเสียง"</string>
-    <string name="onscreenAddCallText" msgid="9075675082903611677">"เพิ่มการโทร"</string>
+    <string name="onscreenAddCallText" msgid="9075675082903611677">"เพิ่มสาย"</string>
     <string name="onscreenMergeCallsText" msgid="3692389519611225407">"รวมสาย"</string>
     <string name="onscreenSwapCallsText" msgid="2682542150803377991">"สลับ"</string>
     <string name="onscreenManageCallsText" msgid="1162047856081836469">"จัดการการโทร"</string>
diff --git a/sip/res/values-as/strings.xml b/sip/res/values-as/strings.xml
index 76a0b5f..13043e3 100644
--- a/sip/res/values-as/strings.xml
+++ b/sip/res/values-as/strings.xml
@@ -20,7 +20,7 @@
     <string name="sip_accounts" msgid="7297896885665783239">"SIP একাউণ্টসমূহ"</string>
     <string name="sip_accounts_title" msgid="3061686404598143943">"একাউণ্টসমূহ"</string>
     <string name="sip_receive_calls" msgid="3403644006618369349">"অন্তৰ্গামী কল লাভ কৰি থকা হৈছে"</string>
-    <string name="sip_receive_calls_summary" msgid="5306603671778761443">"বেটাৰি অধিক ব্যৱহাৰ কৰে"</string>
+    <string name="sip_receive_calls_summary" msgid="5306603671778761443">"বেটাৰী অধিক ব্যৱহাৰ কৰে"</string>
     <string name="sip_call_options_title" msgid="5027066677561068192">"SIP কলিং ব্যৱহাৰ কৰক"</string>
     <string name="sip_call_options_wifi_only_title" msgid="6663105297927456484">"SIP কলিং ব্যৱহাৰ কৰক (কেৱল ৱাই-ফাই)"</string>
     <string name="sip_call_options_entry_1" msgid="4722647332760934261">"ডেটা নেটৱৰ্ক উপলব্ধ থাকোঁতে সকলো কলৰ বাবে"</string>
diff --git a/sip/res/values-kk/strings.xml b/sip/res/values-kk/strings.xml
index 1e59711..2f90a2a 100644
--- a/sip/res/values-kk/strings.xml
+++ b/sip/res/values-kk/strings.xml
@@ -17,8 +17,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="sip_settings" msgid="7452587325305604702">"SIP параметрлері"</string>
-    <string name="sip_accounts" msgid="7297896885665783239">"SIP есептік жазбалары"</string>
-    <string name="sip_accounts_title" msgid="3061686404598143943">"Есептік жазбалар"</string>
+    <string name="sip_accounts" msgid="7297896885665783239">"SIP аккаунттары"</string>
+    <string name="sip_accounts_title" msgid="3061686404598143943">"Аккаунттар"</string>
     <string name="sip_receive_calls" msgid="3403644006618369349">"Кіріс қоңырауларды қабылдау"</string>
     <string name="sip_receive_calls_summary" msgid="5306603671778761443">"Көбірек батарея қуатын пайдаланады"</string>
     <string name="sip_call_options_title" msgid="5027066677561068192">"SIP қызметін пайдалану"</string>
@@ -26,11 +26,11 @@
     <string name="sip_call_options_entry_1" msgid="4722647332760934261">"Деректер желісі қол жетімді болғанда барлық қоңыраулар үшін"</string>
     <string name="sip_call_options_entry_2" msgid="7338504256051655013">"Тек SIP қоңыраулары үшін"</string>
     <string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"Барлық қоңыраулар үшін"</string>
-    <string name="add_sip_account" msgid="5754758646745144384">"Есептік жазба қосу"</string>
-    <string name="remove_sip_account" msgid="8272617403399636513">"Есептік жазбаны жою"</string>
-    <string name="sip_account_list" msgid="2596262496233721769">"SIP есептік жазбалары"</string>
-    <string name="saving_account" msgid="3390358043846687266">"Есептік жазба сақталуда…"</string>
-    <string name="removing_account" msgid="1544132880414780408">"Есептік жазба жойылуда…"</string>
+    <string name="add_sip_account" msgid="5754758646745144384">"Аккаунт қосу"</string>
+    <string name="remove_sip_account" msgid="8272617403399636513">"Аккаунтты жою"</string>
+    <string name="sip_account_list" msgid="2596262496233721769">"SIP аккаунттары"</string>
+    <string name="saving_account" msgid="3390358043846687266">"Аккаунт сақталуда…"</string>
+    <string name="removing_account" msgid="1544132880414780408">"Аккаунт жойылуда…"</string>
     <string name="sip_menu_save" msgid="4377112554203123060">"Сақтау"</string>
     <string name="sip_menu_discard" msgid="1883166691772895243">"Алып тастау"</string>
     <string name="alert_dialog_close" msgid="1734746505531110706">"Профильді жабу"</string>
@@ -40,16 +40,16 @@
     <string name="registration_status_registering" msgid="7986331597809521791">"Тіркелуде…"</string>
     <string name="registration_status_still_trying" msgid="7178623685868766282">"Әлі әрекеттенуде…"</string>
     <string name="registration_status_not_receiving" msgid="3873074208531938401">"Қоңыраулар қабылданып жатқан жоқ."</string>
-    <string name="registration_status_no_data" msgid="2987064560116584121">"Есептік жазбаны тіркеу тоқтатылды, себебі интернет байланысы жоқ."</string>
-    <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Есептік жазбаны тіркеу тоқтатылды, себебі Wi-Fi байланысы жоқ."</string>
-    <string name="registration_status_not_running" msgid="6236403137652262659">"Есептік жазба тіркеу сәтсіз аяқталды."</string>
+    <string name="registration_status_no_data" msgid="2987064560116584121">"Аккаунтты тіркеу тоқтатылды, себебі интернет байланысы жоқ."</string>
+    <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Аккаунтты тіркеу тоқтатылды, себебі Wi-Fi байланысы жоқ."</string>
+    <string name="registration_status_not_running" msgid="6236403137652262659">"Аккаунт тіркеу сәтсіз аяқталды."</string>
     <string name="registration_status_done" msgid="6787397199273357721">"Қоңыраулар қабылдануда."</string>
-    <string name="registration_status_failed_try_later" msgid="7855389184910312091">"Есептік жазбаны тіркеу сәтсіз аяқталды: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); әрекет кейінірек қайталанады"</string>
-    <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"Есептік жазбаны тіркеу сәтсіз аяқталды: Пайдаланушы аты немесе құпия сөз қате."</string>
-    <string name="registration_status_server_unreachable" msgid="3832339558868965604">"Есептік жазба тіркеу сәтсіз аяқталды: Сервер атауын тексеріңіз."</string>
-    <string name="third_party_account_summary" msgid="5918779106950859167">"Бұл есептік жазбаны қазіргі уақытта <xliff:g id="ACCOUNT_OWNER">%s</xliff:g> қолданбасы пайдалануда."</string>
-    <string name="sip_edit_title" msgid="7438891546610820307">"SIP есептік жазбасы туралы мәліметтер"</string>
-    <string name="sip_edit_new_title" msgid="8394790068979636381">"SIP есептік жазбасы туралы мәліметтер"</string>
+    <string name="registration_status_failed_try_later" msgid="7855389184910312091">"Аккаунтты тіркеу сәтсіз аяқталды: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); әрекет кейінірек қайталанады"</string>
+    <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"Аккаунтты тіркеу сәтсіз аяқталды: Пайдаланушы аты немесе құпия сөз қате."</string>
+    <string name="registration_status_server_unreachable" msgid="3832339558868965604">"Аккаунт тіркеу сәтсіз аяқталды: Сервер атауын тексеріңіз."</string>
+    <string name="third_party_account_summary" msgid="5918779106950859167">"Бұл аккаунтты қазіргі уақытта <xliff:g id="ACCOUNT_OWNER">%s</xliff:g> қолданбасы пайдалануда."</string>
+    <string name="sip_edit_title" msgid="7438891546610820307">"SIP аккаунты туралы мәліметтер"</string>
+    <string name="sip_edit_new_title" msgid="8394790068979636381">"SIP аккаунты туралы мәліметтер"</string>
     <string name="domain_address_title" msgid="8238078615181248579">"Сервер"</string>
     <string name="username_title" msgid="298416796886107970">"Пайдаланушы аты"</string>
     <string name="password_title" msgid="8035579335591959021">"Құпия сөз"</string>
@@ -68,7 +68,7 @@
     <string name="optional_summary" msgid="620379377865437488">"&lt;Міндетті емес&gt;"</string>
     <string name="advanced_settings_show" msgid="2318728080037568529">"▷ Барлығын көрсету үшін түрту"</string>
     <string name="advanced_settings_hide" msgid="6200816937370652083">"▽ Барлығын жасыру үшін түрту"</string>
-    <string name="all_empty_alert" msgid="6085603517610199098">"Жаңа SIP есептік жазбасының мәліметтерін енгізіңіз."</string>
+    <string name="all_empty_alert" msgid="6085603517610199098">"Жаңа SIP аккаунтының мәліметтерін енгізіңіз."</string>
     <string name="empty_alert" msgid="3693655518612836718">"<xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> міндетті және оны бос қалдыруға болмайды."</string>
     <string name="not_a_valid_port" msgid="3664668836663491376">"Порт нөмірі 1000 және 65534 аралығында болуы керек."</string>
     <string name="no_internet_available" msgid="161720645084325479">"SIP қоңырауын шалу үшін алдымен интернет қосылымын тексеріңіз."</string>
diff --git a/sip/src/com/android/services/telephony/sip/SipEditor.java b/sip/src/com/android/services/telephony/sip/SipEditor.java
index dd475e6..9efe5bf 100644
--- a/sip/src/com/android/services/telephony/sip/SipEditor.java
+++ b/sip/src/com/android/services/telephony/sip/SipEditor.java
@@ -16,7 +16,6 @@
 
 package com.android.services.telephony.sip;
 
-import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.content.Intent;
@@ -37,6 +36,8 @@
 import android.widget.Button;
 import android.widget.Toast;
 
+import com.android.phone.FrameworksUtils;
+
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -93,7 +94,7 @@
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             String message = getArguments().getString(KEY_MESSAGE);
 
-            return new AlertDialog.Builder(getActivity())
+            return FrameworksUtils.makeAlertDialogBuilder(getActivity())
                     .setTitle(android.R.string.dialog_alert_title)
                     .setIconAttribute(android.R.attr.alertDialogIcon)
                     .setMessage(message)
diff --git a/sip/src/com/android/services/telephony/sip/SipSettings.java b/sip/src/com/android/services/telephony/sip/SipSettings.java
index 813ba51..5137963 100644
--- a/sip/src/com/android/services/telephony/sip/SipSettings.java
+++ b/sip/src/com/android/services/telephony/sip/SipSettings.java
@@ -17,7 +17,6 @@
 package com.android.services.telephony.sip;
 
 import android.app.ActionBar;
-import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -39,6 +38,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
+import com.android.phone.FrameworksUtils;
 import com.android.phone.R;
 
 import java.io.IOException;
@@ -287,7 +287,7 @@
             startSipEditor(profile);
             return;
         }
-        new AlertDialog.Builder(this)
+        FrameworksUtils.makeAlertDialogBuilder(this)
                 .setTitle(R.string.alert_dialog_close)
                 .setIconAttribute(android.R.attr.alertDialogIcon)
                 .setPositiveButton(R.string.close_profile,
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index ec6ea2b..d249fae 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -220,7 +220,7 @@
             if (mImsMgr.isEnhanced4gLteModeSettingEnabledByUser()) {
                 mImsMgr.setVtSetting((boolean) objValue);
             } else {
-                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(this);
                 DialogInterface.OnClickListener networkSettingsClickListener =
                         new Dialog.OnClickListener() {
                             @Override
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index bf296f9..db1c5b4 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -427,7 +427,8 @@
                                     default: // not reachable
                                         s = getContext().getText(R.string.disable_cfnrc_forbidden);
                                 }
-                                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+                                AlertDialog.Builder builder =
+                                        FrameworksUtils.makeAlertDialogBuilder(getContext());
                                 builder.setNeutralButton(R.string.close_dialog, null);
                                 builder.setTitle(getContext()
                                         .getText(R.string.error_updating_title));
@@ -440,7 +441,8 @@
                                 // Handle the fail-to-enable case.
                                 CharSequence s = getContext()
                                     .getText(R.string.registration_cf_forbidden);
-                                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+                                AlertDialog.Builder builder =
+                                        FrameworksUtils.makeAlertDialogBuilder(getContext());
                                 builder.setNeutralButton(R.string.close_dialog, null);
                                 builder.setTitle(getContext()
                                         .getText(R.string.error_updating_title));
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index a8ef220..7f61f78 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -276,23 +276,10 @@
         private int mState;
         // The possible tones we can play.
         public static final int TONE_NONE = 0;
-        public static final int TONE_CALL_WAITING = 1;
-        public static final int TONE_BUSY = 2;
-        public static final int TONE_CONGESTION = 3;
-        public static final int TONE_CALL_ENDED = 4;
         public static final int TONE_VOICE_PRIVACY = 5;
-        public static final int TONE_REORDER = 6;
-        public static final int TONE_INTERCEPT = 7;
-        public static final int TONE_CDMA_DROP = 8;
-        public static final int TONE_OUT_OF_SERVICE = 9;
-        public static final int TONE_REDIAL = 10;
-        public static final int TONE_OTA_CALL_END = 11;
-        public static final int TONE_UNOBTAINABLE_NUMBER = 13;
 
         // The tone volume relative to other sounds in the stream
-        static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
         static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
-        static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
 
         // Buffer time (in msec) to add on to tone timeout value.
         // Needed mainly when the timeout value for a tone is the
@@ -320,70 +307,11 @@
             int phoneType = mCM.getFgPhone().getPhoneType();
 
             switch (mToneId) {
-                case TONE_CALL_WAITING:
-                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
-                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                    // Call waiting tone is stopped by stopTone() method
-                    toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
-                    break;
-                case TONE_BUSY:
-                    if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
-                        toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
-                        toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
-                        toneLengthMillis = 1000;
-                    } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM
-                            || phoneType == PhoneConstants.PHONE_TYPE_SIP
-                            || phoneType == PhoneConstants.PHONE_TYPE_IMS
-                            || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) {
-                        toneType = ToneGenerator.TONE_SUP_BUSY;
-                        toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                        toneLengthMillis = 4000;
-                    } else {
-                        throw new IllegalStateException("Unexpected phone type: " + phoneType);
-                    }
-                    break;
-                case TONE_CONGESTION:
-                    toneType = ToneGenerator.TONE_SUP_CONGESTION;
-                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 4000;
-                    break;
-
-                case TONE_CALL_ENDED:
-                    toneType = ToneGenerator.TONE_PROP_PROMPT;
-                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 200;
-                    break;
                 case TONE_VOICE_PRIVACY:
                     toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
                     toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 5000;
                     break;
-                case TONE_REORDER:
-                    toneType = ToneGenerator.TONE_CDMA_REORDER;
-                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 4000;
-                    break;
-                case TONE_INTERCEPT:
-                    toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
-                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
-                    toneLengthMillis = 500;
-                    break;
-                case TONE_CDMA_DROP:
-                case TONE_OUT_OF_SERVICE:
-                    toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
-                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
-                    toneLengthMillis = 375;
-                    break;
-                case TONE_REDIAL:
-                    toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
-                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
-                    toneLengthMillis = 5000;
-                    break;
-                case TONE_UNOBTAINABLE_NUMBER:
-                    toneType = ToneGenerator.TONE_SUP_ERROR;
-                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 4000;
-                    break;
                 default:
                     throw new IllegalArgumentException("Bad toneId: " + mToneId);
             }
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index a463243..45ca974 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -65,6 +65,7 @@
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -1064,6 +1065,7 @@
         }
 
         String fileName;
+        String iccid = null;
         if (isNoSimConfig) {
             fileName = getFilenameForNoSimConfig(packageName);
         } else {
@@ -1073,7 +1075,7 @@
                 return null;
             }
 
-            final String iccid = getIccIdForPhoneId(phoneId);
+            iccid = getIccIdForPhoneId(phoneId);
             final int cid = getSpecificCarrierIdForPhoneId(phoneId);
             if (iccid == null) {
                 loge("Cannot restore config with null iccid.");
@@ -1102,7 +1104,15 @@
         } catch (FileNotFoundException e) {
             // Missing file is normal occurrence that might occur with a new sim or when restoring
             // an override file during boot and should not be treated as an error.
-            if (file != null) logd("File not found: " + file.getPath());
+            if (file != null) {
+                if (isNoSimConfig) {
+                    logd("File not found: " + file.getPath());
+                } else {
+                    String filePath = file.getPath();
+                    filePath = getFilePathForLogging(filePath, iccid);
+                    logd("File not found : " + filePath);
+                }
+            }
         } catch (IOException e) {
             loge(e.toString());
         }
@@ -1110,6 +1120,22 @@
         return restoredBundle;
     }
 
+    /**
+     * This method will mask most part of iccid in the filepath for logging on userbuild
+     */
+    private String getFilePathForLogging(String filePath, String iccid) {
+        // If loggable then return with actual file path
+        if (Rlog.isLoggable(LOG_TAG, Log.VERBOSE)) {
+            return filePath;
+        }
+        String path = filePath;
+        int length = (iccid != null) ? iccid.length() : 0;
+        if (length > 5 && filePath != null) {
+            path = filePath.replace(iccid.substring(5), "***************");
+        }
+        return path;
+    }
+
     private PersistableBundle restoreConfigFromXml(String packageName, @NonNull String extraString,
             int phoneId) {
         return restoreConfigFromXml(packageName, extraString, phoneId, false);
diff --git a/src/com/android/phone/CdmaCallWaitingPreference.java b/src/com/android/phone/CdmaCallWaitingPreference.java
index 3713b19..6ff47d2 100644
--- a/src/com/android/phone/CdmaCallWaitingPreference.java
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -78,7 +78,7 @@
     public void onClick() {
         super.onClick();
 
-        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+        AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(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() {
diff --git a/src/com/android/phone/ChangeIccPinScreen.java b/src/com/android/phone/ChangeIccPinScreen.java
index 70bf431..5369aa3 100644
--- a/src/com/android/phone/ChangeIccPinScreen.java
+++ b/src/com/android/phone/ChangeIccPinScreen.java
@@ -277,7 +277,7 @@
     private AlertDialog mPUKAlert;
     private void displayPUKAlert () {
         if (mPUKAlert == null) {
-            mPUKAlert = new AlertDialog.Builder(this)
+            mPUKAlert = FrameworksUtils.makeAlertDialogBuilder(this)
             .setMessage (R.string.puk_requested)
             .setCancelable(false)
             .show();
diff --git a/src/com/android/phone/FrameworksUtils.java b/src/com/android/phone/FrameworksUtils.java
new file mode 100644
index 0000000..dcf10bd
--- /dev/null
+++ b/src/com/android/phone/FrameworksUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.res.Configuration;
+
+/**
+ * This class provides utility functions over framework APIs
+ */
+public class FrameworksUtils {
+    /**
+     * Create a new instance of {@link AlertDialog.Builder}.
+     * @param context reference to a Context
+     * @return an instance of AlertDialog.Builder
+     */
+    public static AlertDialog.Builder makeAlertDialogBuilder(Context context) {
+        boolean isDarkTheme = (context.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+        return new AlertDialog.Builder(context, isDarkTheme
+                ? android.R.style.Theme_DeviceDefault_Dialog_Alert : 0);
+    }
+}
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
new file mode 100644
index 0000000..c7f15bf
--- /dev/null
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -0,0 +1,1088 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
+import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
+import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
+import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR;
+import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+import static android.telephony.ims.feature.ImsFeature.STATE_READY;
+import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
+
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.feature.ImsFeature;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IImsStateCallback;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.ims.ImsResolver;
+import com.android.internal.telephony.util.HandlerExecutor;
+import com.android.services.telephony.rcs.RcsFeatureController;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of the controller managing {@link ImsStateCallback}s
+ */
+public class ImsStateCallbackController {
+    private static final String TAG = "ImsStateCallbackController";
+    private static final boolean VDBG = false;
+
+    /**
+     * Create a FeatureConnector for this class to use to connect to an ImsManager.
+     */
+    @VisibleForTesting
+    public interface MmTelFeatureConnectorFactory {
+        /**
+         * Create a FeatureConnector for this class to use to connect to an ImsManager.
+         * @param listener will receive ImsManager instance.
+         * @param executor that the Listener callbacks will be called on.
+         * @return A FeatureConnector
+         */
+        FeatureConnector<ImsManager> create(Context context, int slotId,
+                String logPrefix, FeatureConnector.Listener<ImsManager> listener,
+                Executor executor);
+    }
+
+    /**
+     * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+     */
+    @VisibleForTesting
+    public interface RcsFeatureConnectorFactory {
+        /**
+         * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+         * @param listener will receive RcsFeatureManager instance.
+         * @param executor that the Listener callbacks will be called on.
+         * @return A FeatureConnector
+         */
+        FeatureConnector<RcsFeatureManager> create(Context context, int slotId,
+                FeatureConnector.Listener<RcsFeatureManager> listener,
+                Executor executor, String logPrefix);
+    }
+
+    /** Indicates that the state is not valid, used in ExternalRcsFeatureState only */
+    private static final int STATE_UNKNOWN = -1;
+
+    /** The unavailable reason of ImsFeature is not initialized */
+    private static final int NOT_INITIALIZED = -1;
+    /** The ImsFeature is available. */
+    private static final int AVAILABLE = 0;
+
+    private static final int EVENT_SUB_CHANGED = 1;
+    private static final int EVENT_REGISTER_CALLBACK = 2;
+    private static final int EVENT_UNREGISTER_CALLBACK = 3;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 4;
+    private static final int EVENT_EXTERNAL_RCS_STATE_CHANGED = 5;
+    private static final int EVENT_MSIM_CONFIGURATION_CHANGE = 6;
+
+    private static ImsStateCallbackController sInstance;
+
+    /**
+     * get the instance
+     */
+    public static ImsStateCallbackController getInstance() {
+        synchronized (ImsStateCallbackController.class) {
+            return sInstance;
+        }
+    }
+
+    private final PhoneGlobals mApp;
+    private final Handler mHandler;
+    private final ImsResolver mImsResolver;
+    private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
+    private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
+
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyRegistryManager mTelephonyRegistryManager;
+    private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
+    private RcsFeatureConnectorFactory mRcsFeatureFactory;
+
+    private HashMap<IBinder, CallbackWrapper> mWrappers = new HashMap<>();
+
+    private int mNumSlots;
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null) {
+                return;
+            }
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                Bundle bundle = intent.getExtras();
+                if (bundle == null) {
+                    return;
+                }
+                int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_PHONE_INDEX);
+                int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+                if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                    loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid slotId");
+                    return;
+                }
+
+                if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid subId");
+                    //subscription changed will be notified by mSubChangedListener
+                    return;
+                }
+
+                notifyCarrierConfigChanged(slotId);
+            }
+        }
+    };
+
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
+                mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
+            }
+        }
+    };
+
+    private final class MyHandler extends Handler {
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            logv("handleMessage: " + msg);
+            switch (msg.what) {
+                case EVENT_SUB_CHANGED:
+                    onSubChanged();
+                    break;
+
+                case EVENT_REGISTER_CALLBACK:
+                    onRegisterCallback((ImsStateCallbackController.CallbackWrapper) msg.obj);
+                    break;
+
+                case EVENT_UNREGISTER_CALLBACK:
+                    onUnregisterCallback((IImsStateCallback) msg.obj);
+                    break;
+
+                case EVENT_CARRIER_CONFIG_CHANGED:
+                    onCarrierConfigChanged(msg.arg1);
+                    break;
+
+                case EVENT_EXTERNAL_RCS_STATE_CHANGED:
+                    if (msg.obj == null) break;
+                    onExternalRcsStateChanged((ExternalRcsFeatureState) msg.obj);
+                    break;
+
+                case EVENT_MSIM_CONFIGURATION_CHANGE:
+                    AsyncResult result = (AsyncResult) msg.obj;
+                    Integer numSlots = (Integer) result.result;
+                    if (numSlots == null) {
+                        Log.w(TAG, "msim config change with null num slots");
+                        break;
+                    }
+                    updateFeatureControllerSize(numSlots);
+                    break;
+
+                default:
+                    loge("Unhandled event " + msg.what);
+            }
+        }
+    }
+
+    private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
+        private FeatureConnector<ImsManager> mConnector;
+        private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        private int mState = STATE_UNAVAILABLE;
+        private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
+
+        /*
+         * Remember the last return of verifyImsMmTelConfigured().
+         * true means ImsResolver found an IMS package for FEATURE_MMTEL.
+         *
+         * mReason is updated through connectionUnavailable triggered by ImsResolver.
+         * mHasConfig is update through notifyConfigChanged triggered by mReceiver.
+         * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
+         * However, when a carrier config changes, we are not sure the order
+         * of execution of connectionUnavailable and notifyConfigChanged.
+         * So, it's safe to use a separated state to retain it.
+         * We assume mHasConfig is true, until it's determined explicitly.
+         */
+        private boolean mHasConfig = true;
+
+        private int mSlotId = -1;
+        private String mLogPrefix = "";
+
+        MmTelFeatureListener(int slotId) {
+            mLogPrefix = "[MMTEL, " + slotId + "] ";
+            logv(mLogPrefix + "create");
+            mConnector = mMmTelFeatureFactory.create(
+                    mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
+            mConnector.connect();
+        }
+
+        void setSubId(int subId) {
+            logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
+            if (mSubId == subId) return;
+            logd(mLogPrefix + "setSubId subId changed");
+
+            mSubId = subId;
+        }
+
+        void destroy() {
+            logv(mLogPrefix + "destroy");
+            mConnector.disconnect();
+            mConnector = null;
+        }
+
+        @Override
+        public void connectionReady(ImsManager manager) {
+            logd(mLogPrefix + "connectionReady");
+
+            mState = STATE_READY;
+            mReason = AVAILABLE;
+            mHasConfig = true;
+            onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
+        }
+
+        @Override
+        public void connectionUnavailable(int reason) {
+            logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
+
+            reason = convertReasonType(reason);
+            if (mReason == reason) return;
+
+            connectionUnavailableInternal(reason);
+        }
+
+        private void connectionUnavailableInternal(int reason) {
+            mState = STATE_UNAVAILABLE;
+            mReason = reason;
+
+            /* If having no IMS package for MMTEL,
+             * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+            if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
+
+            onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
+        }
+
+        void notifyConfigChanged(boolean hasConfig) {
+            if (mHasConfig == hasConfig) return;
+
+            logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
+
+            mHasConfig = hasConfig;
+            if (hasConfig) {
+                // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
+                // since there is no configuration of IMS package for MMTEL.
+                // Now, a carrier configuration change is notified and
+                // the response from ImsResolver is changed from false to true.
+                if (mState != STATE_READY) {
+                    if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+                        // In this case, notify clients the reason, REASON_DISCONNCTED,
+                        // to update the state.
+                        connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+                    } else {
+                        // ImsResolver and ImsStateCallbackController run with different Looper.
+                        // In this case, FeatureConnectorListener is updated ahead of this.
+                        // But, connectionUnavailable didn't notify clients since mHasConfig is
+                        // false. So, notify clients here.
+                        connectionUnavailableInternal(mReason);
+                    }
+                }
+            } else {
+                // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
+                // so report the reason here.
+                connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+            }
+        }
+
+        // called from onRegisterCallback
+        boolean notifyState(CallbackWrapper wrapper) {
+            logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
+
+            return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
+        }
+    }
+
+    private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
+        private FeatureConnector<RcsFeatureManager> mConnector;
+        private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        private int mState = STATE_UNAVAILABLE;
+        private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
+
+        /*
+         * Remember the last return of verifyImsMmTelConfigured().
+         * true means ImsResolver found an IMS package for FEATURE_RCS.
+         *
+         * mReason is updated through connectionUnavailable triggered by ImsResolver.
+         * mHasConfig is update through notifyConfigChanged triggered by mReceiver,
+         * and notifyExternalRcsState which triggered by TelephonyRcsService refers it.
+         * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
+         * However, when a carrier config changes, we are not sure the order
+         * of execution of connectionUnavailable, notifyConfigChanged and notifyExternalRcsState.
+         * So, it's safe to use a separated state to retain it.
+         * We assume mHasConfig is true, until it's determined explicitly.
+         */
+        private boolean mHasConfig = true;
+
+        /*
+         * TelephonyRcsService doesn’t try to connect to RcsFeature if there is no active feature
+         * for a given subscription. The active features are declared by carrier configs and
+         * configuration resources. The APIs of ImsRcsManager and SipDelegateManager are available
+         * only when the RcsFeatureController has a STATE_READY state connection.
+         * This configuration is different from the configuration of IMS package for RCS.
+         * ImsStateCallbackController's FeatureConnectorListener can be STATE_READY state,
+         * even in case there is no active RCS feature. But Manager's APIs throws exception.
+         *
+         * For RCS, in addition to mHasConfig, the sate of TelephonyRcsService and
+         * RcsFeatureConnector will be traced to determine the state to be notified to clients.
+         */
+        private ExternalRcsFeatureState mExternalState = null;
+
+        private int mSlotId = -1;
+        private String mLogPrefix = "";
+
+        RcsFeatureListener(int slotId) {
+            mLogPrefix = "[RCS, " + slotId + "] ";
+            logv(mLogPrefix + "create");
+
+            mConnector = mRcsFeatureFactory.create(
+                    mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
+            mConnector.connect();
+        }
+
+        void setSubId(int subId) {
+            logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
+            if (mSubId == subId) return;
+            logd(mLogPrefix + "setSubId subId changed");
+
+            mSubId = subId;
+        }
+
+        void destroy() {
+            logv(mLogPrefix + "destroy");
+
+            mConnector.disconnect();
+            mConnector = null;
+        }
+
+        @Override
+        public void connectionReady(RcsFeatureManager manager) {
+            logd(mLogPrefix + "connectionReady");
+
+            mState = STATE_READY;
+            mReason = AVAILABLE;
+            mHasConfig = true;
+
+            if (mExternalState != null && mExternalState.isReady()) {
+                onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+            }
+        }
+
+        @Override
+        public void connectionUnavailable(int reason) {
+            logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
+
+            reason = convertReasonType(reason);
+            if (mReason == reason) return;
+
+            connectionUnavailableInternal(reason);
+        }
+
+        private void connectionUnavailableInternal(int reason) {
+            mState = STATE_UNAVAILABLE;
+            mReason = reason;
+
+            /* If having no IMS package for RCS,
+             * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+            if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
+
+            if (mExternalState == null && reason != REASON_NO_IMS_SERVICE_CONFIGURED) {
+                // Wait until TelephonyRcsService notifies its state.
+                return;
+            }
+
+            if (mExternalState != null && !mExternalState.hasActiveFeatures()) {
+                // notifyExternalState has notified REASON_NO_IMS_SERVICE_CONFIGURED already
+                // ignore it
+                return;
+            }
+
+            if ((mExternalState != null && mExternalState.hasActiveFeatures())
+                    || mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+                onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+            }
+        }
+
+        void notifyConfigChanged(boolean hasConfig) {
+            if (mHasConfig == hasConfig) return;
+
+            logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
+
+            mHasConfig = hasConfig;
+            if (hasConfig) {
+                // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
+                // since there is no configuration of IMS package for RCS.
+                // Now, a carrier configuration change is notified and
+                // the response from ImsResolver is changed from false to true.
+                if (mState != STATE_READY) {
+                    if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+                        // In this case, notify clients the reason, REASON_DISCONNCTED,
+                        // to update the state.
+                        connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+                    } else {
+                        // ImsResolver and ImsStateCallbackController run with different Looper.
+                        // In this case, FeatureConnectorListener is updated ahead of this.
+                        // But, connectionUnavailable didn't notify clients since mHasConfig is
+                        // false. So, notify clients here.
+                        connectionUnavailableInternal(mReason);
+                    }
+                }
+            } else {
+                // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
+                // so report the reason here.
+                connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+            }
+        }
+
+        void notifyExternalRcsState(ExternalRcsFeatureState fs) {
+            logv(mLogPrefix + "notifyExternalRcsState"
+                    + " state=" + (fs.mState == STATE_UNKNOWN
+                            ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
+                    + ", reason=" + imsStateReasonToString(fs.mReason));
+
+            ExternalRcsFeatureState oldFs = mExternalState;
+            // External state is from TelephonyRcsService while a feature is added or removed.
+            if (fs.mState == STATE_UNKNOWN) {
+                if (oldFs != null) fs.mState = oldFs.mState;
+                else fs.mState = STATE_UNAVAILABLE;
+            }
+
+            mExternalState = fs;
+
+            // No IMS package found.
+            // REASON_NO_IMS_SERVICE_CONFIGURED is notified to clients already.
+            if (!mHasConfig) return;
+
+            if (fs.hasActiveFeatures()) {
+                if (mState == STATE_READY) {
+                    if ((oldFs == null || !oldFs.isReady()) && fs.isReady()) {
+                        // it is waiting RcsFeatureConnector's notification.
+                        // notify clients here.
+                        onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+                    } else if (!fs.isReady()) {
+                        // Wait RcsFeatureConnector's notification
+                    } else {
+                        // ignore duplicated notification
+                    }
+                }
+            } else {
+                // notify only once
+                if (oldFs == null || oldFs.hasActiveFeatures()) {
+                    if (mReason != REASON_NO_IMS_SERVICE_CONFIGURED) {
+                        onFeatureStateChange(
+                                mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+                                REASON_NO_IMS_SERVICE_CONFIGURED);
+                    }
+                } else {
+                    // ignore duplicated notification
+                }
+            }
+        }
+
+        // called from onRegisterCallback
+        boolean notifyState(CallbackWrapper wrapper) {
+            logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
+
+            if (mHasConfig) {
+                if (mExternalState == null) {
+                    // Wait until TelephonyRcsService notifies its state.
+                    return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+                            REASON_IMS_SERVICE_DISCONNECTED);
+                } else if (!mExternalState.hasActiveFeatures()) {
+                    return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+                            REASON_NO_IMS_SERVICE_CONFIGURED);
+                }
+            }
+
+            return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
+        }
+    }
+
+    /**
+     * A wrapper class for the callback registered
+     */
+    private static class CallbackWrapper {
+        private final int mSubId;
+        private final int mRequiredFeature;
+        private final IImsStateCallback mCallback;
+        private final IBinder mBinder;
+
+        CallbackWrapper(int subId, int feature, IImsStateCallback callback) {
+            mSubId = subId;
+            mRequiredFeature = feature;
+            mCallback = callback;
+            mBinder = callback.asBinder();
+        }
+
+        /**
+         * @return false when accessing callback binder throws an Exception.
+         * That means the callback binder is not valid any longer.
+         * The death of remote process can cause this.
+         * This instance shall be removed from the list.
+         */
+        boolean notifyState(int subId, int feature, int state, int reason) {
+            logv("CallbackWrapper notifyState subId=" + subId
+                    + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+                    + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+                    + ", reason=" + imsStateReasonToString(reason));
+
+            try {
+                if (state == STATE_READY) {
+                    mCallback.onAvailable();
+                } else {
+                    mCallback.onUnavailable(reason);
+                }
+            } catch (Exception e) {
+                loge("CallbackWrapper notifyState e=" + e);
+                return false;
+            }
+
+            return true;
+        }
+
+        void notifyInactive() {
+            logv("CallbackWrapper notifyInactive subId=" + mSubId);
+
+            try {
+                mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+            } catch (Exception e) {
+                // ignored
+            }
+        }
+    }
+
+    private static class ExternalRcsFeatureState {
+        private int mSlotId;
+        private int mState = STATE_UNAVAILABLE;
+        private int mReason = NOT_INITIALIZED;
+
+        ExternalRcsFeatureState(int slotId, int state, int reason) {
+            mSlotId = slotId;
+            mState = state;
+            mReason = reason;
+        }
+
+        boolean hasActiveFeatures() {
+            return mReason != REASON_NO_IMS_SERVICE_CONFIGURED;
+        }
+
+        boolean isReady() {
+            return mState == STATE_READY;
+        }
+    }
+
+    /**
+     * create an instance
+     */
+    public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
+        synchronized (ImsStateCallbackController.class) {
+            if (sInstance == null) {
+                logd("ImsStateCallbackController created");
+
+                HandlerThread handlerThread = new HandlerThread(TAG);
+                handlerThread.start();
+                sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
+                        ImsManager::getConnector, RcsFeatureManager::getConnector,
+                        ImsResolver.getInstance());
+            }
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
+            MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
+            ImsResolver imsResolver) {
+        mApp = app;
+        mHandler = new MyHandler(looper);
+        mImsResolver = imsResolver;
+        mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
+        mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
+        mMmTelFeatureFactory = mmTelFactory;
+        mRcsFeatureFactory = rcsFactory;
+
+        updateFeatureControllerSize(numSlots);
+
+        mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+
+        PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
+                EVENT_MSIM_CONFIGURATION_CHANGE, null);
+
+        mApp.registerReceiver(mReceiver, new IntentFilter(
+                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        onSubChanged();
+    }
+
+    /**
+     * Update the number of {@link RcsFeatureController}s that are created based on the number of
+     * active slots on the device.
+     */
+    @VisibleForTesting
+    public void updateFeatureControllerSize(int newNumSlots) {
+        if (mNumSlots != newNumSlots) {
+            Log.d(TAG, "updateFeatures: oldSlots=" + mNumSlots
+                    + ", newNumSlots=" + newNumSlots);
+            if (mNumSlots < newNumSlots) {
+                for (int i = mNumSlots; i < newNumSlots; i++) {
+                    MmTelFeatureListener m = new MmTelFeatureListener(i);
+                    mMmTelFeatureListeners.put(i, m);
+                    RcsFeatureListener r = new RcsFeatureListener(i);
+                    mRcsFeatureListeners.put(i, r);
+                }
+            } else {
+                for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
+                    MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
+                    if (m != null) {
+                        mMmTelFeatureListeners.remove(i);
+                        m.destroy();
+                    }
+                    RcsFeatureListener r = mRcsFeatureListeners.get(i);
+                    if (r != null) {
+                        mRcsFeatureListeners.remove(i);
+                        r.destroy();
+                    }
+                }
+            }
+        }
+        mNumSlots = newNumSlots;
+    }
+
+    /**
+     * Dependencies for testing.
+     */
+    @VisibleForTesting
+    public void onSubChanged() {
+        logv("onSubChanged size=" + mWrappers.size());
+
+        for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+            MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
+            l.setSubId(getSubId(i));
+        }
+
+        for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+            RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
+            l.setSubId(getSubId(i));
+        }
+
+        if (mWrappers.size() == 0) return;
+
+        ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
+        final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+
+        logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
+
+        // Remove callbacks for inactive subscriptions
+        for (IBinder binder : mWrappers.keySet()) {
+            CallbackWrapper wrapper = mWrappers.get(binder);
+            if (wrapper != null) {
+                if (!isActive(activeSubs, wrapper.mSubId)) {
+                    // inactive subscription
+                    inactiveCallbacks.add(binder);
+                }
+            } else {
+                // unexpected, remove it
+                inactiveCallbacks.add(binder);
+            }
+        }
+        removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
+    }
+
+    private void onFeatureStateChange(int subId, int feature, int state, int reason) {
+        logv("onFeatureStateChange subId=" + subId
+                + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+                + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+                + ", reason=" + imsStateReasonToString(reason));
+
+        ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
+        mWrappers.values().forEach(wrapper -> {
+            if (subId == wrapper.mSubId
+                    && feature == wrapper.mRequiredFeature
+                    && !wrapper.notifyState(subId, feature, state, reason)) {
+                // callback has exception, remove it
+                inactiveCallbacks.add(wrapper.mBinder);
+            }
+        });
+        removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
+    }
+
+    private void onRegisterCallback(CallbackWrapper wrapper) {
+        if (wrapper == null) return;
+
+        logv("onRegisterCallback before size=" + mWrappers.size());
+        logv("onRegisterCallback subId=" + wrapper.mSubId
+                + ", feature=" + wrapper.mRequiredFeature);
+
+        // Not sure the following case can happen or not:
+        // step1) Subscription changed
+        // step2) ImsStateCallbackController not processed onSubChanged yet
+        // step3) Client registers with a strange subId
+        // The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
+        // So, register the wrapper here before trying to notifyState.
+        // TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
+        mWrappers.put(wrapper.mBinder, wrapper);
+
+        if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
+            for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+                MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
+                if (l.mSubId == wrapper.mSubId
+                        && !l.notifyState(wrapper)) {
+                    mWrappers.remove(wrapper.mBinder);
+                    break;
+                }
+            }
+        } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
+            for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+                RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
+                if (l.mSubId == wrapper.mSubId
+                        && !l.notifyState(wrapper)) {
+                    mWrappers.remove(wrapper.mBinder);
+                    break;
+                }
+            }
+        }
+
+        logv("onRegisterCallback after size=" + mWrappers.size());
+    }
+
+    private void onUnregisterCallback(IImsStateCallback cb) {
+        if (cb == null) return;
+        mWrappers.remove(cb.asBinder());
+    }
+
+    private void onCarrierConfigChanged(int slotId) {
+        if (slotId >= mNumSlots) {
+            logd("onCarrierConfigChanged invalid slotId "
+                    + slotId + ", mNumSlots=" + mNumSlots);
+            return;
+        }
+
+        logd("onCarrierConfigChanged slotId=" + slotId);
+
+        boolean hasConfig = verifyImsMmTelConfigured(slotId);
+        if (slotId < mMmTelFeatureListeners.size()) {
+            MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
+            listener.notifyConfigChanged(hasConfig);
+        }
+
+        hasConfig = verifyImsRcsConfigured(slotId);
+        if (slotId < mRcsFeatureListeners.size()) {
+            RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
+            listener.notifyConfigChanged(hasConfig);
+        }
+    }
+
+    private void onExternalRcsStateChanged(ExternalRcsFeatureState fs) {
+        logv("onExternalRcsStateChanged slotId=" + fs.mSlotId
+                + ", state=" + (fs.mState == STATE_UNKNOWN
+                        ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
+                + ", reason=" + imsStateReasonToString(fs.mReason));
+
+        RcsFeatureListener listener = mRcsFeatureListeners.get(fs.mSlotId);
+        if (listener != null) {
+            listener.notifyExternalRcsState(fs);
+        } else {
+            // unexpected state
+            loge("onExternalRcsStateChanged slotId=" + fs.mSlotId + ", no listener.");
+        }
+    }
+
+    /**
+     * Interface to be notified from TelephonyRcsSerice and RcsFeatureController
+     *
+     * @param ready true if feature's state is STATE_READY. Valid only when it is true.
+     * @param hasActiveFeatures true if the RcsFeatureController has active features.
+     */
+    public void notifyExternalRcsStateChanged(
+            int slotId, boolean ready, boolean hasActiveFeatures) {
+        int state = STATE_UNKNOWN;
+        int reason = REASON_IMS_SERVICE_DISCONNECTED;
+
+        if (ready) {
+            // From RcsFeatureController
+            state = STATE_READY;
+            reason = AVAILABLE;
+        } else if (!hasActiveFeatures) {
+            // From TelephonyRcsService
+            reason = REASON_NO_IMS_SERVICE_CONFIGURED;
+            state = STATE_UNAVAILABLE;
+        } else {
+            // From TelephonyRcsService
+            // TelephonyRcsService doesn't know the exact state of FeatureConnection.
+            // Only when there is no feature, we can assume the state.
+        }
+
+        logv("notifyExternalRcsStateChanged slotId=" + slotId
+                + ", ready=" + ready
+                + ", hasActiveFeatures=" + hasActiveFeatures);
+
+        ExternalRcsFeatureState fs = new ExternalRcsFeatureState(slotId, state, reason);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_EXTERNAL_RCS_STATE_CHANGED, fs));
+    }
+
+    /**
+     * Notifies carrier configuration has changed.
+     */
+    @VisibleForTesting
+    public void notifyCarrierConfigChanged(int slotId) {
+        logv("notifyCarrierConfigChanged slotId=" + slotId);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
+    }
+    /**
+     * Register IImsStateCallback
+     *
+     * @param feature for which state is changed, ImsFeature.FEATURE_*
+     */
+    public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb) {
+        logv("registerImsStateCallback subId=" + subId + ", feature=" + feature);
+
+        CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
+    }
+
+    /**
+     * Unegister previously registered callback
+     */
+    public void unregisterImsStateCallback(IImsStateCallback cb) {
+        logv("unregisterImsStateCallback");
+
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
+    }
+
+    private void removeInactiveCallbacks(
+            ArrayList<IBinder> inactiveCallbacks, String message) {
+        if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
+
+        logv("removeInactiveCallbacks size=" + inactiveCallbacks.size() + " from " + message);
+
+        for (IBinder binder : inactiveCallbacks) {
+            CallbackWrapper wrapper = mWrappers.get(binder);
+            if (wrapper != null) {
+                // Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
+                wrapper.notifyInactive();
+                mWrappers.remove(binder);
+            }
+        }
+        inactiveCallbacks.clear();
+    }
+
+    private int getSubId(int slotId) {
+        Phone phone = mPhoneFactoryProxy.getPhone(slotId);
+        if (phone != null) return phone.getSubId();
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    private static boolean isActive(final int[] activeSubs, int subId) {
+        for (int i : activeSubs) {
+            if (i == subId) return true;
+        }
+        return false;
+    }
+
+    private static int convertReasonType(int reason) {
+        switch(reason) {
+            case UNAVAILABLE_REASON_NOT_READY:
+                return REASON_IMS_SERVICE_NOT_READY;
+            case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
+                return REASON_NO_IMS_SERVICE_CONFIGURED;
+            default:
+                break;
+        }
+
+        return REASON_IMS_SERVICE_DISCONNECTED;
+    }
+
+    private boolean verifyImsMmTelConfigured(int slotId) {
+        boolean ret = false;
+        if (mImsResolver == null) {
+            loge("verifyImsMmTelConfigured mImsResolver is null");
+        } else {
+            ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
+        }
+        logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
+        return ret;
+    }
+
+    private boolean verifyImsRcsConfigured(int slotId) {
+        boolean ret = false;
+        if (mImsResolver == null) {
+            loge("verifyImsRcsConfigured mImsResolver is null");
+        } else {
+            ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
+        }
+        logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
+        return ret;
+    }
+
+    private static String connectorReasonToString(int reason) {
+        switch(reason) {
+            case UNAVAILABLE_REASON_DISCONNECTED:
+                return "DISCONNECTED";
+            case UNAVAILABLE_REASON_NOT_READY:
+                return "NOT_READY";
+            case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
+                return "IMS_UNSUPPORTED";
+            case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
+                return "SERVER_UNAVAILABLE";
+            default:
+                break;
+        }
+        return "";
+    }
+
+    private static String imsStateReasonToString(int reason) {
+        switch(reason) {
+            case REASON_UNKNOWN_TEMPORARY_ERROR:
+                return "UNKNOWN_TEMPORARY_ERROR";
+            case REASON_UNKNOWN_PERMANENT_ERROR:
+                return "UNKNOWN_PERMANENT_ERROR";
+            case REASON_IMS_SERVICE_DISCONNECTED:
+                return "IMS_SERVICE_DISCONNECTED";
+            case REASON_NO_IMS_SERVICE_CONFIGURED:
+                return "NO_IMS_SERVICE_CONFIGURED";
+            case REASON_SUBSCRIPTION_INACTIVE:
+                return "SUBSCRIPTION_INACTIVE";
+            case REASON_IMS_SERVICE_NOT_READY:
+                return "IMS_SERVICE_NOT_READY";
+            default:
+                break;
+        }
+        return "";
+    }
+
+    /**
+     * PhoneFactory Dependencies for testing.
+     */
+    @VisibleForTesting
+    public interface PhoneFactoryProxy {
+        /**
+         * Override getPhone for testing.
+         */
+        Phone getPhone(int index);
+    }
+
+    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
+        @Override
+        public Phone getPhone(int index) {
+            return PhoneFactory.getPhone(index);
+        }
+    };
+
+    private void release() {
+        logv("release");
+
+        mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+        mApp.unregisterReceiver(mReceiver);
+
+        for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+            mMmTelFeatureListeners.valueAt(i).destroy();
+        }
+        mMmTelFeatureListeners.clear();
+
+        for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+            mRcsFeatureListeners.valueAt(i).destroy();
+        }
+        mRcsFeatureListeners.clear();
+    }
+
+    /**
+     * destroy the instance
+     */
+    @VisibleForTesting
+    public void destroy() {
+        logv("destroy it");
+
+        release();
+        mHandler.getLooper().quit();
+    }
+
+    /**
+     * get the handler
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Determine whether the callback is registered or not
+     */
+    @VisibleForTesting
+    public boolean isRegistered(IImsStateCallback cb) {
+        if (cb == null) return false;
+        return mWrappers.containsKey(cb.asBinder());
+    }
+
+    private static void logv(String msg) {
+        if (VDBG) {
+            Rlog.d(TAG, msg);
+        }
+    }
+
+    private static void logd(String msg) {
+        Rlog.d(TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/PhoneDisplayMessage.java b/src/com/android/phone/PhoneDisplayMessage.java
index 199fbb8..be7fc7f 100644
--- a/src/com/android/phone/PhoneDisplayMessage.java
+++ b/src/com/android/phone/PhoneDisplayMessage.java
@@ -68,7 +68,7 @@
         // displaying system alert dialog on the screen instead of
         // using another activity to display the message.  This
         // places the message at the forefront of the UI.
-        sDisplayMessageDialog = new AlertDialog.Builder(context)
+        sDisplayMessageDialog = FrameworksUtils.makeAlertDialogBuilder(context)
                 .setIcon(android.R.drawable.ic_dialog_info)
                 .setTitle(title)
                 .setMessage(msg)
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 1a1c321..9607919 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -74,6 +74,7 @@
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.settings.SettingsConstants;
@@ -159,6 +160,7 @@
     TelephonyRcsService mTelephonyRcsService;
     public PhoneInterfaceManager phoneMgr;
     public ImsRcsController imsRcsController;
+    public ImsStateCallbackController mImsStateCallbackController;
     CarrierConfigLoader configLoader;
 
     private Phone phoneInEcm;
@@ -241,13 +243,13 @@
 
         // if passed in subType is unknown, retrieve it here.
         if (subType == -1) {
-            final UiccCard uiccCard = phone.getUiccCard();
-            if (uiccCard == null) {
+            final UiccPort uiccPort = phone.getUiccPort();
+            if (uiccPort == null) {
                 Log.e(LOG_TAG,
-                        "handleSimLock: uiccCard for phone " + phone.getPhoneId() + " is null");
+                        "handleSimLock: uiccPort for phone " + phone.getPhoneId() + " is null");
                 return;
             }
-            final UiccProfile uiccProfile = uiccCard.getUiccProfile();
+            final UiccProfile uiccProfile = uiccPort.getUiccProfile();
             if (uiccProfile == null) {
                 Log.e(LOG_TAG,
                         "handleSimLock: uiccProfile for phone " + phone.getPhoneId() + " is null");
@@ -462,6 +464,8 @@
             imsRcsController = ImsRcsController.init(this);
 
             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
+                mImsStateCallbackController =
+                        ImsStateCallbackController.make(this, PhoneFactory.getPhones().length);
                 mTelephonyRcsService = new TelephonyRcsService(this,
                         PhoneFactory.getPhones().length);
                 mTelephonyRcsService.initialize();
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 9c078dd..55452f3 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -22,6 +22,7 @@
 import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
 import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_IMS;
 import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT;
 
 import android.Manifest;
 import android.Manifest.permission;
@@ -152,6 +153,7 @@
 import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IImsStateCallback;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.ITelephony;
@@ -180,6 +182,7 @@
 import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.metrics.RcsStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccIoResult;
@@ -189,6 +192,7 @@
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.internal.telephony.util.LocaleUtils;
@@ -503,7 +507,7 @@
             MainThreadRequest request;
             Message onCompleted;
             AsyncResult ar;
-            UiccCard uiccCard;
+            UiccPort uiccPort;
             IccAPDUArgument iccArgument;
             final Phone defaultPhone = getDefaultPhone();
 
@@ -553,15 +557,15 @@
                 case CMD_TRANSMIT_APDU_LOGICAL_CHANNEL:
                     request = (MainThreadRequest) msg.obj;
                     iccArgument = (IccAPDUArgument) request.argument;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
                         loge("iccTransmitApduLogicalChannel: No UICC");
                         request.result = new IccIoResult(0x6F, 0, (byte[])null);
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE,
                             request);
-                        uiccCard.iccTransmitApduLogicalChannel(
+                        uiccPort.iccTransmitApduLogicalChannel(
                             iccArgument.channel, iccArgument.cla, iccArgument.command,
                             iccArgument.p1, iccArgument.p2, iccArgument.p3, iccArgument.data,
                             onCompleted);
@@ -590,15 +594,15 @@
                 case CMD_TRANSMIT_APDU_BASIC_CHANNEL:
                     request = (MainThreadRequest) msg.obj;
                     iccArgument = (IccAPDUArgument) request.argument;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
                         loge("iccTransmitApduBasicChannel: No UICC");
                         request.result = new IccIoResult(0x6F, 0, (byte[])null);
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE,
                             request);
-                        uiccCard.iccTransmitApduBasicChannel(
+                        uiccPort.iccTransmitApduBasicChannel(
                             iccArgument.cla, iccArgument.command, iccArgument.p1, iccArgument.p2,
                             iccArgument.p3, iccArgument.data, onCompleted);
                     }
@@ -626,15 +630,15 @@
                 case CMD_EXCHANGE_SIM_IO:
                     request = (MainThreadRequest) msg.obj;
                     iccArgument = (IccAPDUArgument) request.argument;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
                         loge("iccExchangeSimIO: No UICC");
                         request.result = new IccIoResult(0x6F, 0, (byte[])null);
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_EXCHANGE_SIM_IO_DONE,
                                 request);
-                        uiccCard.iccExchangeSimIO(iccArgument.cla, /* fileID */
+                        uiccPort.iccExchangeSimIO(iccArgument.cla, /* fileID */
                                 iccArgument.command, iccArgument.p1, iccArgument.p2, iccArgument.p3,
                                 iccArgument.data, onCompleted);
                     }
@@ -653,14 +657,14 @@
 
                 case CMD_SEND_ENVELOPE:
                     request = (MainThreadRequest) msg.obj;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
                         loge("sendEnvelopeWithStatus: No UICC");
                         request.result = new IccIoResult(0x6F, 0, (byte[])null);
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_SEND_ENVELOPE_DONE, request);
-                        uiccCard.sendEnvelopeWithStatus((String)request.argument, onCompleted);
+                        uiccPort.sendEnvelopeWithStatus((String)request.argument, onCompleted);
                     }
                     break;
 
@@ -685,16 +689,16 @@
 
                 case CMD_OPEN_CHANNEL:
                     request = (MainThreadRequest) msg.obj;
-                    uiccCard = getUiccCardFromRequest(request);
+                    uiccPort = getUiccPortFromRequest(request);
                     Pair<String, Integer> openChannelArgs = (Pair<String, Integer>) request.argument;
-                    if (uiccCard == null) {
+                    if (uiccPort == null) {
                         loge("iccOpenLogicalChannel: No UICC");
                         request.result = new IccOpenLogicalChannelResponse(-1,
                             IccOpenLogicalChannelResponse.STATUS_MISSING_RESOURCE, null);
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_OPEN_CHANNEL_DONE, request);
-                        uiccCard.iccOpenLogicalChannel(openChannelArgs.first,
+                        uiccPort.iccOpenLogicalChannel(openChannelArgs.first,
                                 openChannelArgs.second, onCompleted);
                     }
                     break;
@@ -742,14 +746,14 @@
 
                 case CMD_CLOSE_CHANNEL:
                     request = (MainThreadRequest) msg.obj;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
                         loge("iccCloseLogicalChannel: No UICC");
                         request.result = false;
                         notifyRequester(request);
                     } else {
                         onCompleted = obtainMessage(EVENT_CLOSE_CHANNEL_DONE, request);
-                        uiccCard.iccCloseLogicalChannel((Integer) request.argument, onCompleted);
+                        uiccPort.iccCloseLogicalChannel((Integer) request.argument, onCompleted);
                     }
                     break;
 
@@ -1096,7 +1100,9 @@
                             // any service for voice call.
                             if ((callForwardInfo.serviceClass
                                     & CommandsInterface.SERVICE_CLASS_VOICE) > 0) {
-                                callForwardingInfo = new CallForwardingInfo(true,
+                                callForwardingInfo = new CallForwardingInfo(
+                                        callForwardInfo.status
+                                                == CommandsInterface.CF_ACTION_ENABLE,
                                         callForwardInfo.reason,
                                         callForwardInfo.number,
                                         callForwardInfo.timeSeconds);
@@ -1461,16 +1467,16 @@
 
                 case CMD_GET_FORBIDDEN_PLMNS:
                     request = (MainThreadRequest) msg.obj;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
-                        loge("getForbiddenPlmns() UiccCard is null");
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
+                        loge("getForbiddenPlmns() UiccPort is null");
                         request.result = new IllegalArgumentException(
-                                "getForbiddenPlmns() UiccCard is null");
+                                "getForbiddenPlmns() UiccPort is null");
                         notifyRequester(request);
                         break;
                     }
                     Integer appType = (Integer) request.argument;
-                    UiccCardApplication uiccApp = uiccCard.getApplicationByType(appType);
+                    UiccCardApplication uiccApp = uiccPort.getApplicationByType(appType);
                     if (uiccApp == null) {
                         loge("getForbiddenPlmns() no app with specified type -- "
                                 + appType);
@@ -1766,9 +1772,9 @@
                     break;
                 case CMD_SET_FORBIDDEN_PLMNS:
                     request = (MainThreadRequest) msg.obj;
-                    uiccCard = getUiccCardFromRequest(request);
-                    if (uiccCard == null) {
-                        loge("setForbiddenPlmns: UiccCard is null");
+                    uiccPort = getUiccPortFromRequest(request);
+                    if (uiccPort == null) {
+                        loge("setForbiddenPlmns: UiccPort is null");
                         request.result = -1;
                         notifyRequester(request);
                         break;
@@ -1777,7 +1783,7 @@
                             (Pair<Integer, List<String>>) request.argument;
                     appType = setFplmnsArgs.first;
                     List<String> fplmns = setFplmnsArgs.second;
-                    uiccApp = uiccCard.getApplicationByType(appType);
+                    uiccApp = uiccPort.getApplicationByType(appType);
                     if (uiccApp == null) {
                         loge("setForbiddenPlmns: no app with specified type -- " + appType);
                         request.result = -1;
@@ -1975,7 +1981,7 @@
                             (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
                     onCompleted = obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
                             request);
-                    phone.getServiceStateTracker().setSignalStrengthUpdateRequest(
+                    phone.getSignalStrengthController().setSignalStrengthUpdateRequest(
                                     request.subId, pair.first /*callingUid*/,
                                     pair.second /*request*/, onCompleted);
                     break;
@@ -2003,7 +2009,7 @@
                             (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
                     onCompleted = obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
                             request);
-                    phone.getServiceStateTracker().clearSignalStrengthUpdateRequest(
+                    phone.getSignalStrengthController().clearSignalStrengthUpdateRequest(
                                     request.subId, pair.first /*callingUid*/,
                                     pair.second /*request*/, onCompleted);
                     break;
@@ -2055,7 +2061,8 @@
                 case CMD_PREPARE_UNATTENDED_REBOOT:
                     request = (MainThreadRequest) msg.obj;
                     request.result =
-                            UiccController.getInstance().getPinStorage().prepareUnattendedReboot();
+                            UiccController.getInstance().getPinStorage()
+                                .prepareUnattendedReboot(request.workSource);
                     notifyRequester(request);
                     break;
 
@@ -2295,10 +2302,10 @@
                 ? getDefaultPhone() : getPhone(subId);
     }
 
-    private UiccCard getUiccCardFromRequest(MainThreadRequest request) {
+    private UiccPort getUiccPortFromRequest(MainThreadRequest request) {
         Phone phone = getPhoneFromRequest(request);
         return phone == null ? null :
-                UiccController.getInstance().getUiccCard(phone.getPhoneId());
+                UiccController.getInstance().getUiccPort(phone.getPhoneId());
     }
 
     // returns phone associated with the subId.
@@ -3221,7 +3228,12 @@
         String tac = null;
         if (phone != null) {
             String imei = phone.getImei();
-            tac = imei == null ? null : imei.substring(0, TYPE_ALLOCATION_CODE_LENGTH);
+            try {
+                tac = imei == null ? null : imei.substring(0, TYPE_ALLOCATION_CODE_LENGTH);
+            } catch (IndexOutOfBoundsException e) {
+                Log.e(LOG_TAG, "IMEI length shorter than upper index.");
+                return null;
+            }
         }
         return tac;
     }
@@ -3260,7 +3272,13 @@
         String manufacturerCode = null;
         if (phone != null) {
             String meid = phone.getMeid();
-            manufacturerCode = meid == null ? null : meid.substring(0, MANUFACTURER_CODE_LENGTH);
+            try {
+                manufacturerCode =
+                        meid == null ? null : meid.substring(0, MANUFACTURER_CODE_LENGTH);
+            } catch (IndexOutOfBoundsException e) {
+                Log.e(LOG_TAG, "MEID length shorter than upper index.");
+                return null;
+            }
         }
         return manufacturerCode;
     }
@@ -3382,17 +3400,6 @@
         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
     }
 
-    /**
-     * Make sure the caller is system.
-     *
-     * @throws SecurityException if the caller is not system.
-     */
-    private static void enforceSystemCaller() {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Caller must be system");
-        }
-    }
-
     private void enforceActiveEmergencySessionPermission() {
         mApp.enforceCallingOrSelfPermission(
                 android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
@@ -5511,11 +5518,9 @@
      */
     public int setForbiddenPlmns(int subId, int appType, List<String> fplmns, String callingPackage,
             String callingFeatureId) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mApp, subId, callingPackage,
-                callingFeatureId, "setForbiddenPlmns")) {
-            if (DBG) logv("no permissions for setForbiddenplmns");
-            throw new IllegalStateException("No Permissions for setForbiddenPlmns");
-        }
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "setForbiddenPlmns");
+
         if (appType != TelephonyManager.APPTYPE_USIM && appType != TelephonyManager.APPTYPE_SIM) {
             loge("setForbiddenPlmnList(): App Type must be USIM or SIM");
             throw new IllegalArgumentException("Invalid appType: App Type must be USIM or SIM");
@@ -6576,34 +6581,6 @@
     }
 
     /**
-     * Enable or disable always reporting signal strength changes from radio.
-     *
-     * @param isEnable {@code true} for enabling; {@code false} for disabling.
-     */
-    @Override
-    public void setAlwaysReportSignalStrength(int subId, boolean isEnable) {
-        enforceModifyPermission();
-        enforceSystemCaller();
-
-        final long identity = Binder.clearCallingIdentity();
-        final Phone phone = getPhone(subId);
-        try {
-            if (phone != null) {
-                if (DBG) {
-                    log("setAlwaysReportSignalStrength: subId=" + subId
-                            + " isEnable=" + isEnable);
-                }
-                phone.setAlwaysReportSignalStrength(isEnable);
-            } else {
-                loge("setAlwaysReportSignalStrength: no phone found for subId="
-                        + subId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
      * Get the user enabled state of Mobile Data.
      *
      * TODO: remove and use isUserDataEnabled.
@@ -6813,14 +6790,14 @@
             loge("getCarrierPrivilegeStatus: Invalid subId");
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
         }
-        UiccCard card = UiccController.getInstance().getUiccCard(phone.getPhoneId());
-        if (card == null) {
+        UiccPort port = UiccController.getInstance().getUiccPort(phone.getPhoneId());
+        if (port == null) {
             loge("getCarrierPrivilegeStatus: No UICC");
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
         }
 
         return getCarrierPrivilegeStatusFromCarrierConfigRules(
-            card.getCarrierPrivilegeStatusForCurrentTransaction(
+            port.getCarrierPrivilegeStatusForCurrentTransaction(
                 phone.getContext().getPackageManager()), Binder.getCallingUid(), phone);
     }
 
@@ -6851,13 +6828,13 @@
         }
 
         int phoneId = SubscriptionManager.getPhoneId(subId);
-        UiccCard card = UiccController.getInstance().getUiccCard(phoneId);
-        if (card == null) {
+        UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
+        if (port == null) {
             loge("checkCarrierPrivilegesForPackage: No UICC on subId " + subId);
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
         }
         return getCarrierPrivilegeStatusFromCarrierConfigRules(
-            card.getCarrierPrivilegeStatus(mApp.getPackageManager(), pkgName),
+            port.getCarrierPrivilegeStatus(mApp.getPackageManager(), pkgName),
             getPhone(phoneId), pkgName);
     }
 
@@ -6868,14 +6845,14 @@
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
         int result = TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
         for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
-            UiccCard card = UiccController.getInstance().getUiccCard(i);
-            if (card == null) {
+            UiccPort port = UiccController.getInstance().getUiccPort(i);
+            if (port == null) {
               // No UICC in that slot.
               continue;
             }
 
             result = getCarrierPrivilegeStatusFromCarrierConfigRules(
-                card.getCarrierPrivilegeStatus(mApp.getPackageManager(), pkgName),
+                port.getCarrierPrivilegeStatus(mApp.getPackageManager(), pkgName),
                 getPhone(i), pkgName);
             if (result == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 break;
@@ -6892,12 +6869,12 @@
             loge("phoneId " + phoneId + " is not valid.");
             return null;
         }
-        UiccCard card = UiccController.getInstance().getUiccCard(phoneId);
-        if (card == null) {
+        UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
+        if (port == null) {
             loge("getCarrierPackageNamesForIntentAndPhone: No UICC");
             return null ;
         }
-        return card.getCarrierPackageNamesForIntent(mApp.getPackageManager(), intent);
+        return port.getCarrierPackageNamesForIntent(mApp.getPackageManager(), intent);
     }
 
     @Override
@@ -6906,10 +6883,10 @@
         PackageManager pm = mApp.getPackageManager();
         List<String> privilegedPackages = new ArrayList<>();
         List<PackageInfo> packages = null;
-        UiccCard card = UiccController.getInstance().getUiccCard(phoneId);
+        UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
         // has UICC in that slot.
-        if (card != null) {
-            if (card.hasCarrierPrivilegeRules()) {
+        if (port != null) {
+            if (port.hasCarrierPrivilegeRules()) {
                 if (packages == null) {
                     // Only check packages in user 0 for now
                     packages = pm.getInstalledPackagesAsUser(
@@ -6921,7 +6898,9 @@
                 for (int p = packages.size() - 1; p >= 0; p--) {
                     PackageInfo pkgInfo = packages.get(p);
                     if (pkgInfo != null && pkgInfo.packageName != null
-                            && card.getCarrierPrivilegeStatus(pkgInfo)
+                            && getCarrierPrivilegeStatusFromCarrierConfigRules(
+                                    port.getCarrierPrivilegeStatus(pkgInfo),
+                                    getPhone(phoneId), pkgInfo.packageName)
                             == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                         privilegedPackages.add(pkgInfo.packageName);
                     }
@@ -6950,11 +6929,11 @@
 
     private String getIccId(int subId) {
         final Phone phone = getPhone(subId);
-        UiccCard card = phone == null ? null : phone.getUiccCard();
-        if (card == null) {
+        UiccPort port = phone == null ? null : phone.getUiccPort();
+        if (port == null) {
             return null;
         }
-        String iccId = card.getIccId();
+        String iccId = port.getIccId();
         if (TextUtils.isEmpty(iccId)) {
             return null;
         }
@@ -8122,7 +8101,7 @@
             }
             String aid = null;
             try {
-                aid = UiccController.getInstance().getUiccCard(phone.getPhoneId())
+                aid = UiccController.getInstance().getUiccPort(phone.getPhoneId())
                         .getApplicationByType(appType).getAid();
             } catch (Exception e) {
                 Log.e(LOG_TAG, "Not getting aid. Exception ex=" + e);
@@ -8311,6 +8290,16 @@
             int result = (int) sendRequest(CMD_ENABLE_VONR, enabled, subId,
                     workSource);
             if (DBG) log("setVoNrEnabled result: " + result);
+
+            if (result == TelephonyManager.ENABLE_VONR_SUCCESS) {
+                if (DBG) {
+                    log("Set VoNR settings in siminfo db; subId=" + subId + ", value:" + enabled);
+                }
+                SubscriptionManager.setSubscriptionProperty(
+                        subId, SubscriptionManager.NR_ADVANCED_CALLING_ENABLED,
+                        (enabled ? "1" : "0"));
+            }
+
             return result;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -8699,6 +8688,22 @@
         return isAllowed;
     }
 
+    private boolean haveCarrierPrivilegeAccess(UiccCard card, String callingPackage) {
+        // TODO once MEP API refactoring CL is merged, loop port list from UiccCardInfo,
+        //  and if find the matching UiccPort by UiccController.getUiccPortForSlot(slot, portIdx)
+        //  Update each UiccPort object based on privilege access
+        UiccPort[] uiccPorts = card.getUiccPortList();
+        for (UiccPort port : uiccPorts) {
+            UiccProfile profile = port.getUiccProfile();
+            if (profile == null ||
+                    profile.getCarrierPrivilegeStatus(mApp.getPackageManager(), callingPackage)
+                    != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     public List<UiccCardInfo> getUiccCardsInfo(String callingPackage) {
         // Verify that tha callingPackage belongs to the calling UID
@@ -8732,14 +8737,15 @@
                 // For an inactive eUICC, the UiccCard will be null even though the UiccCardInfo
                 // is available
                 UiccCard card = uiccController.getUiccCardForSlot(cardInfo.getSlotIndex());
-                if (card == null || card.getUiccProfile() == null) {
-                    // assume no access if the card or profile is unavailable
+                // TODO remove card.getUiccPortList().length once MEP API refactoring CL is merged
+                //  Get UiccPortInfo from CardInfo and process further based on each UiccPort
+                if (card == null || card.getUiccPortList().length == 0) {
+                    // assume no access if the card or ports are unavailable
                     filteredInfos.add(cardInfo.getUnprivileged());
                     continue;
                 }
-                UiccProfile profile = card.getUiccProfile();
-                if (profile.getCarrierPrivilegeStatus(mApp.getPackageManager(), callingPackage)
-                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+
+                if (haveCarrierPrivilegeAccess(card, callingPackage)) {
                     filteredInfos.add(cardInfo);
                 } else {
                     filteredInfos.add(cardInfo.getUnprivileged());
@@ -8860,11 +8866,11 @@
             if (phone == null) {
                 return;
             }
-            UiccCard uiccCard = phone.getUiccCard();
-            if (uiccCard == null) {
+            UiccPort uiccPort = phone.getUiccPort();
+            if (uiccPort == null) {
                 return;
             }
-            UiccProfile uiccProfile = uiccCard.getUiccProfile();
+            UiccProfile uiccProfile = uiccPort.getUiccProfile();
             if (uiccProfile == null) {
                 return;
             }
@@ -9371,11 +9377,11 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            UiccCard uiccCard = phone.getUiccCard();
-            if (uiccCard == null) {
+            UiccPort uiccPort = phone.getUiccPort();
+            if (uiccPort == null) {
                 return false;
             }
-            UiccProfile uiccProfile = uiccCard.getUiccProfile();
+            UiccProfile uiccProfile = uiccPort.getUiccProfile();
             if (uiccProfile == null) {
                 return false;
             }
@@ -10379,6 +10385,9 @@
             } else {
                 configBinder.setRcsClientConfiguration(rcc);
             }
+
+            RcsStats.getInstance().onRcsClientProvisioningStats(subId,
+                    RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT);
         } catch (RemoteException e) {
             Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
             throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
@@ -10781,7 +10790,7 @@
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(callingUid, callingPackage);
 
-        validateSignalStrengthUpdateRequest(request, callingUid);
+        validateSignalStrengthUpdateRequest(mApp, request, callingUid);
 
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -10820,19 +10829,19 @@
         }
     }
 
-    private static void validateSignalStrengthUpdateRequest(SignalStrengthUpdateRequest request,
-            int callingUid) {
+    private static void validateSignalStrengthUpdateRequest(Context context,
+            SignalStrengthUpdateRequest request, int callingUid) {
         if (callingUid == Process.PHONE_UID || callingUid == Process.SYSTEM_UID) {
             // phone/system process do not have further restriction on request
             return;
         }
 
         // Applications has restrictions on how to use the request:
-        // Only system caller can set mIsSystemThresholdReportingRequestedWhileIdle
+        // Non-system callers need permission to set mIsSystemThresholdReportingRequestedWhileIdle
         if (request.isSystemThresholdReportingRequestedWhileIdle()) {
-            // This is not system caller which has been checked above
-            throw new IllegalArgumentException(
-                    "Only system can set isSystemThresholdReportingRequestedWhileIdle");
+            context.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH,
+                    "validateSignalStrengthUpdateRequest");
         }
 
         for (SignalThresholdInfo info : request.getSignalThresholdInfos()) {
@@ -10884,11 +10893,12 @@
     @Override
     @TelephonyManager.PrepareUnattendedRebootResult
     public int prepareForUnattendedReboot() {
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
         enforceRebootPermission();
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null);
+            return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null, workSource);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -10912,4 +10922,68 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    /**
+     * Register an IMS connection state callback
+     */
+    @Override
+    public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb) {
+        if (feature == ImsFeature.FEATURE_MMTEL) {
+            // ImsMmTelManager
+            // The following also checks READ_PRIVILEGED_PHONE_STATE.
+            TelephonyPermissions
+                    .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                            mApp, subId, "registerImsStateCallback");
+        } else if (feature == ImsFeature.FEATURE_RCS) {
+            // ImsRcsManager or SipDelegateManager
+            TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+                    Binder.getCallingUid(), "registerImsStateCallback",
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                    Manifest.permission.READ_PRECISE_PHONE_STATE,
+                    Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+                    Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+        }
+
+        if (!ImsManager.isImsSupportedOnDevice(mApp)) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
+        }
+
+        ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+        if (controller == null) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "IMS not available on device.");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            int slotId = getSlotIndexOrException(subId);
+            controller.registerImsStateCallback(subId, feature, cb);
+        } catch (ImsException e) {
+            throw new ServiceSpecificException(e.getCode());
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Unregister an IMS connection state callback
+     */
+    @Override
+    public void unregisterImsStateCallback(IImsStateCallback cb) {
+        final long token = Binder.clearCallingIdentity();
+        ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+        if (controller == null) {
+            return;
+        }
+        try {
+            controller.unregisterImsStateCallback(cb);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index c520063..35cfdd3 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -468,7 +468,8 @@
                     };
 
                 // build the dialog
-                final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper)
+                final AlertDialog newDialog =
+                        FrameworksUtils.makeAlertDialogBuilder(contextThemeWrapper)
                         .setMessage(text)
                         .setView(dialogView)
                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 23c4c5a..e819afc 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -16,6 +16,10 @@
 
 package com.android.phone;
 
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION;
+
 import android.Manifest;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
@@ -47,6 +51,8 @@
 import com.android.ims.FeatureUpdates;
 import com.android.ims.RcsFeatureManager;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.RcsStats;
+import com.android.internal.telephony.metrics.RcsStats.RcsProvisioningCallback;
 import com.android.internal.telephony.util.HandlerExecutor;
 import com.android.internal.util.CollectionUtils;
 import com.android.telephony.Rlog;
@@ -75,6 +81,10 @@
     private static final int EVENT_RESET = 8;
     private static final int EVENT_FEATURE_ENABLED_OVERRIDE = 9;
 
+    // indicate that the carrier single registration capable is initial value as
+    // carrier config is not ready yet.
+    private static final int MASK_CAP_CARRIER_INIT = 0xF000;
+
     private final PhoneGlobals mPhone;
     private final Handler mHandler;
     // Cache the RCS provsioning info and related sub id
@@ -96,6 +106,8 @@
     private final RoleManagerAdapter mRoleManager;
     private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
 
+    private RcsStats mRcsStats;
+
     private static RcsProvisioningMonitor sInstance;
 
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
@@ -220,7 +232,23 @@
         }
 
         void setSingleRegistrationCapability(int singleRegistrationCapability) {
-            mSingleRegistrationCapability = singleRegistrationCapability;
+            if (mSingleRegistrationCapability != singleRegistrationCapability) {
+                mSingleRegistrationCapability = singleRegistrationCapability;
+                notifyDma();
+
+                // update whether single registration supported.
+                mRcsStats.setEnableSingleRegistration(mSubId,
+                        mSingleRegistrationCapability == ProvisioningManager.STATUS_CAPABLE);
+            }
+        }
+
+        void notifyDma() {
+            // notify only if capable value has been updated when carrier config ready.
+            if ((mSingleRegistrationCapability & MASK_CAP_CARRIER_INIT) != MASK_CAP_CARRIER_INIT) {
+                logi("notify default messaging app for sub:" + mSubId + " with capability:"
+                        + mSingleRegistrationCapability);
+                notifyDmaForSub(mSubId, mSingleRegistrationCapability);
+            }
         }
 
         int getSingleRegistrationCapability() {
@@ -322,6 +350,9 @@
                     } else {
                         notifyRcsAutoConfigurationReceived();
                     }
+
+                    // check callback for metrics if not registered, register callback
+                    registerMetricsCallback();
                 } else {
                     // clear callbacks if rcs disconnected
                     clearCallbacks();
@@ -381,6 +412,18 @@
                 }
             }
         }
+
+        private void registerMetricsCallback() {
+            RcsProvisioningCallback rcsProvisioningCallback = mRcsStats.getRcsProvisioningCallback(
+                    mSubId, mSingleRegistrationCapability == ProvisioningManager.STATUS_CAPABLE);
+
+            // if not yet registered, register callback and set registered value
+            if (rcsProvisioningCallback != null && !rcsProvisioningCallback.getRegistered()) {
+                if (addRcsConfigCallback(rcsProvisioningCallback)) {
+                    rcsProvisioningCallback.setRegistered(true);
+                }
+            }
+        }
     }
 
     @VisibleForTesting
@@ -438,7 +481,7 @@
 
     @VisibleForTesting
     public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager,
-            FeatureConnectorFactory<RcsFeatureManager> factory) {
+            FeatureConnectorFactory<RcsFeatureManager> factory, RcsStats rcsStats) {
         mPhone = app;
         mHandler = new MyHandler(looper);
         mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
@@ -449,6 +492,7 @@
         logv("DMA is " + mDmaPackageName);
         mDmaChangedListener = new DmaChangedListener();
         mFeatureFactory = factory;
+        mRcsStats = rcsStats;
         init();
     }
 
@@ -461,7 +505,8 @@
             HandlerThread handlerThread = new HandlerThread(TAG);
             handlerThread.start();
             sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
-                    new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
+                    new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector,
+                    RcsStats.getInstance());
         }
         return sInstance;
     }
@@ -675,7 +720,7 @@
             logv("new default messaging application " + mDmaPackageName);
 
             mRcsProvisioningInfos.forEach((k, v) -> {
-                notifyDmaForSub(k, v.getSingleRegistrationCapability());
+                v.notifyDma();
 
                 byte[] cachedConfig = v.getConfig();
                 //clear old callbacks
@@ -688,6 +733,10 @@
                     logv("acs not used, set cached config and notify.");
                     v.setConfig(cachedConfig);
                 }
+
+                // store RCS metrics - DMA changed event
+                mRcsStats.onRcsClientProvisioningStats(k,
+                        RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED);
             });
         }
     }
@@ -715,17 +764,20 @@
         return b.getBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL);
     }
 
-    private boolean isSingleRegistrationRequiredByCarrier(int subId) {
+    private int getSingleRegistrationRequiredByCarrier(int subId) {
         Boolean enabledByOverride = mCarrierSingleRegistrationEnabledOverride.get(subId);
         if (enabledByOverride != null) {
-            return enabledByOverride;
+            return enabledByOverride ? ProvisioningManager.STATUS_CAPABLE
+                    : ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE;
         }
 
         PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
-        if (b == null) {
-            return false;
+        if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) {
+            return MASK_CAP_CARRIER_INIT;
         }
-        return b.getBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL);
+        return b.getBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL)
+                ? ProvisioningManager.STATUS_CAPABLE
+                : ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE;
     }
 
     private int getSingleRegistrationCapableValue(int subId) {
@@ -735,10 +787,9 @@
                 : mPhone.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
 
-        int value = (isSingleRegistrationEnabledOnDevice ? 0
-                : ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) | (
-                isSingleRegistrationRequiredByCarrier(subId) ? 0
-                : ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE);
+        int value = (isSingleRegistrationEnabledOnDevice ? ProvisioningManager.STATUS_CAPABLE
+                : ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE)
+                | getSingleRegistrationRequiredByCarrier(subId);
         logv("SingleRegistrationCapableValue : " + value);
         return value;
     }
@@ -746,11 +797,8 @@
     private void onCarrierConfigChange() {
         logv("onCarrierConfigChange");
         mRcsProvisioningInfos.forEach((subId, info) -> {
-            int value = getSingleRegistrationCapableValue(subId);
-            if (value != info.getSingleRegistrationCapability()) {
-                info.setSingleRegistrationCapability(value);
-                notifyDmaForSub(subId, value);
-            }
+            info.setSingleRegistrationCapability(
+                    getSingleRegistrationCapableValue(subId));
         });
     }
 
@@ -765,9 +813,8 @@
                 byte[] data = loadConfigForSub(i);
                 int capability = getSingleRegistrationCapableValue(i);
                 logv("new info is created for sub : " + i + ", single registration capability :"
-                        + capability + ", rcs config : " + data);
+                        + capability + ", rcs config : " + Arrays.toString(data));
                 mRcsProvisioningInfos.put(i, new RcsProvisioningInfo(i, capability, data));
-                notifyDmaForSub(i, capability);
             }
         }
 
@@ -783,8 +830,20 @@
         logv("onConfigReceived, subId:" + subId + ", config:"
                 + config + ", isCompressed:" + isCompressed);
         RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+        if (info == null) {
+            logd("sub[" + subId + "] has been removed");
+            return;
+        }
         info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
         updateConfigForSub(subId, config, isCompressed);
+
+        // Supporting ACS means config data comes from ACS
+        // store RCS metrics - received provisioning event
+        if (isAcsUsed(subId)) {
+            mRcsStats.onRcsAcsProvisioningStats(subId, 200,
+                    RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML,
+                    isRcsVolteSingleRegistrationEnabled(subId));
+        }
     }
 
     private void onReconfigRequest(int subId) {
@@ -796,6 +855,10 @@
             updateConfigForSub(subId, null, true);
             info.triggerRcsReconfiguration();
         }
+
+        // store RCS metrics - reconfig event
+        mRcsStats.onRcsClientProvisioningStats(subId,
+                RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION);
     }
 
     private void notifyDmaForSub(int subId, int capability) {
@@ -852,6 +915,10 @@
         }
     }
 
+    private static void logi(String msg) {
+        Rlog.i(TAG, msg);
+    }
+
     private static void logd(String msg) {
         Rlog.d(TAG, msg);
     }
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 04c4f48..8952865 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -43,7 +43,7 @@
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
-import android.util.Pair;
+import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -55,10 +55,10 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.MoreExecutors;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -333,19 +333,25 @@
         if (recordsSize == null || getRecordCount(recordsSize) == 0) {
             return;
         }
+        int efid = efIdForEfType(efType);
+        // Have to load the existing records to get the size because there may be more than one
+        // phonebook set in which case the total capacity is the sum of the capacity of EF_ADN for
+        // all the phonebook sets whereas the recordsSize is just the size for a single EF.
+        List<AdnRecord> existingRecords = mIccPhoneBookSupplier.get()
+                .getAdnRecordsInEfForSubscriber(subscriptionInfo.getSubscriptionId(), efid);
+        if (existingRecords == null) {
+            existingRecords = ImmutableList.of();
+        }
         MatrixCursor.RowBuilder row = result.newRow()
                 .add(ElementaryFiles.SLOT_INDEX, subscriptionInfo.getSimSlotIndex())
                 .add(ElementaryFiles.SUBSCRIPTION_ID, subscriptionInfo.getSubscriptionId())
                 .add(ElementaryFiles.EF_TYPE, efType)
-                .add(ElementaryFiles.MAX_RECORDS, getRecordCount(recordsSize))
+                .add(ElementaryFiles.MAX_RECORDS, existingRecords.size())
                 .add(ElementaryFiles.NAME_MAX_LENGTH,
                         AdnRecord.getMaxAlphaTagBytes(getRecordSize(recordsSize)))
                 .add(ElementaryFiles.PHONE_NUMBER_MAX_LENGTH,
                         AdnRecord.getMaxPhoneNumberDigits());
         if (result.getColumnIndex(ElementaryFiles.RECORD_COUNT) != -1) {
-            int efid = efIdForEfType(efType);
-            List<AdnRecord> existingRecords = mIccPhoneBookSupplier.get()
-                    .getAdnRecordsInEfForSubscriber(subscriptionInfo.getSubscriptionId(), efid);
             int nonEmptyCount = 0;
             for (AdnRecord record : existingRecords) {
                 if (!record.isEmpty()) {
@@ -368,39 +374,46 @@
             return new MatrixCursor(projection, 0);
         }
         MatrixCursor result = new MatrixCursor(projection, records.size());
-        List<Pair<AdnRecord, MatrixCursor.RowBuilder>> rowBuilders = new ArrayList<>(
-                records.size());
-        for (AdnRecord record : records) {
+        SparseArray<MatrixCursor.RowBuilder> rowBuilders = new SparseArray<>(records.size());
+        for (int i = 0; i < records.size(); i++) {
+            AdnRecord record = records.get(i);
             if (!record.isEmpty()) {
-                rowBuilders.add(Pair.create(record, result.newRow()));
+                rowBuilders.put(i, result.newRow());
             }
         }
         // This is kind of ugly but avoids looking up columns in an inner loop.
         for (String column : projection) {
             switch (column) {
                 case SimRecords.SUBSCRIPTION_ID:
-                    for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
-                        row.second.add(args.subscriptionId);
+                    for (int i = 0; i < rowBuilders.size(); i++) {
+                        rowBuilders.valueAt(i).add(args.subscriptionId);
                     }
                     break;
                 case SimRecords.ELEMENTARY_FILE_TYPE:
-                    for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
-                        row.second.add(args.efType);
+                    for (int i = 0; i < rowBuilders.size(); i++) {
+                        rowBuilders.valueAt(i).add(args.efType);
                     }
                     break;
                 case SimRecords.RECORD_NUMBER:
-                    for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
-                        row.second.add(row.first.getRecId());
+                    for (int i = 0; i < rowBuilders.size(); i++) {
+                        int index = rowBuilders.keyAt(i);
+                        MatrixCursor.RowBuilder rowBuilder = rowBuilders.valueAt(i);
+                        // See b/201685690. The logical record number, i.e. the 1-based index in the
+                        // list, is used the rather than AdnRecord.getRecId() because getRecId is
+                        // not offset when a single logical EF is made up of multiple physical EFs.
+                        rowBuilder.add(index + 1);
                     }
                     break;
                 case SimRecords.NAME:
-                    for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
-                        row.second.add(row.first.getAlphaTag());
+                    for (int i = 0; i < rowBuilders.size(); i++) {
+                        AdnRecord record = records.get(rowBuilders.keyAt(i));
+                        rowBuilders.valueAt(i).add(record.getAlphaTag());
                     }
                     break;
                 case SimRecords.PHONE_NUMBER:
-                    for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
-                        row.second.add(row.first.getNumber());
+                    for (int i = 0; i < rowBuilders.size(); i++) {
+                        AdnRecord record = records.get(rowBuilders.keyAt(i));
+                        rowBuilders.valueAt(i).add(record.getNumber());
                     }
                     break;
                 default:
@@ -765,20 +778,9 @@
         if (records == null || args.recordNumber > records.size()) {
             return null;
         }
-        AdnRecord result = records.get(args.recordNumber - 1);
-        // This should be true but the service could have a different implementation.
-        if (result.getRecId() == args.recordNumber) {
-            return result;
-        }
-        for (AdnRecord record : records) {
-            if (record.getRecId() == args.recordNumber) {
-                return result;
-            }
-        }
-        return null;
+        return records.get(args.recordNumber - 1);
     }
 
-
     private int[] getRecordsSizeForEf(PhonebookArgs args) {
         try {
             return mIccPhoneBookSupplier.get().getAdnRecordsSizeForSubscriber(
diff --git a/src/com/android/phone/SpecialCharSequenceMgr.java b/src/com/android/phone/SpecialCharSequenceMgr.java
index 674449e..3bf0e1a 100644
--- a/src/com/android/phone/SpecialCharSequenceMgr.java
+++ b/src/com/android/phone/SpecialCharSequenceMgr.java
@@ -264,7 +264,7 @@
                 }
                 return isMMIHandled;
             } else {
-                AlertDialog dialog = new AlertDialog.Builder(context)
+                AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(context)
                         .setMessage(R.string.pin_puk_system_user_only)
                         .setPositiveButton(R.string.ok, null)
                         .setCancelable(true).create();
@@ -294,7 +294,7 @@
         int labelId = TelephonyCapabilities.getDeviceIdLabel(phone);
         String deviceId = phone.getDeviceId();
 
-        AlertDialog alert = new AlertDialog.Builder(context)
+        AlertDialog alert = FrameworksUtils.makeAlertDialogBuilder(context)
                 .setTitle(labelId)
                 .setMessage(deviceId)
                 .setPositiveButton(R.string.ok, null)
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index 8c5ae6d..caef176 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -84,7 +84,7 @@
                 || id == FDN_CHECK_FAILURE || id == STK_CC_SS_TO_DIAL_ERROR
                 || id == STK_CC_SS_TO_USSD_ERROR || id == STK_CC_SS_TO_SS_ERROR
                 || id == STK_CC_SS_TO_DIAL_VIDEO_ERROR) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(this);
 
             int msgId;
             int titleId = R.string.error_updating_title;
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 224a1f9..6bc71dc 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -78,6 +78,9 @@
             new SubscriptionManager.OnSubscriptionsChangedListener() {
         @Override
         public void onSubscriptionsChanged() {
+            if (getActivity() == null) {
+                return;
+            }
             updateAccounts();
         }
     };
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 9c89e9e..9321e1e 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -787,12 +787,12 @@
             default:
                 break;
         }
-        return resourceId == null ? "" : context.getResources().getText(resourceId);
+        return resourceId == null ? "" : context.getResources().getString(resourceId);
     }
 
     private static boolean isRadioOffForThermalMitigation(int phoneId) {
         Phone phone = PhoneFactory.getPhone(phoneId);
-        return phone.isRadioOffForThermalMitigation();
+        return phone == null ? false : phone.isRadioOffForThermalMitigation();
     }
 
     /**
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 0be927a..c62b4fa 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -257,7 +257,7 @@
 
                 @Override
                 public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
-                    Log.d(this, "onConnectionPropertiesChanged: Connection: %s,"
+                    Log.i(ImsConference.this, "onConnectionPropertiesChanged: Connection: %s,"
                             + " connectionProperties: %s", c, connectionProperties);
                     updateConnectionProperties(connectionProperties);
                 }
@@ -383,6 +383,11 @@
     private boolean mIsUsingSimCallManager = false;
 
     /**
+     * See {@link #isRemotelyHosted()} for details.
+     */
+    private boolean mWasRemotelyHosted = false;
+
+    /**
      * Where {@link #isMultiparty()} is {@code false}, contains the
      * {@link ConferenceParticipantConnection#getUserEntity()} and
      * {@link ConferenceParticipantConnection#getEndpoint()} of the single participant which this
@@ -522,11 +527,16 @@
                 (properties & Connection.PROPERTY_IS_EXTERNAL_CALL) != 0);
 
         conferenceProperties = changeBitmask(conferenceProperties,
-                Connection.PROPERTY_REMOTELY_HOSTED, !isConferenceHost());
+                Connection.PROPERTY_REMOTELY_HOSTED, isRemotelyHosted());
 
         conferenceProperties = changeBitmask(conferenceProperties,
                 Connection.PROPERTY_IS_ADHOC_CONFERENCE,
                 (properties & Connection.PROPERTY_IS_ADHOC_CONFERENCE) != 0);
+        Log.i(this, "applyHostProperties: confProp=%s", conferenceProperties);
+
+        conferenceProperties = changeBitmask(conferenceProperties,
+                Connection.PROPERTY_CROSS_SIM,
+                (properties & Connection.PROPERTY_CROSS_SIM) != 0);
 
         return conferenceProperties;
     }
@@ -774,6 +784,26 @@
     }
 
     /**
+     * Returns whether the conference is remotely hosted or not.
+     * This method will cache the current remotely hosted state when the conference host or
+     * original connection becomes null.  This is important for scenarios where the conference host
+     * or original connection changes midway through a conference such as in an SRVCC scenario.
+     * @return {@code true} if the conference was remotely hosted based on the conference host and
+     * its original connection, or based on the last known remotely hosted state.  {@code false}
+     * otherwise.
+     */
+    public boolean isRemotelyHosted() {
+        if (mConferenceHost == null || mConferenceHost.getOriginalConnection() == null) {
+            return mWasRemotelyHosted;
+        }
+        com.android.internal.telephony.Connection originalConnection =
+                mConferenceHost.getOriginalConnection();
+        mWasRemotelyHosted = originalConnection.isMultiparty()
+                && !originalConnection.isConferenceHost();
+        return mWasRemotelyHosted;
+    }
+
+    /**
      * Determines if this conference is hosted on the current device or the peer device.
      *
      * @return {@code true} if this conference is hosted on the current device, {@code false} if it
@@ -1209,6 +1239,7 @@
         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
                 parent.getOriginalConnection(), participant,
                 !isConferenceHost() /* isRemotelyHosted */);
+
         if (participant.getConnectTime() == 0) {
             connection.setConnectTimeMillis(parent.getConnectTimeMillis());
             connection.setConnectionStartElapsedRealtimeMillis(
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 228541a..9aa3dbe 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -40,6 +40,7 @@
  */
 final class TelephonyConferenceController {
     private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
+    private static final String RIL_REPORTED_CONFERENCE_CALL_STRING = "Conference Call";
 
     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
             new TelephonyConnection.TelephonyConnectionListener() {
@@ -179,7 +180,6 @@
     private void recalculateConference() {
         Set<TelephonyConnection> conferencedConnections = new HashSet<>();
         int numGsmConnections = 0;
-
         for (TelephonyConnection connection : mTelephonyConnections) {
             com.android.internal.telephony.Connection radioConnection =
                 connection.getOriginalConnection();
@@ -271,11 +271,19 @@
                             // Remove all instances of PROPERTY_IS_DOWNGRADED_CONFERENCE. This
                             // property should only be set on the parent call (i.e. the newly
                             // created TelephonyConference.
-                            Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE from connection"
-                                    + " %s", connection);
-                            int newProperties = connection.getConnectionProperties()
-                                    & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
-                            connection.setTelephonyConnectionProperties(newProperties);
+                            // This doesn't apply to a connection whose address is "Conference
+                            // Call", which may be updated by some modem to create a connection
+                            // to represent a merged conference connection in SRVCC.
+                            if (connection.getAddress() == null
+                                    || !connection.getAddress().getSchemeSpecificPart()
+                                            .equalsIgnoreCase(
+                                                    RIL_REPORTED_CONFERENCE_CALL_STRING)) {
+                                Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE"
+                                        + " from connection %s", connection);
+                                int newProperties = connection.getConnectionProperties()
+                                        & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
+                                connection.setTelephonyConnectionProperties(newProperties);
+                            }
                             isDowngradedConference = true;
                         }
                         mTelephonyConference.addTelephonyConnection(connection);
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
old mode 100755
new mode 100644
index 53e923e..ed07726
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -2707,6 +2707,10 @@
         capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
                 isLocalVideoSupported);
 
+        capabilities = changeBitmask(capabilities, CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT,
+                (mOriginalConnectionCapabilities & Capability.SUPPORTS_RTT_REMOTE)
+                == Capability.SUPPORTS_RTT_REMOTE);
+
         return capabilities;
     }
 
@@ -3810,4 +3814,13 @@
             mCommunicator.sendMessages(set);
         }
     }
+
+    /**
+     * Returns the current telephony connection listeners for test purposes.
+     * @return list of telephony connection listeners.
+     */
+    @VisibleForTesting
+    public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
+        return new ArrayList<>(mTelephonyListeners);
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 766a75e..55456f6 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -65,6 +65,7 @@
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.phone.FrameworksUtils;
 import com.android.phone.MMIDialogActivity;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
@@ -498,7 +499,8 @@
 
         IntentFilter intentFilter = new IntentFilter(
                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
-        registerReceiver(mTtyBroadcastReceiver, intentFilter);
+        registerReceiver(mTtyBroadcastReceiver, intentFilter,
+                android.Manifest.permission.MODIFY_PHONE_STATE, null);
     }
 
     @Override
@@ -2459,7 +2461,7 @@
                 if (showDialog) {
                     Log.d(this, "Creating UT Data enable dialog");
                     String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
-                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
+                    AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(context);
                     DialogInterface.OnClickListener networkSettingsClickListener =
                             new Dialog.OnClickListener() {
                                 @Override
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 7834903..cc1a2cc 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ImsStateCallbackController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -139,6 +140,8 @@
                         // ImsService is gone.
                         updateConnectionStatus(manager);
                         setupConnectionToService(manager);
+                        ImsStateCallbackController.getInstance()
+                                .notifyExternalRcsStateChanged(mSlotId, true, true);
                     } catch (ImsException e) {
                         updateConnectionStatus(null /*manager*/);
                         // Use deprecated Exception for compatibility.
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index 034382c..dfcea74 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -32,7 +32,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.metrics.RcsStats;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ImsStateCallbackController;
 import com.android.phone.R;
 
 import java.io.FileDescriptor;
@@ -163,6 +165,7 @@
         mFeatureControllers = new SparseArray<>(numSlots);
         mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
         mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+        RcsStats.getInstance().registerUceCallback();
     }
 
     @VisibleForTesting
@@ -173,6 +176,7 @@
         mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
         sResourceProxy = resourceProxy;
         mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+        RcsStats.getInstance().registerUceCallback();
     }
 
     /**
@@ -310,6 +314,9 @@
         }
         // Only start the connection procedure if we have active features.
         if (c.hasActiveFeatures()) c.connect();
+
+        ImsStateCallbackController.getInstance()
+                .notifyExternalRcsStateChanged(slotId, false, c.hasActiveFeatures());
     }
 
     /**
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
new file mode 100644
index 0000000..75690bb
--- /dev/null
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
+import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
+import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.telephony.IImsStateCallback;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.ims.ImsResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for RcsProvisioningMonitor
+ */
+public class ImsStateCallbackControllerTest {
+    private static final String TAG = "ImsStateCallbackControllerTest";
+    private static final int FAKE_SUB_ID_BASE = 0x0FFFFFF0;
+
+    private static final int SLOT_0 = 0;
+    private static final int SLOT_1 = 1;
+
+    private static final int SLOT_0_SUB_ID = 1;
+    private static final int SLOT_1_SUB_ID = 2;
+    private static final int SLOT_2_SUB_ID = 3;
+
+    private ImsStateCallbackController mImsStateCallbackController;
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+    private TestableLooper mLooper;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
+    @Mock private TelephonyRegistryManager mTelephonyRegistryManager;
+    @Mock private ITelephony.Stub mITelephony;
+    @Mock private RcsFeatureManager mRcsFeatureManager;
+    @Mock private ImsManager mMmTelFeatureManager;
+    @Mock private ImsStateCallbackController.MmTelFeatureConnectorFactory mMmTelFeatureFactory;
+    @Mock private ImsStateCallbackController.RcsFeatureConnectorFactory mRcsFeatureFactory;
+    @Mock private FeatureConnector<ImsManager> mMmTelFeatureConnectorSlot0;
+    @Mock private FeatureConnector<ImsManager> mMmTelFeatureConnectorSlot1;
+    @Mock private FeatureConnector<RcsFeatureManager> mRcsFeatureConnectorSlot0;
+    @Mock private FeatureConnector<RcsFeatureManager> mRcsFeatureConnectorSlot1;
+    @Captor ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListenerSlot0;
+    @Captor ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListenerSlot1;
+    @Captor ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListenerSlot0;
+    @Captor ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListenerSlot1;
+    @Mock private PhoneGlobals mPhone;
+    @Mock ImsStateCallbackController.PhoneFactoryProxy mPhoneFactoryProxy;
+    @Mock Phone mPhoneSlot0;
+    @Mock Phone mPhoneSlot1;
+    @Mock private IBinder mBinder0;
+    @Mock private IBinder mBinder1;
+    @Mock private IBinder mBinder2;
+    @Mock private IBinder mBinder3;
+    @Mock private IImsStateCallback mCallback0;
+    @Mock private IImsStateCallback mCallback1;
+    @Mock private IImsStateCallback mCallback2;
+    @Mock private IImsStateCallback mCallback3;
+    @Mock private ImsResolver mImsResolver;
+
+    private Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mPhone.getMainExecutor()).thenReturn(mExecutor);
+        when(mPhone.getSystemServiceName(eq(SubscriptionManager.class)))
+                .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+                .thenReturn(mSubscriptionManager);
+        when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class)))
+                .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE);
+        when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE)))
+                .thenReturn(mTelephonyRegistryManager);
+        when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(mPhoneSlot0);
+        when(mPhoneFactoryProxy.getPhone(eq(1))).thenReturn(mPhoneSlot1);
+        when(mPhoneSlot0.getSubId()).thenReturn(SLOT_0_SUB_ID);
+        when(mPhoneSlot1.getSubId()).thenReturn(SLOT_1_SUB_ID);
+
+        when(mCallback0.asBinder()).thenReturn(mBinder0);
+        when(mCallback1.asBinder()).thenReturn(mBinder1);
+        when(mCallback2.asBinder()).thenReturn(mBinder2);
+        when(mCallback3.asBinder()).thenReturn(mBinder3);
+
+        // slot 0
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(0), eq(FEATURE_MMTEL)))
+                .thenReturn(true);
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(0), eq(FEATURE_RCS)))
+                .thenReturn(true);
+
+        // slot 1
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+                .thenReturn(true);
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_RCS)))
+                .thenReturn(true);
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mSubChangedListener = (SubscriptionManager.OnSubscriptionsChangedListener)
+                        invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class),
+                any());
+
+        mHandlerThread = new HandlerThread("ImsStateCallbackControllerTest");
+        mHandlerThread.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mImsStateCallbackController != null) {
+            mImsStateCallbackController.destroy();
+            mImsStateCallbackController = null;
+        }
+
+        if (mLooper != null) {
+            mLooper.destroy();
+            mLooper = null;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testMmTelRegisterThenUnregisterCallback() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testMmTelConnectionUnavailable() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testMmTelConnectionReady() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback0, times(0)).onAvailable();
+
+        mMmTelConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, atLeastOnce()).onAvailable();
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testMmTelIgnoreDuplicatedConsecutiveReason() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRcsRegisterThenUnregisterCallback() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRcsConnectionUnavailable() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // TelephonyRcsService notifying active features
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+        processAllMessages();
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRcsConnectionReady() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // TelephonyRcsService notifying active features
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+        processAllMessages();
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(0)).onAvailable();
+
+        // RcsFeatureController notifying STATE_READY
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+        processAllMessages();
+        verify(mCallback0, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(2)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        // RcsFeatureController notifying STATE_READY
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+
+        mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(2)).onAvailable();
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRcsHasNoActiveFeature() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // TelephonyRcsService notifying NO active feature
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, false);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(0)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(0)).onAvailable();
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRcsIgnoreDuplicatedConsecutiveReason() throws Exception {
+        createController(1);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // TelephonyRcsService notifying active features
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+        processAllMessages();
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+    }
+
+    @Test
+    @SmallTest
+    public void testCallbackRemovedWhenSubInfoChanged() throws Exception {
+        createController(2);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback1);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        makeFakeActiveSubIds(0);
+        mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+        processAllMessages();
+
+        verify(mCallback0, times(1)).onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+        verify(mCallback1, times(1)).onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigurationChanged() throws Exception {
+        createController(2);
+
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+                .thenReturn(false);
+
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_MMTEL, mCallback1);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback2);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+
+        // check initial reason
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback1, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        // ensure only one reason reported until now
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        // state change in RCS for slot 0
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+
+        // ensure there is no change, since callbacks are not interested RCS on slot 0
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        // carrier config changed, no MMTEL package for slot 1
+        mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+        processAllMessages();
+
+        // only the callback for MMTEL of slot 1 received the reason
+        verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback1, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        // ensure no other callbacks
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        mMmTelConnectorListenerSlot1.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        mMmTelConnectorListenerSlot1.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+
+        // resons except REASON_NO_IMS_SERVICE_CONFIGURED are discared
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        // IMS package for MMTEL of slot 1 is added
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+                .thenReturn(true);
+        mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+        processAllMessages();
+
+        // ensure the callback to MMTEL of slot 1 only received REASON_IMS_SERVICE_DISCONNECTED
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // ensure no other reason repored
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(3)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        // carrier config changed, no MMTEL package for slot 1
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+                .thenReturn(false);
+        mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+        mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+        processAllMessages();
+        // only the callback for MMTEL of slot 1 received the reason
+        verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback1, times(2)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+        verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+        // ensure no other reason repored
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(4)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        mMmTelConnectorListenerSlot1.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+
+        // resons except REASON_NO_IMS_SERVICE_CONFIGURED are discared
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(4)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        // IMS package for MMTEL of slot 1 is added
+        when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+                .thenReturn(true);
+        mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+        processAllMessages();
+
+        // ensure the callback to MMTEL of slot 1
+        // there is a pending reason UNAVAILABLE_REASON_NOT_READY
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // ensure no other reason repored
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(5)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback1);
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback2);
+        processAllMessages();
+
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+    }
+
+    @Test
+    @SmallTest
+    public void testMultiSubscriptions() throws Exception {
+        createController(2);
+
+        // registration
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback1);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_MMTEL, mCallback2);
+        mImsStateCallbackController
+                .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback3);
+        processAllMessages();
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+        verify(mCallback3, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+        // TelephonyRcsService notifying active features
+        // slot 0
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+        // slot 1
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_1, false, true);
+        processAllMessages();
+
+        verify(mCallback0, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+        verify(mCallback0, times(0)).onAvailable();
+        verify(mCallback1, times(0)).onAvailable();
+        verify(mCallback2, times(0)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+
+        // connectionUnavailable
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+        mRcsConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+        mMmTelConnectorListenerSlot1.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+        mRcsConnectorListenerSlot1.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+        verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        // connectionReady
+        mMmTelConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(0)).onAvailable();
+        verify(mCallback2, times(0)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(0)).onAvailable();
+        verify(mCallback2, times(0)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(1)).onAvailable();
+        verify(mCallback2, times(0)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        mMmTelConnectorListenerSlot1.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(1)).onAvailable();
+        verify(mCallback2, times(1)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        mRcsConnectorListenerSlot1.getValue().connectionReady(null);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(1)).onAvailable();
+        verify(mCallback2, times(1)).onAvailable();
+        verify(mCallback3, times(0)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_1, true, true);
+        processAllMessages();
+        verify(mCallback0, times(1)).onAvailable();
+        verify(mCallback1, times(1)).onAvailable();
+        verify(mCallback2, times(1)).onAvailable();
+        verify(mCallback3, times(1)).onAvailable();
+        verify(mCallback0, times(2)).onUnavailable(anyInt());
+        verify(mCallback1, times(2)).onUnavailable(anyInt());
+        verify(mCallback2, times(2)).onUnavailable(anyInt());
+        verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+        // unregistration
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback1);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback2);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+        assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+        mImsStateCallbackController.unregisterImsStateCallback(mCallback3);
+        processAllMessages();
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+        assertFalse(mImsStateCallbackController.isRegistered(mCallback3));
+    }
+
+    @Test
+    @SmallTest
+    public void testSlotUpdates() throws Exception {
+        createController(1);
+
+        verify(mMmTelFeatureConnectorSlot0, times(1)).connect();
+        verify(mRcsFeatureConnectorSlot0, times(1)).connect();
+        verify(mMmTelFeatureConnectorSlot0, times(0)).disconnect();
+        verify(mRcsFeatureConnectorSlot0, times(0)).disconnect();
+
+        // Add a new slot.
+        mImsStateCallbackController.updateFeatureControllerSize(2);
+
+        // connect in slot 1
+        verify(mMmTelFeatureConnectorSlot1, times(1)).connect();
+        verify(mRcsFeatureConnectorSlot1, times(1)).connect();
+
+        // no change in slot 0
+        verify(mMmTelFeatureConnectorSlot0, times(1)).connect();
+        verify(mRcsFeatureConnectorSlot0, times(1)).connect();
+
+        // Remove a slot.
+        mImsStateCallbackController.updateFeatureControllerSize(1);
+
+        // destroy in slot 1
+        verify(mMmTelFeatureConnectorSlot1, times(1)).disconnect();
+        verify(mRcsFeatureConnectorSlot1, times(1)).disconnect();
+
+        // no change in slot 0
+        verify(mMmTelFeatureConnectorSlot0, times(0)).disconnect();
+        verify(mRcsFeatureConnectorSlot0, times(0)).disconnect();
+    }
+
+    private void createController(int slotCount) throws Exception {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        makeFakeActiveSubIds(slotCount);
+
+        when(mMmTelFeatureFactory
+                .create(any(), eq(0), any(), mMmTelConnectorListenerSlot0.capture(), any()))
+                .thenReturn(mMmTelFeatureConnectorSlot0);
+        when(mMmTelFeatureFactory
+                .create(any(), eq(1), any(), mMmTelConnectorListenerSlot1.capture(), any()))
+                .thenReturn(mMmTelFeatureConnectorSlot1);
+        when(mRcsFeatureFactory
+                .create(any(), eq(0), mRcsConnectorListenerSlot0.capture(), any(), any()))
+                .thenReturn(mRcsFeatureConnectorSlot0);
+        when(mRcsFeatureFactory
+                .create(any(), eq(1), mRcsConnectorListenerSlot1.capture(), any(), any()))
+                .thenReturn(mRcsFeatureConnectorSlot1);
+
+        mImsStateCallbackController =
+                new ImsStateCallbackController(mPhone, mHandlerThread.getLooper(),
+                        slotCount, mMmTelFeatureFactory, mRcsFeatureFactory, mImsResolver);
+
+        replaceInstance(ImsStateCallbackController.class,
+                "mPhoneFactoryProxy", mImsStateCallbackController, mPhoneFactoryProxy);
+        mImsStateCallbackController.onSubChanged();
+
+        mHandler = mImsStateCallbackController.getHandler();
+        try {
+            mLooper = new TestableLooper(mHandler.getLooper());
+        } catch (Exception e) {
+            logd("Unable to create looper from handler.");
+        }
+
+        verify(mRcsFeatureConnectorSlot0, atLeastOnce()).connect();
+        verify(mMmTelFeatureConnectorSlot0, atLeastOnce()).connect();
+
+        if (slotCount == 1) {
+            verify(mRcsFeatureConnectorSlot1, times(0)).connect();
+            verify(mMmTelFeatureConnectorSlot1, times(0)).connect();
+        } else {
+            verify(mRcsFeatureConnectorSlot1, atLeastOnce()).connect();
+            verify(mMmTelFeatureConnectorSlot1, atLeastOnce()).connect();
+        }
+    }
+
+    private static void replaceInstance(final Class c,
+            final String instanceName, final Object obj, final Object newValue) throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+        field.set(obj, newValue);
+    }
+
+    private void makeFakeActiveSubIds(int count) {
+        final int[] subIds = new int[count];
+        for (int i = 0; i < count; i++) {
+            subIds[i] = FAKE_SUB_ID_BASE + i;
+        }
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
+    }
+
+    private void processAllMessages() {
+        while (!mLooper.getLooper().getQueue().isIdle()) {
+            mLooper.processAllMessages();
+        }
+    }
+
+    private static void logd(String str) {
+        Log.d(TAG, str);
+    }
+}
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 4fba922..8873402 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -16,6 +16,10 @@
 
 package com.android.phone;
 
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
@@ -64,6 +68,7 @@
 import com.android.ims.FeatureConnector;
 import com.android.ims.RcsFeatureManager;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.metrics.RcsStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -179,6 +184,10 @@
     private IRcsConfigCallback mCallback;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private RcsStats mRcsStats;
+    @Mock
+    private RcsStats.RcsProvisioningCallback mRcsProvisioningCallback;
 
     private Executor mExecutor = new Executor() {
         @Override
@@ -305,7 +314,6 @@
     @Test
     @SmallTest
     public void testInitWithSavedConfig() throws Exception {
-        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
         createMonitor(3);
 
         for (int i = 0; i < 3; i++) {
@@ -313,10 +321,6 @@
                     mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
         }
 
-        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
-        Intent capturedIntent = captorIntent.getAllValues().get(1);
-        assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
-                capturedIntent.getAction());
         verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
     }
 
@@ -324,14 +328,8 @@
     @SmallTest
     public void testInitWithoutSavedConfig() throws Exception {
         when(mCursor.getBlob(anyInt())).thenReturn(null);
-        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
         createMonitor(3);
 
-        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
-        Intent capturedIntent = captorIntent.getAllValues().get(1);
-
-        assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
-                capturedIntent.getAction());
         //Should not notify null config
         verify(mIImsConfig, never()).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
     }
@@ -340,16 +338,12 @@
     @SmallTest
     public void testSubInfoChanged() throws Exception {
         createMonitor(3);
-        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
 
         for (int i = 0; i < 3; i++) {
             assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(),
                     mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
         }
-        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
-        Intent capturedIntent = captorIntent.getAllValues().get(1);
-        assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
-                capturedIntent.getAction());
+
         verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
 
         makeFakeActiveSubIds(1);
@@ -401,14 +395,20 @@
     @SmallTest
     public void testCarrierConfigChanged() throws Exception {
         createMonitor(1);
+        // should not broadcast message if carrier config is not ready
+        verify(mPhone, never()).sendBroadcast(any(), any());
+
         when(mPackageManager.hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mBundle.putBoolean(
                 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
         processAllMessages();
-        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+
+        verify(mPhone, times(1)).sendBroadcast(captorIntent.capture(), any());
         Intent capturedIntent = captorIntent.getValue();
         assertEquals(capturedIntent.getAction(),
                 ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
@@ -421,7 +421,8 @@
                 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
         processAllMessages();
-        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+
+        verify(mPhone, times(2)).sendBroadcast(captorIntent.capture(), any());
         capturedIntent = captorIntent.getValue();
         assertEquals(capturedIntent.getAction(),
                 ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
@@ -435,7 +436,8 @@
                 eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
         processAllMessages();
-        verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+
+        verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
         capturedIntent = captorIntent.getValue();
         assertEquals(capturedIntent.getAction(),
                 ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
@@ -479,6 +481,7 @@
 
         when(mPackageManager.hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mBundle.putBoolean(
                 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
@@ -580,13 +583,39 @@
     @Test
     @SmallTest
     public void testSendBroadcastWhenDmaChanged() throws Exception {
-        createMonitor(3);
-        verify(mPhone, times(3)).sendBroadcast(any(), any());
-
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        createMonitor(1);
         updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
         processAllMessages();
 
-        verify(mPhone, times(6)).sendBroadcast(any(), any());
+        // should not broadcast message as no carrier config change happens
+        verify(mPhone, never()).sendBroadcast(any(), any());
+
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+        when(mPackageManager.hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        mBundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+
+        verify(mPhone, times(1)).sendBroadcast(captorIntent.capture(), any());
+        Intent capturedIntent = captorIntent.getValue();
+        assertEquals(capturedIntent.getAction(),
+                ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+
+        updateDefaultMessageApplication(DEFAULT_MESSAGING_APP1);
+        processAllMessages();
+
+        // should broadcast message when default messaging application changed if carrier config
+        // has been loaded
+        verify(mPhone, times(2)).sendBroadcast(captorIntent.capture(), any());
+        capturedIntent = captorIntent.getValue();
+        assertEquals(capturedIntent.getAction(),
+                ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
     }
 
     @Test
@@ -611,6 +640,7 @@
 
         when(mPackageManager.hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mBundle.putBoolean(
                 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
@@ -694,6 +724,7 @@
 
         when(mPackageManager.hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mBundle.putBoolean(
                 CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
         broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
@@ -746,6 +777,66 @@
         assertNull(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
     }
 
+    @Test
+    @SmallTest
+    public void testMetricsAcsNotUsed() throws Exception {
+        createMonitor(1);
+
+        // Not used ACS
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, false);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+        processAllMessages();
+        verify(mRcsStats, never()).onRcsAcsProvisioningStats(anyInt(), anyInt(),
+                anyInt(), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
+    public void testMetricsAcsUsed() throws Exception {
+        when(mRcsStats.getRcsProvisioningCallback(anyInt(), anyBoolean()))
+                .thenReturn(mRcsProvisioningCallback);
+        createMonitor(1);
+
+        verify(mIImsConfig, times(1))
+                .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+        // verify RcsStats.getRcsProvisioningCallback() is called
+        verify(mRcsStats, times(1)).getRcsProvisioningCallback(
+                eq(FAKE_SUB_ID_BASE), anyBoolean());
+        // verify registered callback obj which comes from RcsStats.getRcsProvisioningCallback()
+        verify(mIImsConfig, times(1))
+                .addRcsConfigCallback(eq(mRcsProvisioningCallback));
+
+        // Config data received and ACS used
+        int errorCode = 200;
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, true);
+        broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+        processAllMessages();
+        verify(mRcsStats, times(1)).onRcsAcsProvisioningStats(eq(FAKE_SUB_ID_BASE), eq(errorCode),
+                eq(RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
+    public void testMetricsClientProvisioningStats() throws Exception {
+        createMonitor(1);
+
+        // reconfig trigger
+        mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE);
+        processAllMessages();
+        verify(mRcsStats, times(1)).onRcsClientProvisioningStats(eq(FAKE_SUB_ID_BASE),
+                eq(RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION));
+
+        // DMA changed
+        updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+        processAllMessages();
+        verify(mRcsStats, times(1)).onRcsClientProvisioningStats(eq(FAKE_SUB_ID_BASE),
+                eq(RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED));
+    }
+
     private void createMonitor(int subCount) throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
@@ -755,7 +846,7 @@
                 .thenReturn(mFeatureConnector);
         when(mFeatureManager.getConfig()).thenReturn(mIImsConfig);
         mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(),
-                mRoleManager, mFeatureFactory);
+                mRoleManager, mFeatureFactory, mRcsStats);
         mHandler = mRcsProvisioningMonitor.getHandler();
         try {
             mLooper = new TestableLooper(mHandler.getLooper());
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
index 848ba42..d8518f8 100644
--- a/tests/src/com/android/phone/SimPhonebookProviderTest.java
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -181,16 +181,16 @@
     public void query_entityFiles_multiSim_returnsCursorWithRowForEachSimEf() {
         setupSimsWithSubscriptionIds(2, 3, 7);
 
-        mIccPhoneBook.setRecordsSize(2, IccConstants.EF_ADN, 10, 25);
-        mIccPhoneBook.setRecordsSize(2, IccConstants.EF_FDN, 5, 20);
-        mIccPhoneBook.setRecordsSize(2, IccConstants.EF_SDN, 15, 20);
-        mIccPhoneBook.setRecordsSize(3, IccConstants.EF_ADN, 100, 30);
+        mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_ADN, 10, 25);
+        mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_FDN, 5, 20);
+        mIccPhoneBook.setupEfWithSizes(2, IccConstants.EF_SDN, 15, 20);
+        mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_ADN, 100, 30);
         // These Will be omitted from results because zero size indicates the EF is not supported.
-        mIccPhoneBook.setRecordsSize(3, IccConstants.EF_FDN, 0, 0);
-        mIccPhoneBook.setRecordsSize(3, IccConstants.EF_SDN, 0, 0);
-        mIccPhoneBook.setRecordsSize(7, IccConstants.EF_ADN, 0, 0);
-        mIccPhoneBook.setRecordsSize(7, IccConstants.EF_FDN, 0, 0);
-        mIccPhoneBook.setRecordsSize(7, IccConstants.EF_SDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_FDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(3, IccConstants.EF_SDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_ADN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_FDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(7, IccConstants.EF_SDN, 0, 0);
 
         String[] projection = {
                 ElementaryFiles.SLOT_INDEX, ElementaryFiles.SUBSCRIPTION_ID,
@@ -212,15 +212,44 @@
     public void query_entityFiles_simWithZeroSizes_returnsEmptyCursor() {
         setupSimsWithSubscriptionIds(1);
 
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 0, 0);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 0, 0);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 0, 0);
 
         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
             assertThat(cursor).hasCount(0);
         }
     }
 
+    /**
+     * USIM cards support more than 255 records by having multiple files for one EF type but
+     * IIccPhoneBook.getAdnRecordsSizeForSubscriber returns the size for a single file and so is
+     * inaccurate for such SIMs.
+     *
+     * <p>See b/201385523#comment4 and b/201685690
+     */
+    @Test
+    public void query_entityFiles_adnRecordCountExceedsSize_returnsAdnRecordCountAsMaxRecords() {
+        setupSimsWithSubscriptionIds(1);
+
+        // There are 400 records returned by getAdnRecordsInEfForSubscriber but the count returned
+        // by getAdnRecordsSizeForSubscriber is only 200.
+        AdnRecord[] records = mIccPhoneBook.createEmptyRecords(IccConstants.EF_ADN, 400);
+        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 200, 20);
+        mIccPhoneBook.setRecords(1, IccConstants.EF_ADN, records);
+
+        String[] projection = {
+                ElementaryFiles.SUBSCRIPTION_ID, ElementaryFiles.EF_TYPE,
+                ElementaryFiles.MAX_RECORDS
+        };
+        try (Cursor cursor = mResolver.query(
+                ElementaryFiles.CONTENT_URI, projection, null, null)) {
+            assertThat(cursor).hasCount(1);
+            assertThat(cursor)
+                    .atRow(0).hasRowValues(1, ElementaryFiles.EF_ADN, 400);
+        }
+    }
+
     @Test
     public void query_entityFilesItem_nullProjection_returnsCursorWithCorrectProjection() {
         setupSimsWithSubscriptionIds(1);
@@ -549,7 +578,7 @@
     @Test
     public void query_adnRecords_zeroSizeEf_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 0, 0);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 0, 0);
 
         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
                 () -> mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null, null));
@@ -618,9 +647,9 @@
     @Test
     public void query_itemUriEmptyRecord_returnsEmptyCursor() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 1, 30);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 1, 30);
 
         try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
                 null, null, null);
@@ -638,9 +667,9 @@
     @Test
     public void query_itemUriIndexExceedsMax_returnsEmptyCursor() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 1, 30);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_FDN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_SDN, 1, 30);
 
         try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2),
                 null, null, null);
@@ -741,7 +770,7 @@
     @Test
     public void insert_efFull_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Existing", "8005550101");
 
         ContentValues values = new ContentValues();
@@ -843,7 +872,7 @@
     @Test
     public void insert_phoneNumberOmitted_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
 
         ContentValues values = new ContentValues();
         values.put(SimRecords.NAME, "Name");
@@ -856,7 +885,7 @@
     @Test
     public void insert_nameTooLong_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
 
         ContentValues values = new ContentValues();
         // Name is limited to 11 characters when the max record size is 25
@@ -879,7 +908,7 @@
     @Test
     public void insert_phoneNumberTooLong_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
 
         ContentValues values = new ContentValues();
         values.put(SimRecords.NAME, "Name");
@@ -895,7 +924,7 @@
     @Test
     public void insert_numberWithInvalidCharacters_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 32);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 32);
 
         ContentValues values = new ContentValues();
         values.put(SimRecords.NAME, "Name");
@@ -915,7 +944,7 @@
     @Test
     public void insert_unsupportedColumn_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
 
         ContentValues values = new ContentValues();
         values.put(SimRecords.NAME, "Name");
@@ -1007,7 +1036,7 @@
     @Test
     public void update_indexExceedingMax_returnsZero() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
 
         ContentValues values = new ContentValues();
         values.put(SimRecords.NAME, "name");
@@ -1046,7 +1075,7 @@
     public void delete_indexExceedingMax_returnsZero() {
         setupSimsWithSubscriptionIds(1);
         mIccPhoneBook.makeAllEfsSupported(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 30);
 
         int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
 
@@ -1067,7 +1096,7 @@
     @Test
     public void update_nameOrNumberTooLong_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 25);
         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
 
         ContentValues values = new ContentValues();
@@ -1097,7 +1126,7 @@
     @Test
     public void update_numberWithInvalidCharacters_throwsCorrectException() {
         setupSimsWithSubscriptionIds(1);
-        mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 32);
+        mIccPhoneBook.setupEfWithSizes(1, IccConstants.EF_ADN, 1, 32);
         mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
 
         ContentValues values = new ContentValues();
@@ -1324,7 +1353,7 @@
         // The key for both maps is the (subscription ID, efid)
         private Map<Pair<Integer, Integer>, AdnRecord[]> mRecords = new HashMap<>();
         // The value is the single record size
-        private Map<Pair<Integer, Integer>, Integer> mRecordSizes = new HashMap<>();
+        private Map<Pair<Integer, Integer>, int[]> mRecordSizes = new HashMap<>();
 
         private int mDefaultSubscriptionId = 101;
 
@@ -1332,7 +1361,7 @@
             // Assume that if records are being added then the test wants it to be a valid
             // elementary file so set sizes as well.
             if (!mRecordSizes.containsKey(key)) {
-                setRecordsSize(key.first, key.second,
+                setupEfWithSizes(key.first, key.second,
                         Math.max(record.getRecId(), DEFAULT_RECORDS_COUNT), DEFAULT_RECORD_SIZE);
             }
             mRecords.get(key)[record.getRecId() - 1] = record;
@@ -1402,18 +1431,33 @@
          * subscription IDs.
          */
         public void makeAllEfsSupported(int subscriptionId) {
-            setRecordsSize(subscriptionId, IccConstants.EF_ADN, DEFAULT_RECORDS_COUNT,
+            setupEfWithSizes(subscriptionId, IccConstants.EF_ADN, DEFAULT_RECORDS_COUNT,
                     DEFAULT_RECORD_SIZE);
-            setRecordsSize(subscriptionId, IccConstants.EF_FDN, DEFAULT_RECORDS_COUNT,
+            setupEfWithSizes(subscriptionId, IccConstants.EF_FDN, DEFAULT_RECORDS_COUNT,
                     DEFAULT_RECORD_SIZE);
-            setRecordsSize(subscriptionId, IccConstants.EF_SDN, DEFAULT_RECORDS_COUNT,
+            setupEfWithSizes(subscriptionId, IccConstants.EF_SDN, DEFAULT_RECORDS_COUNT,
                     DEFAULT_RECORD_SIZE);
         }
 
+        public void setRecords(int subscriptionId, int efid, AdnRecord[] records) {
+            mRecords.put(Pair.create(subscriptionId, efid), records);
+        }
+
         public void setRecordsSize(int subscriptionId, int efid, int maxRecordCount,
                 int maxRecordSize) {
+            setRecordsSize(Pair.create(subscriptionId, efid), maxRecordCount, maxRecordSize);
+        }
+
+        private void setRecordsSize(Pair<Integer, Integer> key, int maxRecordCount,
+                int maxRecordSize) {
+            int[] sizes = { maxRecordSize, maxRecordSize * maxRecordCount, maxRecordCount };
+            mRecordSizes.put(key, sizes);
+        }
+
+        public void setupEfWithSizes(int subscriptionId, int efid, int maxRecordCount,
+                int maxRecordSize) {
             Pair<Integer, Integer> key = Pair.create(subscriptionId, efid);
-            mRecordSizes.put(key, maxRecordSize);
+            setRecordsSize(key, maxRecordCount, maxRecordSize);
             AdnRecord[] records = mRecords.computeIfAbsent(key, unused ->
                     createEmptyRecords(efid, maxRecordCount));
             if (records.length < maxRecordCount) {
@@ -1421,7 +1465,7 @@
             }
         }
 
-        private AdnRecord[] createEmptyRecords(int efid, int count) {
+        AdnRecord[] createEmptyRecords(int efid, int count) {
             AdnRecord[] records = new AdnRecord[count];
             for (int i = 0; i < records.length; i++) {
                 if (records[i] == null) {
@@ -1499,12 +1543,11 @@
         @Override
         public int[] getAdnRecordsSizeForSubscriber(int subId, int efid) {
             Pair<Integer, Integer> key = Pair.create(subId, efid);
-            Integer recordSize = mRecordSizes.get(key);
-            if (recordSize == null) {
+            int[] recordsSize = mRecordSizes.get(key);
+            if (recordsSize == null) {
                 return new int[]{0, 0, 0};
             }
-            int count = mRecords.get(key).length;
-            return new int[]{recordSize, recordSize * count, count};
+            return recordsSize;
         }
 
         @Override
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 3bc5ee8..9d2f5ac 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -622,6 +622,46 @@
     }
 
     /**
+     * Tests a scenario where a handover connection arrives via
+     * {@link TelephonyConnection#onOriginalConnectionRedialed(
+     * com.android.internal.telephony.Connection)}.  During this process, the conference properties
+     * get updated.  Since the original connection is null at this point, we need to verify that
+     * the remotely hosted property is retained from before the original connection was nulled.
+     */
+    @Test
+    public void testIsConferenceRemotelyHostedCachingOnSRVCC() {
+        mConferenceHost.setIsImsConnection(true);
+        when(mConferenceHost.getMockImsPhoneConnection().isMultiparty()).thenReturn(true);
+        when(mConferenceHost.getMockImsPhoneConnection().isConferenceHost()).thenReturn(true);
+
+        // Start out with a valid conference host.
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
+
+        // By default it is not remotely hosted.
+        assertFalse(imsConference.isRemotelyHosted());
+        assertEquals(0,
+                imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+
+        // Simulate a change to the original connection due to srvcc
+        com.android.internal.telephony.Connection previousOriginalConnection =
+                mConferenceHost.getMockImsPhoneConnection();
+        mConferenceHost.setMockImsPhoneConnection(null);
+
+        // Trigger the property update which takes place when the original connection changes.
+        mConferenceHost.getTelephonyConnectionListeners().forEach(
+                l -> l.onConnectionPropertiesChanged(mConferenceHost,
+                        mConferenceHost.getConnectionProperties()));
+
+        // Should still NOT be remotely hosted based on cached value.
+        assertFalse(imsConference.isRemotelyHosted());
+        assertEquals(0,
+                imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+    }
+
+    /**
      * Verifies that an ImsConference can handle SIP and TEL URIs for both the P-Associated-Uri and
      * conference event package identities.
      */
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index cfdc2fd..b7fe988 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -23,9 +23,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.net.Uri;
 import android.os.Looper;
 import android.telecom.Conference;
 import android.telecom.Connection;
+import android.telecom.PhoneAccount;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -35,7 +37,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Tests the functionality in TelephonyConferenceController.java
@@ -111,6 +115,46 @@
     }
 
     /**
+     * Verify the connection with "Conference Call" with PROPERTY_IS_DOWNGRADED_CONFERENCE
+     * during SRVCC
+     */
+    @Test
+    @SmallTest
+    public void testSrvccConferenceConnection() {
+        when(mTestTelephonyConnectionA.mMockRadioConnection.getCall()
+                .isMultiparty()).thenReturn(true);
+        when(mTestTelephonyConnectionB.mMockRadioConnection.getCall()
+                .isMultiparty()).thenReturn(true);
+
+        List<Connection> listConnections = Arrays.asList(
+                mTestTelephonyConnectionA, mTestTelephonyConnectionB);
+        when(mMockTelephonyConnectionServiceProxy.getAllConnections()).thenReturn(listConnections);
+
+        mTestTelephonyConnectionA.setAddress(
+                Uri.fromParts(PhoneAccount.SCHEME_TEL, "Conference Call", null), 0);
+        mTestTelephonyConnectionA.setTelephonyConnectionProperties(
+                Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+        mTestTelephonyConnectionB.setAddress(
+                Uri.fromParts(PhoneAccount.SCHEME_TEL, "5551213", null), 0);
+        mTestTelephonyConnectionB.setTelephonyConnectionProperties(
+                Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+
+        // add telephony connection B
+        mControllerTest.add(mTestTelephonyConnectionB);
+
+        // add telephony connection A
+        mControllerTest.add(mTestTelephonyConnectionA);
+
+        // verify the connection with "Conference Call" has PROPERTY_IS_DOWNGRADED_CONFERENCE
+        assertTrue((mTestTelephonyConnectionA.getConnectionProperties()
+                & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+
+        // verify the connection with "5551213" hasn't PROPERTY_IS_DOWNGRADED_CONFERENCE
+        assertFalse((mTestTelephonyConnectionB.getConnectionProperties()
+                & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+    }
+
+    /**
      * Behavior: add telephony connection B and A to conference controller,
      *           set status for connections and merged calls, remove one call
      * Assumption: after performing the behaviours, the status of Connection A is STATE_ACTIVE;
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 817e9a7..e149d3b 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -305,4 +305,12 @@
     public PersistableBundle getCarrierConfigBundle() {
         return mCarrierConfig;
     }
+
+    public ImsPhoneConnection getMockImsPhoneConnection() {
+        return mImsPhoneConnection;
+    }
+
+    public void setMockImsPhoneConnection(ImsPhoneConnection connection) {
+        mImsPhoneConnection = connection;
+    }
 }